【操作系统】FileOutputStream的flush操作有时不生效

news2024/11/15 14:09:05

按照我们的理解:FileOutputStream的flush()方法的作用就是将缓冲区中的数据立即写入到文件中,即使缓冲区没有填满。这样可以确保数据的及时写入,而不需要等待缓冲区填满或者调用 close() 方法关闭流时才写入。真的是这样吗???

在这里插入图片描述

FileOutputStream的flush()丢数据演示

package com.morris.io;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * FileOutputStream丢失数据测试
 *
 * 直接使用虚拟机的强制关机,就会丢失数据
 */
public class FileOutputStreamMissDataTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("FileOutputStreamMissDataTest.txt");
        outputStream.write("abc1234567890".getBytes());
        outputStream.flush();
        System.in.read();
        outputStream.close();
    }
}

在执行完上面的代码后,直接使用虚拟机的强制关机,相当于拔掉电源,这样我们可以看到文件FileOutputStreamMissDataTest.txt创建了,但是文件大小为0。

FileOutputStream.flush()方法的实现

在为FileOutputStream写入数据后调用了flush(),试图将缓冲区中的字节全部写入文件。但查看flush()源码发现,FileOutputStream并没有实现这个方法,因而调用的实际是其父类OutputStream.flush(),但也只是一个空方法:

public void flush() throws IOException {
}

也就是说FileOutputStream.flush()方法没有任何作用,只有BufferedOutputStream这类实现了缓存区的读写流的flush()才有作用。

BufferedOutputStream.flush()方法的实现

BufferedOutputStream实现了flush()方法:

private void flushBuffer() throws IOException {
    if (count > 0) {
        out.write(buf, 0, count);
        count = 0;
    }
}

public synchronized void flush() throws IOException {
    flushBuffer();
    out.flush();
}

从BufferedOutputStream的源码可以看到,它只是在应用层建了一个数组作为buffer,当数组满了之后才会数据传递给操作系统进行写入,但不保证操作系统马上将这些字节实际写入到磁盘,只是减少了系统调用的次数,提高了写入的效率,并不能保证不丢失数据。

丢数据的原因分析

实际上我们使用OutputStream.write()方法只是将数据写入操作系统中的Page Cache中,至于操作系统何时将数据写入到磁盘中,取决于操作系统下面参数的配置:

$ sudo sysctl -a | grep "dirty"
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
vm.dirtytime_expire_seconds = 43200

具体参数说明:

  • vm.dirty_background_bytes:设置了系统内存中可以保持脏数据的最大字节数。当系统内存中的脏数据超过这个值时,Linux会开始触发后台刷新(异步刷新)将脏数据写入磁盘。

  • vm.dirty_background_ratio:设置了系统内存中可以保持脏数据的最大比例,默认为10%。

  • vm.dirty_bytes:设置了系统内存中允许累积的脏数据的最大字节数。当脏数据超过这个值时,Linux会触发前台刷新(同步刷新),直到将脏数据写入磁盘为止。

  • vm.dirty_ratio:设置了系统内存中允许累积的脏数据的最大比例,默认为20%。

  • vm.dirty_expire_centisecs:该参数指定了脏数据在内存中能够存活的时间,单位为百分之一秒。当脏数据在内存中超过这个时间后,系统会将其异步写入磁盘中,默认值为3000(30秒)。

  • vm.dirty_writeback_centisecs:表示系统在多长时间内进行一次脏数据的后台写回操作。它的单位是百分之一秒(centiseconds),默认值为500,即系统每5秒钟进行一次后台写回操作。

  • vm.dirtytime_expire_seconds:代表内存中脏数据的允许存储时间,单位为秒。当脏数据在内存中存储的时间超过这个时间,系统会将其写入磁盘,以释放内存。

在上面的例子中,我们直接强制关机,相当于拔电源,这样操作系统来不及将Page Cache中的数据写入磁盘,这样就会导致丢失数据。

当然,除了上面操作系统被动的将Page Cache写入磁盘外,还提供了下面的系统调用主动把Page Cache中内容写入磁盘中:

  • sync:将所有未写的系统缓冲区数据写入磁盘,不需要带任何参数。

  • syncfs:syncfs需要一个文件描述符,只将文件描述符指向的文件相关的文件系统的缓冲区数据写入磁盘。

  • fsync:将文件描述符fd引用的文件修改过的元数据和数据写入磁盘。

  • fdatasync:fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。

FileOutputStream手动强制同步Page Cache到磁盘

FileDescriptor.sync()强制所有系统缓冲区与基础设备同步。该方法在此FileDescriptor的所有修改数据和属性都写入相关设备后返回。特别是,如果此FileDescriptor引用物理存储介质,比如文件系统中的文件,则一直要等到将与此FileDesecriptor有关的缓冲区的所有内存中修改副本写入物理介质中,sync方法才会返回。

package com.morris.io;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * FileOutputStream强制同步Page Cache到磁盘
 *
 */
public class FileOutputStreamSyncDataTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream outputStream = new FileOutputStream("FileOutputStreamSyncDataTest.txt");
        outputStream.write("abc1234567890".getBytes());
        outputStream.flush();
        outputStream.getFD().sync();
        outputStream.close();
    }
}

产生的系统调用如下:

openat(AT_FDCWD, "FileOutputStreamSyncDataTest.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0777, st_size=0, ...}) = 0
write(4, "abc1234567890", 13)           = 13
fsync(4)                                = 0
close(4)

可以看到底层也是通过fsync系统调用来完成强制同步Page Cache到磁盘。

FileChannel手动强制同步Page Cache到磁盘

我们的代码中为了提高读写效率,经常会使用FileChannel来操作文件,那么FileChannel中有没有提供手动强制同步Page Cache到磁盘的方法呢?

FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。

package com.morris.io;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

/**
 * FileChannel强制同步Page Cache到磁盘
 */
public class FileChannelSyncDataTest {
    public static void main(String[] args) throws IOException {
        FileChannel fileChannel = new FileOutputStream("FileChannelSyncDataTest.txt").getChannel();
        // 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("abc1234567890".getBytes(StandardCharsets.UTF_8));
        buffer.flip();

        // 将数据从缓冲区写入到输出文件
        fileChannel.write(buffer);
        fileChannel.force(true);
        buffer.clear(); // 清空缓冲区
        fileChannel.close();
    }
}

产生的系统调用如下:

openat(AT_FDCWD, "FileChannelSyncDataTest.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0777, st_size=0, ...}) = 0
write(4, "abc1234567890", 13)           = 13
fsync(4)
close(4)

可以看到底层还是通过fsync系统调用来完成强制同步Page Cache到磁盘。

总结

Page Cache是Linux内核中用于提高文件读写性能的缓存机制,但是它也可能会导致数据丢失。Page Cache在计算机故障时可能会丢失数据,这是因为它的设计目标是在内存中缓存文件数据,而不是持久化存储数据。

当计算机发生故障时,如断电或系统崩溃,Page Cache中的数据可能会丢失,因为这些数据还没有被写入到磁盘上。此外,Page Cache的刷盘策略也会导致数据丢失。当Page Cache中的数据太多或太脏时,内核会将一些数据写入磁盘,但如果此时系统崩溃,尚未写入磁盘的数据可能会丢失。

为了减少数据丢失的风险,可以采取一些措施。首先,定期备份重要数据是一个好习惯,这样可以确保在Page Cache中的数据丢失后,可以从备份中恢复数据。其次,可以适当调整Page Cache的大小和刷盘策略,以减少数据丢失的风险。最后,可以使用持久化存储技术,如SSD或RAID,来提高数据的可靠性和持久性。

总之,虽然Page Cache可以提高文件读写性能,但它也可能会导致数据丢失。为了确保数据的可靠性和持久性,应该采取适当的措施来减少数据丢失的风险。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1424619.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

dv和ov通配符SSL证书的区别

SSL数字证书是一种数字证书,可以保护网站传输数据安全以及对服务器身份进行验证,SSL证书有很多种,而通配符证书则是其中的一种特殊类型。SSL证书旗下的通配符SSL证书随着互联网的发展,颁发量也越来越多,为了使用户选择…

websocket编写聊天室

【黑马程序员】WebSocket打造在线聊天室【配套资料源码】 总时长 02:45:00 共6P 此文章包含第1p-第p6的内容 简介 温馨提示:现在都是第三方支持聊天,如极光,学这个用于自己项目完全没问题,大项目不建议使用 需求分析 代码

Celeryconfig配置文件

Celery配置文件 本篇介绍Celery配置文件相关,celeryconfig.py Celeryconfig.py 在上篇celery基础用法中,是这样使用celery的实例化的。 # tasks.py 文件名需与实例化对象第一个参数一致 import time from celery import Celeryredis_url redis://Pa…

重磅!讯飞星火V3.5正式发布,3大核心能力超GPT-4 Turbo!

1月30日,科大讯飞召开星火认知大模型V3.5升级发布会,这是国内首个基于全国产算力训练的多模态认知大模型。科大讯飞董事长刘庆峰先生、研究院院长刘聪先生出席了大会,并对最新产品进行了多维度解读。 讯飞星火V3.5的7大核心能力实现全面大幅…

用的到的linux-文件移动-Day2

前言: 在上一节,我们复习了cd大法和创建生成文件和文件夹的方法,介绍了一些“偷懒”(高效)的小技巧,本节,我们一起来探讨下,我们对文件移动操作时有哪些可以偷懒的小技巧~ 一、复制…

Oracle 集群】RAC知识图文详细教程(四)--缓存融合技术和主要后台进程

Cache Fusion 原理 前面已经介绍了 RAC 的后台进程,为了更深入的了解这些后台进程的工作原理,先了解一下 RAC 中多节点对共享数据文件访问的管理是如何进行的。要了解 RAC 工作原理的中心,需要知道 Cache Fusion 这个重要的概念,要…

基于腾讯云服务器搭建幻兽帕鲁服务器保姆级教程

随着网络游戏的普及,越来越多的玩家希望能够拥有自己的游戏服务器,以便能够自由地玩耍。而腾讯云服务器作为一个优秀的云计算平台,为玩家们提供了一个便捷、稳定、安全的游戏服务器解决方案。本文将为大家介绍如何基于腾讯云服务器搭建幻兽帕…

三、C++中的Mat对象

图片在C中是作为矩阵Matrix进行处理对待的,通过Mat数据类型进行处理 新建项目这里就不再赘述了哈,可以参考博文:零、环境搭建(第三部分Visula Studio中新建项目) 我这边创建的项目名称为:1_31_matrix 为了养成良好的项目开发习惯…

ubuntu QT openssl支持https

1、Building Qt 5 from Git - Qt Wiki 2、下载编译对应的opengssl [ 1.1.1 ] - /source/old/1.1.1/index.html 3、安装所需基础工具 sudo apt-get install build-essential perl python3 git sudo apt-get install ^libxcb.*-dev libx11-xcb-dev libglu1-mesa-dev libxrende…

基于YOLOv8的水下生物检测,多种优化方法---MSAM(CBAM升级版)助力涨点(二)

💡💡💡本文主要内容:详细介绍了水下生物检测整个过程,从数据集到训练模型到结果可视化分析,以及如何优化提升检测性能。 💡💡💡加入自研注意力MSAM mAP0.5由原始的0.522提升至0.534…

Epicypher—SMARCA2 Chromatin Remodeling Enzyme (Human BRM)

EpiCypher是一家为表观遗传学和染色质生物学研究提供高质量试剂和工具的专业制造商。EpiCypher(国内授权代理 欣博盛生物)能提供全长重组人SMARCA2重塑酶。SMARCA2是ATP依赖性染色质重塑复合物SWI/SNF的核心ATP酶亚基,可动员核小体&#xff0…

第二百九十九回

文章目录 1. 概念介绍2. 实现方法2.1 使用Steam实现2.2 使用Timer实现 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何实现每隔一段时间执行某项目任务"相关的内容,本章回中将介绍如何实现倒计时功能.闲话休提,让我们一起Talk Flutter吧…

Elasticsearch:Geoshape query

Geoshape 查询可以用于过滤使用 geo_shape 或 geo_point 类型索引的文档。 geo_shape 查询使用与 geo_shape 或 geo_point 映射相同的索引来查找具有与查询形状相关的形状的文档,并使用指定的空间关系:相交(intersect)、包含(con…

【百度Apollo】本地调试仿真:加速自动驾驶系统开发的利器

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下…

【C++干货基地】C++引用与指针的区别:深入理解两者特性及选择正确应用场景

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 引入 哈喽各位铁汁们好啊,我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发,不知道各位的…

go并发编程-runtime、Channel与Goroutine

1. runtime包 1.1.1. runtime.Gosched() 让出CPU时间片,重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤,但是你妈让你去相亲,两种情况第一就是你相亲速度非常快,见面就黄不耽误你继续烧烤,第二种情况就是你相亲速度…

C#,入门教程(36)——尝试(try)捕捉(catch)不同异常(Exception)的点滴知识与源代码

上一篇: C#,入门教程(35)——哈希表(Hashtable)的基础知识与用法https://blog.csdn.net/beijinghorn/article/details/124236243 1、try catch 错误机制 Try-catch 语句包含一个后接一个或多个 catch 子句的 try 块,这…

项目:博客

1. 运行环境: 主机 主机名 系统 服务 192.168.223.129 Server_Web Linux Web 192.168.48.131 Server-NFS-DNS Linux NFS/DNS 2. 基础配置 配置主机名,静态IP地址 开启防火墙并配置 部分开启SElinux并配置 服务器之间使用同ntp.aliyun.com进行…

prometheus和alertmanager inhibit_rules抑制的使用

172.16.10.21 prometheus 172.16.10.33 altermanager 172.16.10.59 mysql服务,node探针以及mysql的探针 [rootk8s-node02 ~]# docker ps -a CONTAINER ID IMAGE …

SpringBoot+BCrypt算法加密

BCrypt是一种密码哈希函数,BCrypt算法使用“盐”来加密密码,这是一种随机生成的字符串,可以在密码加密过程中使用,以确保每次加密结果都不同。盐的使用增强了安全性,因为攻击者需要花费更多的时间来破解密码。 下图为…