一、介绍
我自己对BIO的印象一直比较模糊,用到的时候经常百度一下就直接使用。直观的感觉就是一堆类以及使用中的各种嵌套,让人觉得摸不着头脑。本文将从设计模式的角度来介绍BIO的设计及使用,希望各位看官能静下心来,一起思考。
二、设计模式
说到IO,大家都会想到IO流,其实这也是IO的本质。在BIO中,不论是字节流还是字符流,最终都是字节流。理论上,jdk只需要提供了字节流的输入输出功能,我们便可以做所有的事情。但是为了使用方便,jdk又在字节流的输入输出功能上,又做了各种各样的封装来提供一些增强功能以满足常用的开发需求,比如增加缓冲区、字符流等。BIO的封装思路主要是采用装饰者模式和适配器模式。
装饰者模式,通俗点说就是在基础类的外层又包了一层包装类,基础类作为包装类的成员变量存在。包装类借助于基础类可以提供增强型的功能。比如BufferedInputStream可以将FileInputStream包装起来,就实现了带有缓冲区的文件输入输出功能,并且会额外提供一些缓冲区的操作方法。如果jdk不提供BufferedInputStream,我们也可以自己写一个类似的包装类。很多的博客在介绍到装饰者模式时都会提一下为什么不用继承。这里我还是跟大家一样的说法,如果全部采用继承方式的话,会造成类爆炸。而装饰者模式可以使BufferedInputStream在成员变量中维护一个接口InputStream,便可以包装所有的子类。
适配器模式,一个很形象的例子就是充电插口,比如插脚是扁的,插孔是圆的,想要将插脚插到插孔里,就需要一个适配器来连接两者。在BIO里,需要适配器的地方是字节流与字符流的转化。我们都知道BIO提供了Reader和Writer来操作字符流,他们的底层其实还是字节流,只不过是通过适配的方式,将字节流转成了字符流。
三、装饰者模式举例
1、FileInputStream
File file = new File("D:\test.txt");
InputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[1024];
//字节数组的可用长度
int aval = 0;
while((aval = fileInputStream.read(bytes))>0){
System.out.println(new String(bytes,0,aval));
}
FileInputStream作为基础类,只有基础的字节流输入输出功能,每次都从文件中读取。
2、BufferedInputStream(缓冲区增强)
File file = new File("D:\test.txt");
InputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
byte[] bytes = new byte[1024];
//字节数组的可用长度
int aval = 0;
while((aval = bufferedInputStream.read(bytes))>0){
System.out.println(new String(bytes,0,aval));
}
BufferedInputStream内部有一个字节数组,默认大小是8192字节,当首次读取数据时会通过FileInputStream一次性读入8192字节的数据,之后的读取都会首先从缓冲区中读取。
四、适配器模式
1、InputStreamReader(同OutputStreamReader)
File file = new File("D:\test.txt");
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
System.out.println(bufferedReader.readLine());
以上代码并没有体现适配器模式,只是字符流的一个简单使用。适配器模式需要在源码里体现。FileReader继承自InputStreamReader,InputStreamReader成员变量中有StreamDecoder流解码器并且构造函数中需要InputStream,StreamDecoder的成员变量中有InputStream。当Reader调用read方法时,底层上还是用InputStream读字节流,然后经过StreamDecoder解码形成字符流。