流的基本概念
将流理解为连接到数据源或数据目标的管道,可以通过连接到源的流从源当中读取数据,或通过连接到目标的流向目标中写入数据。从上述概念中我们能得出从源中读取数据或向目标写数据都是通过流来操作的。下图比较完整的表达这个概念。源和目标有哪些呢?如文件,键盘(输入设备),显示器(输出设备,可以是命令行窗口),网络。
字节流与字符流
根据流处理的数据类型不同分为字节流与字符流,处理单位分别为字节与字符。用4个抽象基类来表示,如下表格
字节流 | 字符流 | |
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
节点流与处理流
节点流一般用于直接从指定的位置进行读写操作。这些操作底层,原始,功能单一。
而处理流对其他输入/输出流(如:节点流或处理流)进行封装,提供缓存,过滤,编码转换,按行读写等增强功能。所以处理流更适合面向上层用户,提供高效,方便等读写操作。
如下示意图,处理流对节点流再次封装,当然也可以处理流对处理流再次封装。具体如何实现封装下面再介绍。
我们先看看节点流
下面是输入字节流InputStream继承关系
InputStream详细类图
InputStream是所有输入字节流的抽象基类;
关键抽象方法public abstract int read() throws IOException;由子类实现。比如读取文件,内存中字节时,子类FileInputStream,ByteArrayInputStream必须实现。其他read签名方法依赖此方法实现。read()方法返回的是一个字节值,但是int型且是0~255之间。这个字节值已经在byte与int之间做了转化,因为byte值范围-127~126。
skip方法指定输入流中跳过的字节数,返回实际跳过的字节数。
available():返回此输入流下次调用方法可以不受阻塞地读取或跳过的字节数。api中给出这样说明:试图使用此方法的返回值分配缓冲区,以保存此流所有数据的做法是不正确的。
markSupported():是否支持标记,默认为false。若为true,则可调用mark和reset方法。
mark(int readlimit):在输入流中标记当前位置,以便下次再回到此处读取。readlimit表示从标记位置开始,此流可以记忆的最大字节数。这里是空实现,子类实现。对于在readlimit个以后读取的字节数,有可能记忆也有可能不记忆,要看具体实现了。
reset():重新定位到最后一次标记的位置。这里是直接抛出异常,子类实现。
OutputStream,Reader,Writer不在讲述。关注下flush(),ready()方法。
接下来看看处理流
处理流不是直接面向源或目标使用,是对节点流类装饰后才能使用,这里使用了装饰模式。上述表格列出了增强功能了的处理流,方便用户操作。简述装饰模式,类图如下:
说明:
抽象构件Component(对应抽象类InputStream),具体构件ConcreteComponent(对应FileInputStream),
装饰角色Decorator(对应FilterInputStream),具体装饰角色ConcreteDecorator(对应BufferedInputStream,LineNumberInputStream)。
Decorator中被装饰对象component是protected,以便具体类也可以引用。
ConcreteDecorator中doSomething()通过调用private方法addedBehavior()增加component. doSomething()功能。在BufferedInputStream中对应fill(),read1(...)方法。
处理流的构造函数与节点流类的构造函数是不一样的,因为处理流装饰了节点流,节点流是面向流的源或目标的,如下:
public BufferedInputStream(InputStream in);
public FileInputStream(File file) throws FileNotFoundException;
所以如下方式使用
FileInputStream fis = new FileInputStream("F:/test.txt");
BufferedInputStream bis = new BufferedInputStream(fis);