✓ 附录:IO 流 - 第三小节

This commit is contained in:
Moilk
2019-11-19 15:38:12 +08:00
parent ca53609ef4
commit 29ee624202

View File

@@ -72,25 +72,47 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节:
## 添加属性和有用的接口
装饰器在[泛型](./20-Generics.md)这一章引入。Java I/O 类库需要多种不同功能的组合,这正是使用装饰器模式的原因所在[^1]。而之所以存在 **filter**(过滤器)类,是因为让抽象类 **filter** 作为所有装饰器类的基类。装饰器必须具有和它所装饰对象相同的接口,但它也可以扩展接口,不过这种情况只发生在个别 **filter** 类中。
但是装饰器模式也有一个缺点在编写程序的时候它给我们带来了相当多的灵活性因为我们可以很容易地对属性进行混搭但它同时也增加了代码的复杂性。Java I/O 类库操作不便的原因在于:我们必须创建许多类(“核心” I/O 类型加上所有的装饰器)才能得到我们所希望的单个 I/O 对象。
`FilterInputStream``FilterOutputStream` 是用来提供装饰器类接口以控制特定输入流 `InputStream` 和 输出流 `OutputStream` 的两个类,但它们的名字并不是很直观。`FilterInputStream``FilterOutputStream` 分别从 I/O 类库中的基类 `InputStream``OutputStream` 派生而来,这两个类是创建装饰器的必要条件(这样它们才能为所有被装饰的对象提供统一接口)。
### 通过 `FilterInputStream` 从 `InputStream` 读取
`FilterInputStream` 类能够完成两件截然不同的事情。其中,`DataInputStream` 允许我们读取不同的基本数据类型和 `String` 类型的对象(所有方法都以 “read” 开头,例如 `readByte()``readFloat()`等等)。搭配其对应的 `DataOutputStream`,我们就可以通过数据“流”将基本数据类型的数据从一个地方迁移到另一个地方。具体是那些“地方”是由[表 I/O-1](#table-io-1) 中的那些类决定的。
其它 `FilterInputStream` 类则在内部修改 `InputStream` 的行为方式:是否缓冲,是否保留它所读过的行(允许我们查询行数或设置行数),以及是否允许把单个字符推回输入流等等。最后两个类看起来就像是为了创建编译器提供的(它们被添加进来可能是为了对“用 Java 构建编译器”实现提供支持),因此我们在一般编程中不会用到它们。
在实际应用中,不管连接的是什么 I/O 设备,我们基本上都会对输入进行缓冲。所以当初 I/O 类库如果能默认都让输入进行缓冲,同时将无缓冲输入作为一种特殊情况(或者只是简单地提供一个方法调用),这样会更加合理,而不是像现在这样迫使我们基本上每次都得手动添加缓冲。
<!-- 译者注感觉第四版中文版536页把上面这一段的意思弄反了 -->
<span id="table-io-3">**表 I/O-3`FilterInputStream` 类型**</span>
| 类 | 功能 | 构造器参数 | 如何使用 |
| :--: | :-- | :-------- | :----- |
| `DataInputStream` | 与 `DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int``char``long` 等) | `InputStream` | 包含用于读取基本类型数据的全部接口 |
| `DataInputStream` | 与 `DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int``char``long` 等) | `InputStream` | 包含用于读取基本数据类型的全部接口 |
| `BufferedInputStream` | 使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲区” | `InputStream`,可以指定缓冲区大小(可选) | 本质上不提供接口,只是向进程添加缓冲功能。与接口对象搭配 |
| `LineNumberInputStream` | 跟踪输入流中的行号,可调用 `getLineNumber()``setLineNumber(int)` | `InputStream` | 仅增加了行号,因此可能要与接口对象搭配使用 |
| `PushbackInputStream` | 具有能弹出一个字节的缓冲区,因此可以将读到的最后一个字符回退 | `InputStream` | 通常作为编译器的扫描器,我们可能永远也不会用到 |
### 通过 `FilterOutputStream` 向 `OutputStream` 写入
`DataInputStream` 对应的是 `DataOutputStream`,它可以将各种基本数据类型和 `String` 类型的对象格式化输出到“流”中,。这样一来,任何机器上的任何 `DataInputStream` 都可以读出它们。所有方法都以 “write” 开头,例如 `writeByte()``writeFloat()` 等等。
`PrintStream` 最初的目的就是为了以可视化格式打印所有基本数据类型和 `String` 类型的对象。这和 `DataOutputStream` 不同,后者的目的是将数据元素置入“流”中,使 `DataInputStream` 能够可移植地重构它们。
`PrintStream` 内有两个重要方法:`print()``println()`。它们都被重载了,可以打印各种各种数据类型。`print()``println()` 之间的差异是,后者在操作完毕后会添加一个换行符。
`PrintStream` 可能会造成一些问题,因为它捕获了所有 `IOException`(因此,我们必须使用 `checkError()` 自行测试错误状态,如果出现错误它会返回 `true`)。另外,`PrintStream` 没有处理好国际化问题。这些问题都在 `PrintWriter` 中得到了解决,这在后面会讲到。
`BufferedOutputStream` 是一个修饰符,表明这个“流”使用了缓冲技术,因此每次向流写入的时候,不是每次都会执行物理写操作。我们在进行输出操作的时候可能会经常用到它。
<span id="table-io-4">**表 I/O-4`FilterOutputStream` 类型**</span>
| 类 | 功能 | 构造器参数 | 如何使用 |
| :--: | :-- | :-------- | :----- |
| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本类型数据(`int``char``long` 等) | `OutputStream` | 包含用于写入基本类型数据的全部接口 |
| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本数据类型`int``char``long` 等) | `OutputStream` | 包含用于写入基本数据类型的全部接口 |
| `PrintStream` | 用于产生格式化输出。其中 `DataOutputStream` 处理数据的存储,`PrintStream` 处理显示 | `OutputStream`,可以用 `boolean` 值指示是否每次换行时清空缓冲区(可选) | 应该是对 `OutputStream` 对象的 `final` 封装。可能会经常用到它 |
| `BufferedOutputStream` | 使用它以避免每次发送数据时都进行实际的写操作。代表“使用缓冲区”。可以调用 `flush()` 清空缓冲区 | `OutputStream`,可以指定缓冲区大小(可选) | 本质上并不提供接口,只是向进程添加缓冲功能。与接口对象搭配 |