文章目录
首先我们来看一下缓冲流的继承结构。
对于字节流和字符流,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()
&spm=1001.2101.3001.5002&articleId=140393753&d=1&t=3&u=f61c7e70bd614fccb45419050ee19982)
551

被折叠的 条评论
为什么被折叠?



