Java I/O系统中的类非常复杂,而且因为使用了装饰器模式,很容易造成使用多种组合方式都可以达到相同的目的,故很容易产生疑惑。
Java中的I/O系统大部分都在java.io包中,并且可以分为两类:针对字节流的I/O;针对字符流的I/O。并且每种I/O又分为输入流和输出流。在处理Java中的I/O时主要关注数据格式要怎么处理和数据要以什么方式传输,这两个问题。
编程语言的I/O类库中常用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。“流”屏蔽了实际的I/O设备中处理数据的细节。
Java类库中的I/O类分成输入和输出两部分。任何继承自InputStream或Reader派生而来的类都含有read()的基本方法,用于读取单个字节或者字节数组。同样,任何继承自OutputStream或Writer的类都含有write()方法,用于写单个字节或者字节数组。但是我们通常不会使用这些方法,它们之所以存在是因为别的类可以使用它们,以提供更有用的接口,因此我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能。实际上,Java中“流”类库让人迷惑的主要原因就在于:创建单一的结果流,却需要创建多个对象。
所有字节流输入相关的类都继承自InputStream,而所有与输出相关的类都继承自OutputStream。所有字符流输入相关的类都继承自Reader,而输出字符流的类继承子Writer。当然这不是绝对的,例如RandomAccessFile就是一个单独的类,它提供了完整的文件处理功能。
InputStream类型
每一种数据源都有相应的InputStream子类,另外,FilterInputStream也属于一种InputStream,它为“装饰器”类提供了基类。“装饰器”类可以把属性或有用的接口与输入流连接在一起。
类 | 功能 | 构造参数/如何使用 |
---|---|---|
ByteArrayInputStream | 包含一个内部缓冲区,该缓冲区包含从流中读取的所有字节。 | 缓冲区(其实是字节数组),可以从中取字节。 与FilterInputStream相连时,提供数据源 |
StringBufferInputStream | 将String转换成InputStream | (过时)将要转换的字符串。底层使用了String来实现,但是它是将String的元素直接强制转化为byte的,故对于非ASCII可能会出现问题。 |
FileInputStream | 从文件中读取字节流。它主要用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。 | 表示文件名的字符串或者File对象。它内部使用FileDescriptor来记录对文件的引用次数。 |
PipedInputStream | 产生用于写入相关的PipedOutStream的数据(用于多线程环境下交换数据)。 | 关联的PipedOutputStream 可以在多线程下提供数据源 |
SequenceInputStream | 将两个或多个InputStream对象转换成单一InputStream | 两个InputStream或者它们组成的Enumeration对象。它内部是采用,若第一个流读取完毕,那就读取第二个的策略。 |
FilterInputStream | 抽象类,作为“装饰器”的接口 | |
ObjectInputStream | 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。 |
OutputStream类型
输出流确定了数据的去向:字节数组(byte数组,可以自己创建),文件或管道。FilterOutputStream为“装饰器”提供了基类,“装饰器”类把属性或者有用的接口与输出流连接了起来。
OutputStream是输出字节流的所有类的超类。输出流接收输出字节并将这些字节发送到某个接收器。OutputStream的继承结构和InputStream的继承结构是相似的,其功能也是相互对应的。
类 | 功能 | 构造器参数/如何使用 |
---|---|---|
ByteArrayOutputStream | 在内存中创建缓冲区(byte数组),并且会自动增长,可使用 toByteArray() 和 toString() 获取数据。 | 缓冲区的初始化尺寸(默认大小32)。 |
FileOutputStream | 将数据写入 File 或 FileDescriptor | 表示文件名的字符串,或者File对象 |
PipedOutputStream | 可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端(适用于多线程环境)。 | PipedInputStream,被指定的管道接收者,数据被写入后,接收者会自动获得数据(调用receive方法)。 |
FilterOutputStream | 抽象类,作为“装饰器”的接口,其中“装饰器”为其他OutputStream提供有用功能。 | |
ObjectOutputStream | 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。 | 它的具体操作是使用了一个内部类实现读写二进制数据BlockDataOutputStream实现的。 |
Filter类
Java I/O类库中需要多种不同功能的组合,而库中的filter(过滤器)类正是实现“装饰器”功能的基类。装饰器要求它和被装饰的对象有相同的接口,同时它也可以拓展接口,不过这种情况只发生在个别filter中。虽然装饰器为我们提供了很大的灵活性,但是它也增加了代码的复杂性。Java I/O类库操作不便的原因在于,我们必须创建许多类——“核心”I/O类型加上所有的“装饰器”,才能得到我们希望的单个I/O对象。
FilterInputStream和FilterOutputStream分别自I/O类库中的基类InputStream和OutputStream派生而来,它们为装饰器提供了基本的支持。DataInputStream(FilterInputStream的子类),允许我们读取基本数据类型以及String对象(readXXX()方法),与之对应的是FilterOutputStream的子类DataOutputStream,它可以将将数据写入指定的输出流。其他的FilterInputStream,类则在内部修改InputStream的行为方式:是否缓冲,是否保留它所读过的行,以及是否把单一字符推回输入流等。
类 | 功能 | 构造参数/如何使用 |
DataInputStream | 与DataOutputStream搭配使用,允许应用程序以可移植方式从输入流中读取基本 Java 数据类型(int,char,long等)。 | InputStream,包含用于读取基本数据类型的全部接口 |
BufferedInputStream | 为另一个输入流添加缓冲输入以及支持 mark 和 reset 方法的能力。 | InputStream,可以指定缓冲区大小。 |
DataOutputStream是FilterOutputStream的子类,它可以将各种基本数据类型,以及String对象格式化输出到“流”中,这样任何DataInputStream都能够读取它们(使用writerXXX()方法)。PrintStream是为了可视化格式打印所有的基本数据类型以及String对象。PrintStream未能完全国际化,不能以平台无关的方式处理换行动作(可以使用PrintWriter解决)。BufferedOutputStream是一个修改过的OutputStream,它对数据使用缓冲技术,因此每当向流写入数据时,不必进行实际物理写入,所有在进行输出时,我们可能更经常使用它。
类 | 功能 | 构造参数/如何使用 |
---|---|---|
DataOutputStream | 与DataInputStream搭配,以适当方式将基本 Java 数据类型写入输出流中。 | OutputStream,包含用于写入基本类型数据的全部接口 |
PrintStream | 用于产生格式化输出。 | 需要指定OutputStream |
BufferedOutputStream | 带缓冲的输出流,避免每次发送数据时都进行实际操作。 | OutputStream,可以指定缓冲区大小。 |
Reader和Writer类型
Reader和Writer不是为了替代InputStream和OutputStream的,它们主要是提供了兼容Unicode与面向字符的I/O功能。其中一个比较重要的点就是,InputStreamReader可以把InputStream转换为Reader,而OutputStreamReader可以把OutputStream转化为Writer。
设计Reader和Writer继承层次结构主要是为了国际化,老的I/O流继承层次结构仅支持8位字节流,不能很好地处理16位的Unicode字符,但是新的继承层次结构能够在所有的I/O操作中都支持Unicode,而且使得它的操作比旧类库快。由于Reader和Writer主要面对Unicode字符操作,故面向字节流时,还是需要使用InputStream和OutputStream来解决问题的。
我们可以看出,InputStream/OutputStream的继承层次和Reader/Writer的继承层次是非常相似的,几乎每个Stream类都有自己相对应的Reader或Writer。不过对于Filter类的层次来说这种对应关系就变得不是那么明显了。而且,BufferedWriter不是FilterWriter的子类,它在继承结构中只是暂时作为占位符。
需要注意一点,在BufferedReader和DataInputStream中都包含readLine()方法,但是我们需要读取数据时最好只选用BufferedReader。
RandomAccessFile类
RandomAccessFile适用于大小已知的记录组成的文件,我们可以使用seek()将记录从一处转移到另一处,然后读取或者修改记录。它不是InputStream和OutputStream继承层次结构的一部分,不过它也实现了DataInput和DataOutput接口。它是一个完全独立的类,大部分方法都是本地方法。
总结
总的来说,使用Java中的I/O时,能弄清楚数据的去向和来源,然后将这些功能恰当的组合起来就可以了。如果需要缓冲功能就使用Buffered类型的类装饰,如果需要字节类型处理过程转字符类型处理过程就使用StreamReader/StreamWriter转为Reader/Writer,如果需要处理文件就使用File类型的Stream或者Writer等等。