BufferedOutputStream深入分析

BufferedOutputStream深入分析FileOutputSt 和 BufferedOutp 都提供了一系列的将数据写入文件的方式 并且我们都知道 BufferedOutp 要比直接使用 FileOutputSt 写入速度要快 本文通过案例实际演示一下两者的区别 代码准备 public class BufferFile public

大家好,我是讯享网,很高兴认识大家。

FileOutputStream和BufferedOutputStream都提供了一系列的将数据写入文件的方式,并且我们都知道BufferedOutputStream要比直接使用FileOutputStream写入速度要快,本文通过案例实际演示一下两者的区别。

代码准备

public class BufferFile { 
    public static void main(String[] args) { 
    //每次向文件中写入一个8字节的数组 byte[] bytes = "\n".getBytes(); //每隔100毫秒通过buffer的方式向文件中写入数据 new Thread(() -> { 
    System.out.println("buffer_while start..."); File file = new File("/var/file_test_data/out_buffer_while.txt"); FileOutputStream fileOutputStream; try { 
    fileOutputStream = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); while (true) { 
    Thread.sleep(100); bufferedOutputStream.write(bytes); } } catch (Exception e) { 
    e.printStackTrace(); } }).start(); //通过buffer的方式向文件中写入1千万次 new Thread(() -> { 
    System.out.println("buffer_for start..."); File file = new File("/var/file_test_data/out_buffer_for.txt"); FileOutputStream fileOutputStream; try { 
    fileOutputStream = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); for (int i = 0; i < ; i++) { 
    bufferedOutputStream.write(bytes); } } catch (Exception e) { 
    e.printStackTrace(); } System.out.println(new Date() + ": buffer_for end..."); }).start(); //通过file的方式向文件中写入1千万次 new Thread(() -> { 
    System.out.println("file_for start..."); File file = new File("/var/file_test_data/out_file_for.txt"); FileOutputStream fileOutputStream; try { 
    fileOutputStream = new FileOutputStream(file); for (int i = 0; i < ; i++) { 
    fileOutputStream.write(bytes); } } catch (Exception e) { 
    e.printStackTrace(); } System.out.println(new Date() + ": file_for end..."); }).start(); } } 

讯享网

开始运行

在这里插入图片描述
讯享网
强行停止后的运行结果

在这里插入图片描述
1、file和buffe写入速度比较

两者分别写入1千万次,时间上buffer比file快8秒,如果当写入次数指数级增加时,buffer的优势将更加明显。

2、数据写入完整性问题

buffer虽然要比file快,但是从最终数据上可以看出,buffer会丢数据

讯享网当第一个线程写入时数据还未满8kb时,强制停止java进程,最终out_buffer_while.txt没有数据。 第二个线程,虽然最终代码执行完毕,但是比较file方式,out_buffer_for.txt文件看起来也丢了一部分数据。 

原因分析

当使用buffer读写文件时,数据并没有直接被写入磁盘,而是被缓存到一个字节数据中,这个字节数组的大小是8kb,默认情况下只有当8kb被填充满了以后,数据才会被一次性写入磁盘,这样一来就大大减少了系统调用的次数(file是每一次write都会产生系统调用),当然也正是因为buffer中的每一次write只是写入到内存中(JVM自身内存中),所以当数据未写入磁盘前,如果JVM进程挂了,那么就会造成数据丢失。

手动刷盘

为了解决数据丢失的问题,buf中提供了flush()方法,用户可以自行决定合适将数据刷写到磁盘中

  • 如果你的flush()调用的非常频繁,那就会退化为普通的file模式了。
  • 如果你的flush()调用的又不太频繁,那么丢数据的可能性就比较高。
  • 无论如何业务逻辑中数据写完时,一定要调用一次flush(),确保缓冲区的数据刷到磁盘上。

将无限循环写入的代码注释掉,在buf写1千万完成后,加上bufferedOutputStream.flush();

public class BufferFile { 
    public static void main(String[] args) { 
    //每次向文件中写入一个8字节的数组 byte[] bytes = "\n".getBytes(); //每隔100毫秒通过buffer的方式向文件中写入数据 /*new Thread(() -> { System.out.println("buffer_while start..."); File file = new File("/var/file_test_data/out_buffer_while.txt"); FileOutputStream fileOutputStream; try { fileOutputStream = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); while (true) { Thread.sleep(100); bufferedOutputStream.write(bytes); } } catch (Exception e) { e.printStackTrace(); } }).start();*/ //通过buffer的方式向文件中写入1千万次 new Thread(() -> { 
    System.out.println("buffer_for start..."); File file = new File("/var/file_test_data/out_buffer_for.txt"); FileOutputStream fileOutputStream; try { 
    fileOutputStream = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); for (int i = 0; i < ; i++) { 
    bufferedOutputStream.write(bytes); } bufferedOutputStream.flush(); } catch (Exception e) { 
    e.printStackTrace(); } System.out.println(new Date() + ": buffer_for end..."); }).start(); //通过file的方式向文件中写入1千万次 new Thread(() -> { 
    System.out.println("file_for start..."); File file = new File("/var/file_test_data/out_file_for.txt"); FileOutputStream fileOutputStream; try { 
    fileOutputStream = new FileOutputStream(file); for (int i = 0; i < ; i++) { 
    fileOutputStream.write(bytes); } } catch (Exception e) { 
    e.printStackTrace(); } System.out.println(new Date() + ": file_for end..."); }).start(); } } 

这次再看数据写入完整了

在这里插入图片描述

buffer源码分析

类的机构图
在这里插入图片描述

首先当创建一个BufferedOutputStream对象时,构造方法就初始化了缓冲的字节数组大小为8kb

讯享网 protected byte buf[]; public BufferedOutputStream(OutputStream out) { 
    this(out, 8192); } public BufferedOutputStream(OutputStream out, int size) { 
    super(out); if (size <= 0) { 
    throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } 

当调用buffer.write(b)时,调用的是父类FilterOutputStream的方法

 public void write(byte b[]) throws IOException { 
    //写入的字节数组b,从0开始,一共要写入的长度 write(b, 0, b.length); } public void write(byte b[], int off, int len) throws IOException { 
    if ((off | len | (b.length - (len + off)) | (off + len)) < 0) throw new IndexOutOfBoundsException(); //遍历数组,一个字节一个字节的把数据写入数组中 for (int i = 0 ; i < len ; i++) { 
    write(b[off + i]); } } public synchronized void write(int b) throws IOException { 
    //判断字节长度是否超过buf.length,buf在初始化已经指定大小为8192,即8kb //如果超过则调用flushBuffer if (count >= buf.length) { 
    flushBuffer(); } 把每一个字节写入缓冲的buf数组中,并且统计值count++ buf[count++] = (byte)b; } private void flushBuffer() throws IOException { 
    if (count > 0) { 
    //真正的调用OutputStream,写入数据到磁盘中 //写入buf缓冲字节数组数据,从0下标开始,一直写到count,即有多少写多少。 out.write(buf, 0, count); count = 0; } } 

关于buf缓冲数据大小设置

buffer提供了可以自定义缓冲大小的构造方法

讯享网 public BufferedOutputStream(OutputStream out, int size) { 
    super(out); if (size <= 0) { 
    throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } 

如果缓冲大小设置的比较大。

  • 好处:进一步减少调用系统内核写数据的方法,提高写入速度,kafka的批写入默认就是16kb写一次。
  • 坏处:1、丢失的数据可能会更多,2、要注意堆内存的消耗。
小讯
上一篇 2025-02-14 17:38
下一篇 2025-03-28 13:51

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/57191.html