1.添加属性和有用的接口
FilterInputStream和FilterOutputStream是用来提供装饰器类接口的以控制特定输入流和输出流的两个类。FilterInputStream和FilterOutputStream分别自I/O类库中的基类InputStream和OutputStream派生而来,这两个类是装饰器的必要条件。
1.1 通过FilterInputStream从InputStream读取数据
FilterInputStream类能够完成两件不同的事情。其中,DataInputStream允许我们读取不同的基本类型数据以及String对象(所有方法都以“read”开头,例如readByte()、readFloat()等等)。搭配相应的DataInputStream,我们就可以通过数据“流”将基本类型的数据从一个地方迁移到另外一个地方。
FilterInputStream的类型及功能如下表所示:
1.2 通过FilterOutputStream向OutputStream写入
与DataInputStream对应的是DataOutputSream,它可以将各种基本数据类型以及String对象格式化输出到“流”中;这样以来,任何机器上任何DataInputStream都能够读取它们。所有方法都以“write”开头,例如writeByte()、writeFloat()等等。
PrintStream最初的目的是为了以可视化格式打印所有的基本数据类型以及String对象。这和DataOutputStream不同,后者的目的是将数据元素置入“流”中,使DataInputStream能够可移植地重构它们。
PrintStream内有两个重要的方法:print()和println()。对它们进行了重载,以便可以打印各种数据类型。print()和println()之间的差异是,后者在操作完毕之后会添加一个换行符。
BufferedOutputStream是一个修改过的OutputStream,它对数据流使用缓冲技术,因此当每次向流写入时,不必每次都进行实际的物理写动作。所以在进行输出时,可能会经常使用它。FilterOutputStream的类型及功能如下图所示:
2.Reader和Writer
几乎所有的原始java I/O流类都有相应的Reader和Writer类来提供天然的Unicode操作。然而在某些场合,面向字符的InputStream和OutputStream才是正确的解决方案,特别是,java.util.zip类库就是面向字节的而不是面向字符的。因此,最明智的做法是尽量尝试使用Reader和Writer,一旦程序无法成功编译,我们就会发现自己不得不使用面向字节的类库。下图展示了两个继承层次结构中,信息的来源和去处:
2.2 更改流的行为
对于InputStream和OutputStream来说,我们会使用FilterInputStream和FilterOutputStream的装饰器子类来修改“流”以满足特殊需要。Reader和Writer的继承层次结构继续沿用相同的思想–但是并不完全相同。
在下图中,相对于上面的那个图来说,左右之间的对应关系的近似程度更加粗略写。造成这种差别的原因是因为类的组织形式不同;尽管BufferedOutputStream和FilterOutputStream的子类,但是BufferedWriter并不是FilterWriter的子类(尽管FilterWriter是抽象类,没有任何子类,把它放在那里也只是把它作为一个占位符),然而,这些类的接口都十分相似:
有一点很清楚:无论我们何时使用readLine(),都不应该使用DataInputStream,而应该使用BufferedReader,除了这一点,DataInputStream仍是I/O类库的首选成员。
为了更容易过渡到使用PrintWriter,它提供了一个既能接受Writer对象又能接受任何OutputStream对象的构造器。PrintWriter格式化接口实际上与PrintStream相同。
2.3 未发生变化的类
有一些类在java1.0和java1.1之间未做改变。如下图所示:
强调内容
特别是DataInputStream,在使用时没有任何变化;因此如果想以“可传输的”格式存储和检索数据,可以使用InputStream和OutputStream。
3. 自我独立的类:RandomAccessFile
RandomAccessFile适用于由大小已知的记录组成的文件,所以我们使用seek()将记录从一处转移到另一处,然后读取或者修改记录。文件中的记录的大小不一定相同,只要我们能够确定那些记录多大以及它们在文件中的位置即可。
从本质上来说,RandomAccessFile的工作方式类似于把DataInputStream和DataOutputStream组合起来使用,还添加了一些方法。其中方法getFilePointer()用于查找当前所处的文件位置,seek()用于在文件内移至新的文职,length()用于判断文件的最大尺寸。令哇,其构造器还需要第二个参数用来指示我们只是“随机读”(r)还是“既读又写”(rw)。它并不支持只写文件,这表明RandomAccessFile若是从DataOutputStream继承而来也可能运行得很好。只有RandomAccessFile支持搜寻方法,并且只适用于文件。
4. I/O流的典型使用方式
4.1缓冲输入文件
如果想要打开一个文件用于字符输入,可以使用以String或File对象作为文件名的FileInputReader。下面的例子使用BufferedReader的构造函数对文件进行缓冲。由于BufferedReader也提供readLine()方法,所以这是最终对象和进行读取的接口。当readLine()将返回null时,就达到了文件的末尾。例子如下:
public class BufferedInputFile {
public static String read(String fileName) throws IOException{
BufferedReader in = new BufferedReader(new FileReader(fileName));
String s ;
StringBuilder sb = new StringBuilder();
while ((s = in.readLine()) != null) {
sb.append(s+"\n");
}
in.close();
return sb.toString();
}
public static void main(String[] args) throws IOException{
System.out.println(read("C:/Users/Administrator/Workspaces/MyEclipse 10/eighteen/src/com/one/BufferedInputFile.java"));
}
}
字符串sb用来累积文件的全部内容(包括必须添加的换行符,因为readLine()已将它们删除)。最后,调用close()关闭文件。
4.2 从内存输入
下面的示例中,从BufferedInputFile.read()读入的String结果用来创建一个StringReader。然后调用read()每次读取一个字符,并把它送到控制台。
public class MemoryInput {
public static void main(String[] args) throws IOException{
StringReader in = new StringReader("C:/Users/Administrator/Workspaces/MyEclipse 10/eighteen/src/com/one/MemoryInput.java");
int c;
while((c = in.read()) != -1){
System.out.println((char)c);
}
}
}
注意read()是以int形式返回下一字节,因此必须类型转换为char才能正确打印。
4.3 格式化的内存输入
要读取格式化数据,可以使用DataInputStream,它是一个面向字节的I/O类(不是面向字符的)。因此我们必须使用InputStream类而不是Reader类。当然,我们可以用InputStream以字节形式读取任何数据(例如一个文件),不过,这里使用的是字符串。例子如下:
public class FormattedMemoryInput {
public static void main(String[] args) throws IOException {
try {
DataInputStream in = new DataInputStream(
new ByteArrayInputStream(
BufferedInputFile
.read("C:/Users/Administrator/Workspaces/MyEclipse 10/eighteen/src/com/one/FormattedMemoryInput.java")
.getBytes()));
while(true){
System.out.println((char)in.readByte());
}
} catch (EOFException e) {
System.out.println("End of stream");
}
}
}
必须为ByteArrayInputStream提供字节数组,为了产生该数组String包含了一个可以实现此项工作的getBytes()方法。这方法产生ByteArrayInputStream是一个适合传递给DataInputStream的InputStream。
4.4 基本的文件输出
FileWriter对象可以向文件写入数据。首先,创建一个与指定文件连接的FileWriter。实际上,通常会用BufferedWriter将其包装起来用以缓冲输出。使用PrintWriter可以使得你不必要每次希望创建文本文件并向其中写入时,都去执行所有的装饰工作。
4.5 存储和恢复数据
PrintWriter可以对数据进行格式化,以便人们的阅读。但是为了输出可供另一个“流”恢复的数据,我们需要用DataOutputStream写入数据,并用DataOutputStream恢复数据。注意DataOutputStream和DataInputStream是面向字节的,因此要使用InputStream和OutputStream。
4.6 读写随机访问文件
使用RandomAccessFile时,你必须知道文件排版,这样才能正确地操作它。RandomAccessFile拥有读取基本类型和UTF-8字符串的各种具体方法。RandomAccessFile除了实现DataInput和DataOutput接口之外,有效地狱I/O继承层次结构的其他部分实现了分离。因为它不支持装饰,所以不能将其与InputStream及OutputStream子类的任何部分组合起来。