Java BufferedOutputStream类的常用方法讲解

网友投稿 217 2022-12-02

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_JvHiVGZpxutest_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、要注意堆内存的消耗。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:FZU 2150 Fire Game (暴力BFS)
下一篇:Codeforces Round #313 (Div. 2)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~