15.4.2 输入 输出流体系

15.4.2 输入 输出流体系

Java的输入/输岀流体系提供了近40个类,这些类看上去杂乱而没有规律,但如果将其按功能进行分类,则不难发现其是非常规律的。表15.1显示了Java输入输出流体系中常用的流分类

展开/折叠

这里有一张图片

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流 Printstream PrintWriter
推回输入流 PushbackInputStream PushbackReader
特殊流 DataInputStream DataOutputStream

注:表15.1中的

  • 粗体字标出的类代表节点流,必须直接与指定的物理节点关联;
  • 斜体字标出的类代表抽象基类,无法直接创建实例。

从表15.1中可以看出,Java的输入/输出流体系之所以如此复杂,主要是因为Java为了实现更好的设计,它把IO流按功能分成了许多类,而每类中又分别提供了字节流和字符流(当然有些流无法提供字节流,有些流无法提供字符流),字节流和字符流里又分别提供了输入流和输岀流两大类,所以导致整个输入输出流体系格外复杂。

其他IO流

表15.1仅仅总结了输入/输出流体系中位于Java.io包下的流,还有一些诸如AudioInputStream,CipherInputStreamDeflaterInputStreamZipInputStream等具有访问音频文件加密解密压缩/解压等功能的字节流,它们具有特殊的功能,位于JDK的其他包下,本书不打算介绍这些特殊的IO流。

字节流功能比字符流强大 但复杂

通常来说,字节流的功能比字符流的功能强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件.但问题是,如果使用字节流来处理文本文件,则需要使用合适的方式把这些字节转换成字符,这就增加了编程的复杂度。

文本内容使用字符流

所以通常有一个规则:如果进行输入/输出的内容是文本内容,则应该考虑使用字符流;

二进制内容使用字节流

如果进行输入/输岀的内容是二进制内容,则应该考虑使用字节流

字符集

计算机的文件常被分为文本文件二进制文件两大类。
所有能用记事本打开并看到其中字符内容的文件称为文本文件,反之则称为二进制文件。
但实质是,计算机里的所有文件都是二进制文件,文本文件只是二进制文件的一种特例,当二进制文件里的内容恰好能被正常解析成字符时,则该二进制文件就变成了文本文件。更甚至于,即使是正常的文本文件,如果打开该文件时强制使用了“错误”的字符集,例如使用EditPlus打开刚刚生成的poem.txt文件时使用的字符集不对,则将看到打开的poem.txt文件内容变成了乱码。
因此,如果希望看到正常的文本文件内容,则必须在打开文件时与保存文件时使用相同的字符集(Windows简体中文默认使用GBK字符集,而Linux下简体中文默认使用UTF8字符集)。

读写数组的节点流

表15.1中还列出了一种以数组为物理节点的节点流,

  • 字节流字节数组为节点,
  • 字符流字符数组为节点;

这种以数组为物理节点的节点流除在创建节点流对象时需要传入一个字节数组或者字符数组之外,用法上与文件节点流完全相似。

读写字符串的节点流

与此类似的是,字符流还可以使用字符串作为物理节点,用于实现从字符串读取内容,或将内容写入字符串(用StringBuffer充当字符串)的功能。

程序 读写字符串的输入输出流

下面程序示范了使用字符串作为物理节点的字符输入/输出流的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.*;

public class StringNodeTest {
public static void main(String[] args) {
String src = "从明天起,做一个幸福的人\n" + "喂马,劈柴,周游世界\n" + "从明天起,关心粮食和蔬菜\n" + "我有一所房子,面朝大海,春暖花开\n" + "从明天起,和每一个亲人通信\n"
+ "告诉他们我的幸福\n";
char[] buffer = new char[32];
int hasRead = 0;
try (StringReader sr = new StringReader(src)) {
// 采用循环读取的访问读取字符串
while ((hasRead = sr.read(buffer)) > 0) {
System.out.print(new String(buffer, 0, hasRead));
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
try (
// 创建StringWriter时,实际上以一个StringBuffer作为输出节点
// 下面指定的20就是StringBuffer的初始长度
StringWriter sw = new StringWriter()) {
// 调用StringWriter的方法执行输出
sw.write("有一个美丽的新世界,\n");
sw.write("她在远方等我,\n");
sw.write("哪里有天真的孩子,\n");
sw.write("还有姑娘的酒窝\n");
System.out.println("----下面是sw的字符串节点里的内容----");
// 使用toString()方法返回StringWriter的字符串节点的内容
System.out.println(sw.toString());
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

上面程序与前面使用FileReaderFileWriter的程序基本相似,只是在创建StringReaderStringWriter对象时传入的是字符串节点,而不是文件节点。由于String是不可变的字符串对象,所以StringWriter使用StringBuffer作为输出节点。

管道流

表15.1中列出了4个访问管道的流:PipedInputStreamPipedOutputStreamPipedReaderPipedWriter,它们都是用于实现进程之间通信功能的,分别是字节输入流、字节输出流、字符输入流和字符输出流。
本书将在第16章介绍这4个流的用法。

缓冲流

表15.1中的4个缓冲流则增加了缓冲功能,增加缓冲功能可以提高输入、输岀的效率,增加缓冲功能后需要使用flush才可以将缓冲区的内容写入实际的物理节点表

对象流

15.1中的对象流主要用于实现对象的序列化,本章的15.8节将系统介绍对象序列化