目录
1. 缓冲流的简单介绍
2. 为什么要用到缓冲流
3. 缓冲流是如何提高读写性能的?
3.1 BufferedInputStream 源码
3.2 BufferdOutputStream 源码
3.3 BufferReader 源码
3.4 BufferWriter 源码
4. BufferInputStream和BufferOutputStream
5. BufferedReader 与 BufferedWriter
我们上贴说到了 FileInputStream,FileOutputStream,FileReader,FileWriter。
其实这四个流,我们通常把它叫做原始流,它们是比较偏底层的;而今天我们要说的四个缓冲流,如下图所示,叫包装流,也叫处理流,它们之间也有继承关系自下而上,并且它们都可以提高原始流读写数据的性能。
我先来说一下为什么要用到字节缓冲流,我们来看下面这幅图。
现在有一个需求,将D盘下一个大小为 16KB 的文件拷贝到C盘,该怎么做?
如果我们采用原始的字节流或者字符流,我们首先定义一个大小为 1KB 的数组,然后通过输入流读取数据,将数据读取到字节数组中, 然后再使用输出流将数组中的数据写入到C盘,由于数据源大小为16KB,我们输入流需要读取16次,输出流同样也要输出16次,这样合计就是32次,非常消耗系统资源,那么有没有什么更好的解决方案呢,在不改变题目的前提下,让我们减少与磁盘的IO次数,提高程序整体的运行效率呢?
当然是有的,它就是我们接下来要说的缓冲流
如上图所示,缓冲流之所以能提高读写的性能,是因为如果我们使用缓存输入流,那么它就会在内存中开辟一处大小为8KB输入缓存区,缓存输出流也是同样的道理,它也会在内存中开辟一处大小为8KB的输出缓存区;
还拿刚才的例子来说,当我们读取文件时,这8KB的输入缓存区会直接读满数据源中8KB的资源,然后,我们的缓存输出流在输出时,会从这8KB的缓存区读取内容,读取到内容之后,再写入另外8KB的输出缓冲区,当输出缓存区满了之后,会一次性的将8KB的数据以下次写入硬盘,由于内存中的读写效率非常的高,所以时间可以忽略不计,我们只看从D盘到内存和从内存到C盘的次数.
16KB需要读取两次,也需要输出两次,共计四次。而我们使用之前输入流和输出流,需要读写32次,效率提高了8倍,可以说是极大地提高了读写效率。
我们下面来看一下源码就知道了
上图是 BufferdInputStream 的源码类,这里定义了一个 int变量 大小为 8192,就是1024 * 8
往下翻可以找到 BufferdInputStream 的构造方法,我们点击 this 查看
可以看到这里它 new 了一个数组,size 就是我们刚才说的那个 int 值,如此看来就是new 了一个大小为 8KB 的字节数组。
这里也是一样,8192 ,也为8KB,点击this 方法查看如下
它也是 new 了一个大小为 8KB 的字节数组。
以上即为 BufferdReader 的源码,可以看到它这里也是 new 了一个大小为 8KB 的字符数组
以上为 BufferdWriter 的源码,也是定义了一个大小为 8BK 的字符数组。
总的来说,缓冲流能提高读写效率的原因,是因为它们会在内存开辟缓存空间,减少了与磁盘的IO次数,从而提高了读写效率。
BufferdInputStream 和 BufferdOutputStream 的构造器如下所示,它们分别需要传入一个 InputStream 的对象 和 OutputStream 的对象。
刚才我们看源码也看到了,其实BufferedInputStream还有一个构造方法,我们可以自行传入一个数组的大小,如果不传入,默认为8KB,我们也可以重载该构造方法,自行传入,例如传入一个 1024 * 16 ,那么就是在内存中创建一个 16KB大小的 缓存输入流空间,其它流也是同样的道理。
我做一个简单的案例,还是从C盘拷贝一个文件到D盘,如下代码
public static void main(String[] args) {
// 创建输入流对象
InputStream is = null;
// 创建输出流对象
OutputStream os = null;
// 创建字节缓冲输入流对象
BufferedInputStream bis = null;
// 创建字节缓冲输出流对象
BufferedOutputStream bos = null;
try {
is = new FileInputStream("user-service/test.txt");
os = new FileOutputStream("D:/test2.txt");
bis = new BufferedInputStream(is);
bos = new BufferedOutputStream(os);
// 定义一个大小为 1024 的字节数组
byte[] bytes = new byte[1024];
// 定义一个长度变量,记录读取到的字节数量
int length;
// 定义循环读取文件内容
while((length = bis.read(bytes)) != -1){
// 将读取到的内容写入磁盘
bos.write(bytes,0,length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (bos != null)
bos.close();
if (bis != null)
bis.close();
if (os != null)
os.close();
if (is != null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行上述代码,得到如下结果,我们到D盘查看,可以看到拷贝成功
BufferedReader 与 BufferedWriter 的构造器根字节缓冲输入流,字节缓冲输出流极其相似,都是要传入一个流对象,这里就不需要我做过多的赘述了,适用语法也半差不差;
在字符缓冲输入流中还新增了一个方法,可以一行一行的读取数据
在字符缓冲输出流中也新增了一个方法,换行操作
我们把刚才字节缓冲流的代码稍微改动一下仍然可以用,如下
public static void main(String[] args) {
// 创建输入流对象
Reader r = null;
// 创建输出流对象
Writer w = null;
// 创建字节缓冲输入流对象
BufferedReader br = null;
// 创建字节缓冲输出流对象
BufferedWriter bw = null;
try {
r = new FileReader("user-service/test.txt");
w = new FileWriter("D:/test3.txt");
br = new BufferedReader(r);
bw = new BufferedWriter(w);
// 定义一个大小为 1024 的字节数组
char[] chars = new char[1024];
// 定义一个长度变量,记录读取到的字节数量
int length;
// 定义循环读取文件内容
while((length = br.read(chars)) != -1){
// 将读取到的内容写入磁盘
bw.write(chars,0,length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (bw != null)
bw.close();
if (br != null)
br.close();
if (w != null)
w.close();
if (r != null)
r.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行上述代码,得到如下结果,到D盘查看,发现存在test3.txt 文件 ,点击查看,文件内容也都开碑过来了,说明代码无误。
未完待续......