整体结构
Java IO库令人迷惑的地方在于使用了装饰器的结构,就是一个类需要使用另外一个类作为构造器参数,而装饰类和底层类都继承自相同的类,导致创建一个IO对象可能需要创建两到三个嵌套的类,这和很多语言里直接通过函数打开一个文件对象很不同。这里笔者先不区分字节流和字符流以及对应的类,而是从装饰类和被装饰类来区分。 在具体获得一个IO流对象的时候,写法是这样的:IO流对象 = new 装饰类(new 底层类)可见问题的关键在于搞清楚哪些是装饰类,哪些是底层类。下边就以输入为例来看一下装饰类和底层类。
底层输入类
常用的底层类根据来源有如下:- ByteArrayInputStream - 输入字节数组
- StringBufferInputStream - 输入字符串
- FileInputStream - 从文件输入,即打开一个文件
- PipedInputStream - 管道输入流,构造器接受一个管道输出流
装饰输入类
我把这一类东西称作外层类,就是使用一个内层类对象作为构造器参数,由于内层类对象本身已经是一个流了,外层类相当于你要选择以何种方式去使用流,不同的外层类提供了不同的使用方式(也就是方法)。来看看常用的外层类:- DataInputStream - 从流中读取基本类型数据
- BufferedInputStream - 采用缓冲区方式读写
- LineNumberInputStream - 有一些方法来跟踪输入流中的行号
如何使用
知道了装饰类和底层类,就可以根据需要来选择使用了。分为三步:- 1 选择来源对应的底层类
- 2 选择装饰类
- 3 套壳得到IO对象
- 1 选择来源,是一个文件,选用FileInputStream类
- 2 既然是ASCII编码,每个字节就是一个字符,因此需要读取基础数据,则选用DataInputStream
- 3 套壳,外层是DataInputStream, 内层是FileInputStream,写成代码如下:
DataInputStream in = new DataInputStream(new FileInputStream("test.txt"));
- 1 选择来源,这里依然是FileInputStream类
- 2 这里需要分析一下,要采取缓冲的方式读取基本类型数据,则我们最后要操作的是读取基本类型数据的API,但还要带有缓冲,因此先将带缓冲的装饰类BufferedInputStream套在FileInputStream外边,然后在最外层套上符合我们需求的DataInputStream类。
- 3 进行套壳,按照上一步的分析,代码如下:
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.out")));
之后就可以调用in.readInt()之类方法来获取其中的基本类型数据。
输出的装饰类和底层类
有了上边输入流的分析,对于输出流也是一样的,只要区分出底层类和装饰类即可,这里简单罗列一下: 输出流的底层类:- ByteArrayOutputStream - 字节数组输出,实际上是在内存中创建一个缓冲区输出
- FileOutputStream - 输出到文件
- PipedOutputStream - 管道输出流,构造器接受一个管道输入流
- DataOutputStream - 向流中写入基本类型数据
- PrintStream - 产生格式化输出,有print()和println()两个方法
- BufferedOutputStream - 带缓冲区的写操作,带有flush()方法用于清空缓冲区并实际执行写操作
Reader和Writer
Reader和Writer提供了针对Unicode字符的支持,是面向字符的类。我一直强调不要一开始就用字符流和字节流的方式来学习IO库,而是使用内外层类的方式。到了Reader和Writer就可以发现,与其区分字节流与字符流来割裂的看待InputStream+OutputStream和Reader+Writer,更好的方式是探究其在背后使用装饰类和底层类设计思想的一致性。 对于上边的所有输入和输出流的底层类,Java 提供了对应的Reader和Writer类;对于装饰类,同样也提供了对应的Reader和Writer类。这意味着要读取和写入字符的时候,其实就是重新挑选对应的Reader和Writer类即可,外层套内层的装饰器设计模式理念是不变的。Reader
常见的Reader底层类有:- FileReader - 读取文件
- StringReader - 读取字符串
- CharArrayReader - 读取字符数组
- PipedReader - 读取管道数据
- BufferedReader - 通用的带缓冲读取字符
- LineNumberReader - 带有行号API的读取
- 1 选择底层类,很显然是FileReader
- 2 BufferedReader就带有readLine功能,也没有其他的读取类可以选,带有行号的API的类其实不如自己操作行号方便。
- 3 进行套壳,代码如下:
BufferedReader in = new BufferedReader(new FileReader("story.txt"));
之后可以使用in.readLine()来读取行,读取的结果不会包括换行符。如果读取到末尾,会返回null,因此使用一个循环就可以不断读取:while ((s = in.readLine()) != null) { doSomething(s) }
Writer
按照同样的理念来分析,Writer的底层类有:- FileWriter - 写文件
- StringWriter - 写字符串
- CharArrayWriter - 写字符数组
- PipedWriter - 向管道中写入字符
- BufferedWriter - 通用的带缓冲写字符
- PrintWriter - 格式化输出,带有print()和println()方法
- 1 需要写文件,底层类选用FileWriter
- 2 写的话一般都需要缓冲,否则性能太差,可见需要先套一个BufferedWriter,这里可以每次加上一个换行符写入。但为了省事,我们可以再套一层PrintWriter
- 3 代码如下:
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("test.data")));