文章目录
首先我们来看一下缓冲流的继承结构。
对于字节流和字符流,Java都提供了对应的缓冲流,而每一种有都有输入和输出之分,所以缓冲流一共有四种。
例如 BufferedInputStream
,Buffered
表示缓冲,InputStream
表示字节输入流,因此这个整体表示 字节缓冲输入流
,它可以在拷贝文件的时候高效的读取数据,后面三个也是同样的道理。

首先我们来学习 字节缓冲流
。
字节缓冲流
一、介绍

字节缓冲流
底层自带了长度为 8192
的缓冲区,利用缓冲区可以一次读取 8192个字节
,从而提高了效率。
在创建对象的时候需要注意,缓冲流是高级流,它是对基本流做了一个包装。
缓冲流本身是不能直接读取文件中的数据的,不能直接把数据写到文件中,在创建对象的时候是需要关联基本流的,在底层真正读取数据的,其实还是这两个基本流,只不过有了这两种流的加持,它读写的效率更高而已。

二、练习:拷贝文件(一次读写一个字节)
需求:利用字节缓冲流去拷贝文件。
BufferedInputStream
有两个构造
第一个构造方法:关联了字节输入流,在它里面会有一个长度默认为8192的缓冲区。
第二个构造方法:除了传递一个字节输入流以外,还能手动设定缓冲区的大小。

BufferedOutputStream
同样也有两个构造。
第一个构造方法:关联了字节输出流,在它里面会有一个长度默认为8192的缓冲区。
第二个构造方法:除了传递一个字节输出流以外,还能手动设定缓冲区的大小。

一般来讲我们用第一个就行了。
//1.创建缓冲流的对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("myio\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myio\\a.txt"));
//2.循环读取并写到目的地
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
//3.释放资源,里面的FileInputStream、FileOutputStream就没必要去关了,直接关外面的bis和bos即可
bos.close();
bis.close();
三、缓冲区长什么样?
选中字节缓冲流 BufferedInputStream
跟进看看源码。
在创建对象的时候是将基本流传递进来,还有一个 DEFAULT_BUFFER_SIZE
。

选中 DEFAULT_BUFFER_SIZE
跟进看看,可以发现它是一个 private static int
修饰的静态变量,长度就是 8192
。

ctrl + alt + ← ,回到构造方法,现在我们就知道了,默认缓冲区的大小就为 8192
,同时它会将 基本流
和 8192
传递给其他构造。
选中 this
跟进。
看 201行
,这个就是它的缓冲区,size
就是刚刚传递进来的 8192
。
我们在读取数据的时候就是将读取到的数据放到缓冲区中。

字节缓冲输出流
其实也是一样的。
跟进 BufferedOutputStream
,发现它会把 字节输出流
和 8192
传递给本来的其他构造。

选择 this
跟进,看 75行
,也创建了一个长度为 8192
的字节数组,它就是缓冲区。

四、为什么关流的时候只需要关缓冲流
选中缓冲流的 close()
跟进看看,可以看见在 188行、191行
都将基本流进行了关闭。
在关闭的时候它会做一个判断 if (flushException == null)
,看看你从缓冲区往外刷新数据的时候有没有出异常,如果没有出异常,直接将基本流关闭;如果有异常,它同样也是先关闭基本流,然后再做 catch
中其他的处理。
此时我们就知道了,不管是字节缓冲输出流,还是字节缓冲输入流,在底层其实都是帮我们将基本流做了关闭,所以我们不需要自己来关,直接调用缓冲流的 close()
就行了。

五、练习:拷贝文件(一次读写一个字节数组)
之前的写法一次只能操作一个字节,下面的写法可以一次操作多个字节。
//1.创建缓冲流的对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("myio\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myio\\copy2.txt"));
//2.拷贝(一次读写多个字节)
byte[] bytes = new byte[1024];
int len; // 表示本次读取到了多少个有效字节
while((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
//3.释放资源
bos.close();
bis.close();
字节缓冲流
的读写原理
在拷贝的时候需要有一个 数据源
,还要有一个 目的地
,它们两个都是在硬盘中的。
拷贝文件其实就是将 数据源的数据
读取到 内存
中,再写到目的地。
接下来我们就看一看刚刚的这段代码到底是怎么运行的,怎么就能提高效率呢?

首先我们先来看创建对象,当创建了 字节缓冲输入流
对象的时候,在它里面还关联了基本流,这就表示真正从文件中读取数据的还是基本流 FileInputStream
,它会从文件中读取数据再交给缓冲输入流,准确来说,是放到缓冲输入流的缓冲区中。
因为缓冲区默认大小是 8192
,所以它会一次性读取 8192个字节
。
再来看右边的输出流,它里面其实也关联了 基本流
,这就表示真正将数据写到文件的还是基本流 FileOutputStream
,它会将缓冲区的数据写到本地文件中。
字节缓冲输出流
它的缓冲区默认大小也是 8192
,所以它会一次性把缓冲区中 8192个字节
写到文件目的地。
细节:缓冲输入流
里面有个缓冲区,缓冲输出流
里面也有一个缓冲区,这两个缓冲区不是同一个东西。

对象创建完毕了,然后就是利用下面这段代码进行了循环的读写。
首先定义了一个变量 b
,那么在内存中就有了一个 b
。
然后再来执行 read方法
,这个 read方法
是从左边的缓冲区里面进行读取的,读取一个字节放到变量 b
中。
然后再去调用下面的 write方法
,将读取到的字节再写到右边的缓冲区中。
所以中间的 变量b
,它其实就做了一个倒手,在左右两边来回的倒腾数据,将左边缓冲区中的数据一个一个放到右边的缓冲区中。
当右边的缓冲区填满了,就会利用基本流自动写到目的地。
但如果 变量b
在缓冲区中读不到数据了,此时又需要从文件中读取 8192个字节
放到缓冲区中,再利用变量b再次倒手,当右边的缓冲区填满后,再去写到目的地。
由于中间这段都是在内存中进行的,而内存的运算速度是非常的快的,所以这个倒手的时间可以几乎忽略不计,它真正节约的是 读和写
的时候跟硬盘之间打交道的时间。
如果你定义的是数组,那么就是一次倒手多个数据,倒手的速度会更快而已。
字符缓冲流
一、前言

字符缓冲流
也是自带 长度为8193
的缓冲区提高读写性能。
之前我们已经学习了字符流的基本流,我们知道,基本流本身已经有缓冲区了。
所以现在所学习的 字符缓冲流
它提高的效率不是很明显,但是还是得学习,因为它们里面有两个非常好用的方法,在以后我们会经常用到,因此我们还是得要学习。
二、字符缓冲流
字符缓冲流其实也是对基本流进行了包装,书写代码的思路跟之前的字节缓冲流是一样的。

唯一不一样的是它里面有两个特有的方法。
字符缓冲输入流特有的方法
:可以读一整行数据,遇到 \r\n(回车换行)
的时候才会停止。
因此如果我们使用 readLine()
读取,那就是一行一行的读取数据,如果读到文件末尾了,没有数据可读了,方法会返回 null
。

字符缓冲输出流特有的方法
:跨平台的换行。
在之前我们写回车换行的时候,我们写的是 \r\n
,但是这是不合理的,因为同样的代码如果放到 Mac
或者 Linux
操作系统中,它的运行结果就不一样,我们还要将代码去改一改,非常的麻烦。
那么有了 newLine()
,就非常的方便了。方法的底层会先判断你是什么操作系统,如果你是 Windows
的操作系统,它就写出 \r\n
;如果你是苹果的 Mac
,它就写出 \r
;如果你是 Linux
,那就写出 \n
。

三、字符缓冲输入流
代码实现
a.txt
中的内容如下

不使用 readLine()
也是可以读取的,但是需要将读取的字符一个一个拼接在一起,太麻烦了。
使用 readLine()
就可以一次读一整行了。
//1.创建字符缓冲输入流的对象
BufferedReader br = new BufferedReader(new FileReader("myio\\a.txt"));
//2.读取数据
//细节:
//readLine方法在读取的时候,一次读一整行,遇到回车换行结束,但是他不会把回车换行读到内存当中
//如果将打印的ln删掉,那么它读取到的数据都会在同一行,因此在读取数据的时候需要加上ln手动换行
String line1 = br.readLine();
System.out.println(line1); // 我有一张1000万兰博基尼的30元优惠券
String line2 = br.readLine();
System.out.println(line2); // 但是兰博基尼太吵了
// 将文件中所有东西读取
String line;
while ((( line = br.readLine()) != null)){
System.out.println(line);
}
//3.释放资源
//同样在关流的时候我们同样也只需要关缓冲流就行了,它里面的FileReader可以不用关闭,因为缓冲流close()的底层它会帮我们把基本流进行关闭
br.close();
四、字符缓冲输出流
代码实现
//1.创建字符缓冲输出流的对象
//如果b.txt不存在,那么它会创建,规则跟之前的基本流是一样的
//如果不想清空文件,可以开启续写,但要注意续写是FileReader的功能
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt", true));
//2.写出数据
bw.write("123");
//之前如果我们想要换行,都是下面这样写,但是这种写法不能跨平台
//br.write("\r\n");
bw.newLine();
bw.write("456");
bw.newLine();
//3.释放资源
bw.close();
五、总结
1、缓冲流有几种?
- 字节缓冲输入流:BufferedInputStream
- 字节缓冲输出流:BufferedOutputStream
- 字符缓冲输入流:BufferedReader
- 字符缓冲输出流:BufferedWriter
2、缓冲流为什么能提高性能?
这个问题在有些课程中会说:缓冲流自带8KB缓冲区。
这里的缓冲区它说的其实没错,但是8KB说的不对。
因为字节缓冲流中,缓冲区是 8192
的字节数组,字节数组是 8KB
。
但是在字节缓冲流中,它的缓冲区是 8192
的字符数组。
以 BufferedReader
为例,跟进,可以发现它会将关联的基本流
和 defaultCharBufferSize
传递给另外一个构造方法。

defaultCharBufferSize
:默认字符缓冲区长度,选中它跟进,可以看见它的长度默认 8192
。

ctrl + alt + ←,选中 this
跟进,看 106行
,可以发现它创建的是长度为8192的 字符数组
!
在Java中,一个字符是两个字节,因此字符缓冲流
底层的缓冲区大小为 16KB
。
因此上面那句话应该改为:缓冲流自带长度为8192的缓冲区,字节缓冲流
它的缓冲区是 byte类型
的,长度是 8KB
;字符缓冲流
它的类型是 char类型
的,长度为 16KB
。
通过缓冲区可以显著提高字节流的读写性能。
但是对应字符流提升不明显,因为字符流的基本流中,底层已经有了缓冲区了。
但是字符缓冲流也是有意义的,因为它里面有两个特都有的方法。
3、字符缓冲流两个特有的方法是什么?
- 字符缓冲输入流BufferedReader:
readLine()
- 字符缓冲输出流BufferedWriter:
newLine()