【Java】缓冲流(字节缓冲流和字符缓冲流)

首先我们来看一下缓冲流的继承结构。

对于字节流和字符流,Java都提供了对应的缓冲流,而每一种有都有输入和输出之分,所以缓冲流一共有四种。

例如 BufferedInputStreamBuffered 表示缓冲,InputStream 表示字节输入流,因此这个整体表示 字节缓冲输入流,它可以在拷贝文件的时候高效的读取数据,后面三个也是同样的道理。

image-20240503211436230

首先我们来学习 字节缓冲流

字节缓冲流

一、介绍

image-20240503211539580

字节缓冲流 底层自带了长度为 8192 的缓冲区,利用缓冲区可以一次读取 8192个字节,从而提高了效率。

在创建对象的时候需要注意,缓冲流是高级流,它是对基本流做了一个包装。

缓冲流本身是不能直接读取文件中的数据的,不能直接把数据写到文件中,在创建对象的时候是需要关联基本流的,在底层真正读取数据的,其实还是这两个基本流,只不过有了这两种流的加持,它读写的效率更高而已。

image-20240503211741452

二、练习:拷贝文件(一次读写一个字节)

需求:利用字节缓冲流去拷贝文件。

BufferedInputStream 有两个构造

第一个构造方法:关联了字节输入流,在它里面会有一个长度默认为8192的缓冲区。

第二个构造方法:除了传递一个字节输入流以外,还能手动设定缓冲区的大小。

image-20240503212211387

BufferedOutputStream 同样也有两个构造。

第一个构造方法:关联了字节输出流,在它里面会有一个长度默认为8192的缓冲区。

第二个构造方法:除了传递一个字节输出流以外,还能手动设定缓冲区的大小。

image-20240503212843321

一般来讲我们用第一个就行了。

//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

image-20240503213530391

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

image-20240503213650699

ctrl + alt + ← ,回到构造方法,现在我们就知道了,默认缓冲区的大小就为 8192,同时它会将 基本流8192 传递给其他构造。

选中 this 跟进。

201行,这个就是它的缓冲区,size 就是刚刚传递进来的 8192

我们在读取数据的时候就是将读取到的数据放到缓冲区中。

image-20240503213909868

字节缓冲输出流 其实也是一样的。

跟进 BufferedOutputStream,发现它会把 字节输出流8192 传递给本来的其他构造。

image-20240503214100189

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

image-20240503214212365

四、为什么关流的时候只需要关缓冲流

选中缓冲流的 close() 跟进看看,可以看见在 188行、191行 都将基本流进行了关闭。

在关闭的时候它会做一个判断 if (flushException == null),看看你从缓冲区往外刷新数据的时候有没有出异常,如果没有出异常,直接将基本流关闭;如果有异常,它同样也是先关闭基本流,然后再做 catch 中其他的处理。

此时我们就知道了,不管是字节缓冲输出流,还是字节缓冲输入流,在底层其实都是帮我们将基本流做了关闭,所以我们不需要自己来关,直接调用缓冲流的 close() 就行了。

image-20240503214344915

五、练习:拷贝文件(一次读写一个字节数组)

之前的写法一次只能操作一个字节,下面的写法可以一次操作多个字节。

//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();

字节缓冲流 的读写原理

在拷贝的时候需要有一个 数据源,还要有一个 目的地,它们两个都是在硬盘中的。

拷贝文件其实就是将 数据源的数据 读取到 内存 中,再写到目的地。

接下来我们就看一看刚刚的这段代码到底是怎么运行的,怎么就能提高效率呢?

image-20240503220010074

首先我们先来看创建对象,当创建了 字节缓冲输入流 对象的时候,在它里面还关联了基本流,这就表示真正从文件中读取数据的还是基本流 FileInputStream,它会从文件中读取数据再交给缓冲输入流,准确来说,是放到缓冲输入流的缓冲区中。

因为缓冲区默认大小是 8192,所以它会一次性读取 8192个字节

再来看右边的输出流,它里面其实也关联了 基本流,这就表示真正将数据写到文件的还是基本流 FileOutputStream,它会将缓冲区的数据写到本地文件中。

字节缓冲输出流 它的缓冲区默认大小也是 8192,所以它会一次性把缓冲区中 8192个字节 写到文件目的地。

细节:缓冲输入流 里面有个缓冲区,缓冲输出流 里面也有一个缓冲区,这两个缓冲区不是同一个东西。

image-20240504082728069

对象创建完毕了,然后就是利用下面这段代码进行了循环的读写。

首先定义了一个变量 b,那么在内存中就有了一个 b

然后再来执行 read方法,这个 read方法 是从左边的缓冲区里面进行读取的,读取一个字节放到变量 b 中。

然后再去调用下面的 write方法,将读取到的字节再写到右边的缓冲区中。

所以中间的 变量b ,它其实就做了一个倒手,在左右两边来回的倒腾数据,将左边缓冲区中的数据一个一个放到右边的缓冲区中。

当右边的缓冲区填满了,就会利用基本流自动写到目的地。

但如果 变量b 在缓冲区中读不到数据了,此时又需要从文件中读取 8192个字节 放到缓冲区中,再利用变量b再次倒手,当右边的缓冲区填满后,再去写到目的地。

image-20240504083039630

由于中间这段都是在内存中进行的,而内存的运算速度是非常的快的,所以这个倒手的时间可以几乎忽略不计,它真正节约的是 读和写 的时候跟硬盘之间打交道的时间。

如果你定义的是数组,那么就是一次倒手多个数据,倒手的速度会更快而已。

image-20240504083507805


字符缓冲流

一、前言

image-20240504083643121

字符缓冲流 也是自带 长度为8193 的缓冲区提高读写性能。

之前我们已经学习了字符流的基本流,我们知道,基本流本身已经有缓冲区了。

所以现在所学习的 字符缓冲流 它提高的效率不是很明显,但是还是得学习,因为它们里面有两个非常好用的方法,在以后我们会经常用到,因此我们还是得要学习。


二、字符缓冲流

字符缓冲流其实也是对基本流进行了包装,书写代码的思路跟之前的字节缓冲流是一样的。

image-20240504083922323

唯一不一样的是它里面有两个特有的方法。

字符缓冲输入流特有的方法:可以读一整行数据,遇到 \r\n(回车换行) 的时候才会停止。

因此如果我们使用 readLine() 读取,那就是一行一行的读取数据,如果读到文件末尾了,没有数据可读了,方法会返回 null

image-20240504084053423

字符缓冲输出流特有的方法:跨平台的换行。

在之前我们写回车换行的时候,我们写的是 \r\n,但是这是不合理的,因为同样的代码如果放到 Mac 或者 Linux 操作系统中,它的运行结果就不一样,我们还要将代码去改一改,非常的麻烦。

那么有了 newLine() ,就非常的方便了。方法的底层会先判断你是什么操作系统,如果你是 Windows 的操作系统,它就写出 \r\n;如果你是苹果的 Mac,它就写出 \r;如果你是 Linux,那就写出 \n

image-20240504084100613

三、字符缓冲输入流 代码实现

a.txt 中的内容如下

image-20240504085315149

不使用 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 传递给另外一个构造方法。

image-20240504090744785

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

image-20240504090808913

ctrl + alt + ←,选中 this 跟进,看 106行,可以发现它创建的是长度为8192的 字符数组

在Java中,一个字符是两个字节,因此字符缓冲流底层的缓冲区大小为 16KB

因此上面那句话应该改为:缓冲流自带长度为8192的缓冲区,字节缓冲流 它的缓冲区是 byte类型 的,长度是 8KB字符缓冲流 它的类型是 char类型 的,长度为 16KB

通过缓冲区可以显著提高字节流的读写性能。

但是对应字符流提升不明显,因为字符流的基本流中,底层已经有了缓冲区了。

但是字符缓冲流也是有意义的,因为它里面有两个特都有的方法。


3、字符缓冲流两个特有的方法是什么?

  • 字符缓冲输入流BufferedReader:readLine()
  • 字符缓冲输出流BufferedWriter:newLine()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值