Java BufferedOutputStream类的常用方法讲解

目录
  • BufferedOutputStream类的常用方法
    • 构造方式
    • 常用方法
    • 程序示例
  • BufferedOutputStream深入分析
    • 代码准备
    • 原因分析
    • 手动刷盘
    • buffer源码分析
    • 关于buf缓冲数据大小设置

BufferedOutputStream类的常用方法

BufferedOutputStream字节缓冲输出流

构造方式

第一种开发中

public BufferedOutputStream(OutputStream out)

采用的默认的缓冲区大小(足够大了) ,来构造一个字节缓冲输出流对象

public BufferedOutputStream(OutputStream out,int size)

指定size缓冲区大小构造缓冲输出流对象

IllegalArgumentException - 如果 size <= 0

常用方法

public void write(int b)throws IOException

一次写一个字节

  • b - 要写入的字节。
public void write(byte[] b,int off,int len) throws IOException

一次写一个字节数组的一部分

  • b - 数据。
  • off - 数据的起始偏移量。
  • len - 要写入的字节数。
public void flush() throws IOException

刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。

public void close() throws IOException

关闭此输出流并释放与此流有关的所有系统资源。

FilterOutputStream 的 close 方法先调用其 flush 方法,然后调用其基础输出流的 close 方法。

程序示例

public static void main(String[] args) throws Exception {
    //符合Java一种设计模式:装饰者设计模式(过滤器:Filter)
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt")) ;
    //写数据
    bos.write("hello".getBytes());
    //释放资源
    bos.close();
}

BufferedOutputStream深入分析

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

代码准备

public class BufferFile {
    public static void main(String[] args) {
        //每次向文件中写入一个8字节的数组
        byte[] bytes = "1234567\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 < 10000000; 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 < 10000000; 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 = "1234567\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 < 10000000; 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 < 10000000; 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、要注意堆内存的消耗。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java中的BufferedInputStream与BufferedOutputStream使用示例

    BufferedInputStream  BufferedInputStream 是缓冲输入流.它继承于FilterInputStream. BufferedInputStream 的作用是为另一个输入流添加一些功能,例如,提供"缓冲功能"以及支持"mark()标记"和"reset()重置方法". BufferedInputStream 本质上是通过一个内部缓冲区数组实现的.例如,在新建某输入流对应的BufferedInputStream后,当我

  • Java基础:流Stream详解

    目录 写在前面 一."流"概念 二.流的分类 1.按流的方向分为:输入流.输出流 2.按流处理数据的单位分为:字节流.字符流 3.按流的功能分为:节点流(又称低级流).过滤流(又称高级流.处理流.包装流) 4.字节流与字符流区别 三.流的方法 1.字节流 字节输入流类:FileInputStream.BufferedInputStream和DataInputStream 构造方法: 常用方法: 构造方法: 常用方法: 构造方法: 常用方法: 字节输出流类:FileOutputStrea

  • InputStreamReader和BufferedReader用法及实例讲解

    一.BufferedReader类 . 所属类库: java.lang.Object java.io.Reader java.io.BufferedReader . 基本概念 : public class BufferedReader    extends Reader 从字符输入流中读取文本,缓冲各个字符,从而实现字符.数组和行的高效读取. 可以指定缓冲区的大小,或者可使用默认的大小.大多数情况下,默认值足够大. 通常, Reader 所作的每个读取请求都会导致对底层字符或字节流进行相应的读取

  • Java BufferedOutputStream类的常用方法讲解

    目录 BufferedOutputStream类的常用方法 构造方式 常用方法 程序示例 BufferedOutputStream深入分析 代码准备 原因分析 手动刷盘 buffer源码分析 关于buf缓冲数据大小设置 BufferedOutputStream类的常用方法 BufferedOutputStream字节缓冲输出流 构造方式 第一种开发中 public BufferedOutputStream(OutputStream out) 采用的默认的缓冲区大小(足够大了) ,来构造一个字节缓

  • Java File类的常用方法总结

    Java File类的功能非常强大,利用Java基本上可以对文件进行所有的操作.本文将对Java File文件操作类进行详细地分析,并将File类中的常用方法进行简单介绍,有需要的Java开发者可以看一下. 构造函数 复制代码 代码如下: public class FileDemo {      public static void main(String[] args){          //构造函数File(String pathname)          File f1 =new Fi

  • java string类的常用方法详细介绍

    String : 字符串类型 一.构造函数 复制代码 代码如下: String(byte[ ] bytes):通过byte数组构造字符串对象. String(char[ ] value):通过char数组构造字符串对象. String(Sting original):构造一个original的副本.即:拷贝一个original. String(StringBuffer buffer):通过StringBuffer数组构造字符串对象. 例如: 复制代码 代码如下: byte[] b = {'a',

  • Java String类的常用方法汇总

    一.String类 String类在java.lang包中,java使用String类创建一个字符串变量,字符串变量属于对象.java把String类声明的final类,不能有类.String类对象创建后不能修改,由0或多个字符组成,包含在一对双引号之间. 二.String类对象的创建 字符串声明:String stringName; 字符串创建:stringName = new String(字符串常量);或stringName = 字符串常量; 三.String类构造方法 1.public

  • Java BeanUtils工具类常用方法讲解

    谨慎使用这个copyproperties这个功能,相同的属性都会被替换,不管是否有值  BeanUtils 是 Apache commons组件的成员之一,主要用于简化JavaBean封装数据的操作.它可以给JavaBean封装一个字符串数据,也可以将一个表单提交的所有数据封装到JavaBean中.使用第三方工具,需要导入jar包: BeanUtils工具常用工具类有两个:BeanUtils.ConvertUtils.BeanUtils用于封装数据,ConvertUtils用于处理类型转换,常用

  • Java之常用类小结案例讲解

    Java常用类 包装类 由于Java语言中的基本类型不是面向对象,并不具备对象的性质,实际使用存在很多不便.Java在java.lang包中提供了八种基本类型对应的包装类,可以方便地将它们转化为对象进行处理,并且可以调用一些方法.Java中基本类型和包装类的对应关系如下表所示: 基本数据类型名称 包装类名称 byte Byte short Short int Integer long Long float Float double Double char Character boolean Bo

  • Java ArrayList类的基础使用讲解

    目录 什么是ArrayList类 ArrayList使用步骤 常用方法和遍历 如何存储基本数据类型 数组的长度是固定的,无法适应数据变化的需求.为了解决这个问题,Java提供了另一个容器 java.util.ArrayList集合类,让我们可以更便捷的存储和操作对象数据. 什么是ArrayList类 java.util.ArrayList 是大小可变的数组的实现,存储在内的数据称为元素.此类提供一些方法来操作内部存储 的元素. ArrayList 中可不断添加元素,其大小也自动增长. Array

  • Java中File类中常用方法详解

    java.io包下的File类用于描述和创建一个文件或文件夹对象,只能对文件或文件夹做一些简单操作,不能修改文件的内容,功能比较有限.下面是对于File类中常用方法的程序演示. [1] 演示程序一 package pack01; import java.io.*; import java.sql.Date; public class FileTest { public static void main(String[] args) { File file1 = new File("d:/TEST

  • Java StringBuilder类原理及常用方法

    这篇文章主要介绍了Java StringBuilder类原理及常用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 StringBuilder类的由来 由于String类的对象内容不可改变(底层是一个被final修饰的数组),所以每当我们进行字符串拼接时,总是会在内存中创建一个新的对象.如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间.为了解决这一问题,我们可以使用java.lang.StringBui

  • java自定义Scanner类似功能类的实例讲解

    读取键盘输入 package com.zjx.io; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * 面试题 * 读取键盘各个数据类型 * */ public class TestFaceIo { public static void main(String[] args) { System.out.print("请输入姓名: "); S

随机推荐