Skip to content

Commit 6484622

Browse files
authored
Merge pull request lingcoder#325 from Moilk/master
附录:流式IO 翻译更新
2 parents ef440c8 + 6240e6a commit 6484622

File tree

1 file changed

+131
-3
lines changed

1 file changed

+131
-3
lines changed

docs/book/Appendix-IO-Streams.md

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,48 +72,176 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节:
7272

7373
## 添加属性和有用的接口
7474

75+
装饰器在[泛型](./20-Generics.md)这一章引入。Java I/O 类库需要多种不同功能的组合,这正是使用装饰器模式的原因所在[^1]。而之所以存在 **filter**(过滤器)类,是因为让抽象类 **filter** 作为所有装饰器类的基类。装饰器必须具有和它所装饰对象相同的接口,但它也可以扩展接口,不过这种情况只发生在个别 **filter** 类中。
76+
77+
但是,装饰器模式也有一个缺点:在编写程序的时候,它给我们带来了相当多的灵活性(因为我们可以很容易地对属性进行混搭),但它同时也增加了代码的复杂性。Java I/O 类库操作不便的原因在于:我们必须创建许多类(“核心” I/O 类型加上所有的装饰器)才能得到我们所希望的单个 I/O 对象。
78+
79+
`FilterInputStream``FilterOutputStream` 是用来提供装饰器类接口以控制特定输入流 `InputStream` 和 输出流 `OutputStream` 的两个类,但它们的名字并不是很直观。`FilterInputStream``FilterOutputStream` 分别从 I/O 类库中的基类 `InputStream``OutputStream` 派生而来,这两个类是创建装饰器的必要条件(这样它们才能为所有被装饰的对象提供统一接口)。
7580

7681
### 通过 `FilterInputStream``InputStream` 读取
7782

83+
`FilterInputStream` 类能够完成两件截然不同的事情。其中,`DataInputStream` 允许我们读取不同的基本数据类型和 `String` 类型的对象(所有方法都以 “read” 开头,例如 `readByte()``readFloat()`等等)。搭配其对应的 `DataOutputStream`,我们就可以通过数据“流”将基本数据类型的数据从一个地方迁移到另一个地方。具体是那些“地方”是由[表 I/O-1](#table-io-1) 中的那些类决定的。
84+
85+
其它 `FilterInputStream` 类则在内部修改 `InputStream` 的行为方式:是否缓冲,是否保留它所读过的行(允许我们查询行数或设置行数),以及是否允许把单个字符推回输入流等等。最后两个类看起来就像是为了创建编译器提供的(它们被添加进来可能是为了对“用 Java 构建编译器”实现提供支持),因此我们在一般编程中不会用到它们。
86+
87+
在实际应用中,不管连接的是什么 I/O 设备,我们基本上都会对输入进行缓冲。所以当初 I/O 类库如果能默认都让输入进行缓冲,同时将无缓冲输入作为一种特殊情况(或者只是简单地提供一个方法调用),这样会更加合理,而不是像现在这样迫使我们基本上每次都得手动添加缓冲。
88+
<!-- 译者注:感觉第四版中文版(536页)把上面这一段的意思弄反了 -->
89+
7890
<span id="table-io-3">**表 I/O-3:`FilterInputStream` 类型**</span>
7991

8092
|| 功能 | 构造器参数 | 如何使用 |
8193
| :--: | :-- | :-------- | :----- |
82-
| `DataInputStream` |`DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int``char``long` 等) | `InputStream` | 包含用于读取基本类型数据的全部接口 |
94+
| `DataInputStream` |`DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int``char``long` 等) | `InputStream` | 包含用于读取基本数据类型的全部接口 |
8395
| `BufferedInputStream` | 使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲区” | `InputStream`,可以指定缓冲区大小(可选) | 本质上不提供接口,只是向进程添加缓冲功能。与接口对象搭配 |
8496
| `LineNumberInputStream` | 跟踪输入流中的行号,可调用 `getLineNumber()``setLineNumber(int)` | `InputStream` | 仅增加了行号,因此可能要与接口对象搭配使用 |
8597
| `PushbackInputStream` | 具有能弹出一个字节的缓冲区,因此可以将读到的最后一个字符回退 | `InputStream` | 通常作为编译器的扫描器,我们可能永远也不会用到 |
8698

8799
### 通过 `FilterOutputStream``OutputStream` 写入
88100

101+
`DataInputStream` 对应的是 `DataOutputStream`,它可以将各种基本数据类型和 `String` 类型的对象格式化输出到“流”中,。这样一来,任何机器上的任何 `DataInputStream` 都可以读出它们。所有方法都以 “write” 开头,例如 `writeByte()``writeFloat()` 等等。
102+
103+
`PrintStream` 最初的目的就是为了以可视化格式打印所有基本数据类型和 `String` 类型的对象。这和 `DataOutputStream` 不同,后者的目的是将数据元素置入“流”中,使 `DataInputStream` 能够可移植地重构它们。
104+
105+
`PrintStream` 内有两个重要方法:`print()``println()`。它们都被重载了,可以打印各种各种数据类型。`print()``println()` 之间的差异是,后者在操作完毕后会添加一个换行符。
106+
107+
`PrintStream` 可能会造成一些问题,因为它捕获了所有 `IOException`(因此,我们必须使用 `checkError()` 自行测试错误状态,如果出现错误它会返回 `true`)。另外,`PrintStream` 没有处理好国际化问题。这些问题都在 `PrintWriter` 中得到了解决,这在后面会讲到。
108+
109+
`BufferedOutputStream` 是一个修饰符,表明这个“流”使用了缓冲技术,因此每次向流写入的时候,不是每次都会执行物理写操作。我们在进行输出操作的时候可能会经常用到它。
110+
89111
<span id="table-io-4">**表 I/O-4:`FilterOutputStream` 类型**</span>
90112

91113
|| 功能 | 构造器参数 | 如何使用 |
92114
| :--: | :-- | :-------- | :----- |
93-
| `DataOutputStream` |`DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本类型数据`int``char``long` 等) | `OutputStream` | 包含用于写入基本类型数据的全部接口 |
115+
| `DataOutputStream` |`DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本数据类型`int``char``long` 等) | `OutputStream` | 包含用于写入基本数据类型的全部接口 |
94116
| `PrintStream` | 用于产生格式化输出。其中 `DataOutputStream` 处理数据的存储,`PrintStream` 处理显示 | `OutputStream`,可以用 `boolean` 值指示是否每次换行时清空缓冲区(可选) | 应该是对 `OutputStream` 对象的 `final` 封装。可能会经常用到它 |
95117
| `BufferedOutputStream` | 使用它以避免每次发送数据时都进行实际的写操作。代表“使用缓冲区”。可以调用 `flush()` 清空缓冲区 | `OutputStream`,可以指定缓冲区大小(可选) | 本质上并不提供接口,只是向进程添加缓冲功能。与接口对象搭配 |
96118

97119
<!-- Readers & Writers -->
98120

99121
## Reader和Writer
100122

123+
Java 1.1 对基本的 I/O 流类库做了重大的修改。你初次遇到 `Reader``Writer` 时,可能会以为这两个类是用来替代 `InputStream``OutputStream` 的,但实际上并不是这样。尽管一些原始的“流”类库已经过时了(如果使用它们,编译器会发出警告),但是 `InputStream``OutputStream` 在面向字节 I/O 这方面仍然发挥着极其重要的作用,而 `Reader``Writer` 则提供兼容 Unicode 和面向字符 I/O 的功能。另外:
124+
125+
1. Java 1.1 往 `InputStream``OutputStream` 的继承体系中又添加了一些新类,所以这两个类显然是不会被取代的;
126+
127+
2. 有时我们必须把来自“字节”层级结构中的类和来自“字符”层次结构中的类结合起来使用。为了达到这个目的,需要用到“适配器(adapter)类”:`InputStreamReader` 可以把 `InputStream` 转换为 `Reader`,而 `OutputStreamWriter` 可以把 `OutputStream` 转换为 `Writer`
128+
129+
设计 `Reader``Writer` 继承体系主要是为了国际化。老的 I/O 流继承体系仅支持 8 比特的字节流,并且不能很好地处理 16 比特的 Unicode 字符。由于 Unicode 用于字符国际化(Java 本身的 `char` 也是 16 比特的 Unicode),所以添加 `Reader``Writer` 继承体系就是为了让所有的 I/O 操作都支持 Unicode。另外,新类库的设计使得它的操作比旧类库要快。
130+
131+
### 数据的来源和去处
132+
133+
几乎所有原始的 Java I/O 流类都有相应的 `Reader``Writer` 类来提供原生的 Unicode 操作。但是在某些场合,面向字节的 `InputStream``OutputStream` 才是正确的解决方案。特别是 `java.util.zip` 类库就是面向字节而不是面向字符的。因此,最明智的做法是尽量**尝试**使用 `Reader``Writer`,一旦代码没法成功编译,你就会发现此时应该使用面向字节的类库了。
134+
135+
下表展示了在两个继承体系中,信息的来源和去处(即数据物理上来自哪里又去向哪里)之间的对应关系:
136+
137+
| 来源与去处:Java 1.0 类 | 相应的 Java 1.1 类 |
138+
| :-------------------: | :--------------: |
139+
| `InputStream` | `Reader` <br/> 适配器:`InputStreamReader` |
140+
| `OutputStream` | `Writer` <br/> 适配器:`OutputStreamWriter` |
141+
| `FileInputStream` | `FileReader` |
142+
| `FileOutputStream` | `FileWriter` |
143+
| `StringBufferInputStream`(已弃用) | `StringReader` |
144+
| (无相应的类) | `StringWriter` |
145+
| `ByteArrayInputStream` | `CharArrayReader` |
146+
| `ByteArrayOutputStream` | `CharArrayWriter` |
147+
| `PipedInputStream` | `PipedReader` |
148+
| `PipedOutputStream` | `PipedWriter` |
149+
150+
总的来说,这两个不同的继承体系中的接口即便不能说完全相同,但也是非常相似的。
151+
152+
### 更改流的行为
153+
154+
对于 `InputStream``OutputStream` 来说,我们会使用 `FilterInputStream``FilterOutputStream` 的装饰器子类来修改“流”以满足特殊需要。`Reader``Writer` 的类继承体系沿用了相同的思想——但是并不完全相同。
155+
156+
在下表中,左右之间对应关系的近似程度现比上一个表格更加粗略一些。造成这种差别的原因是类的组织形式不同,`BufferedOutputStream``FilterOutputStream` 的子类,但 `BufferedWriter` 却不是 `FilterWriter` 的子类(尽管 `FilterWriter` 是抽象类,但却没有任何子类,把它放在表格里只是占个位置,不然你可能奇怪 `FilterWriter` 上哪去了)。然而,这些类的接口却又十分相似。
157+
158+
| 过滤器:Java 1.0 类 | 相应 Java 1.1 类 |
159+
| :--------------- | :-------------- |
160+
| `FilterInputStream` | `FilterReader` |
161+
| `FilterOutputStream` | `FilterWriter` (抽象类,没有子类) |
162+
| `BufferedInputStream` | `BufferedReader`(也有 `readLine()`) |
163+
| `BufferedOutputStream` | `BufferedWriter` |
164+
| `DataInputStream` | 使用 `DataInputStream`( 如果必须用到 `readLine()`,那你就得使用 `BufferedReader`。否则,一般情况下就用 `DataInputStream` |
165+
| `PrintStream` | `PrintWriter` |
166+
| `LineNumberInputStream` | `LineNumberReader` |
167+
| `StreamTokenizer` | `StreamTokenizer`(使用具有 `Reader` 参数的构造器) |
168+
| `PushbackInputStream` | `PushbackReader` |
169+
170+
有一条限制需要明确:一旦要使用 `readLine()`,我们就不应该用 `DataInputStream`(否则,编译时会得到使用了过时方法的警告),而应该使用 `BufferedReader`。除了这种情况之外的情形中,`DataInputStream` 仍是 I/O 类库的首选成员。
171+
172+
为了使用时更容易过渡到 `PrintWriter`,它提供了一个既能接受 `Writer` 对象又能接受任何 `OutputStream` 对象的构造器。`PrintWriter` 的格式化接口实际上与 `PrintStream` 相同。
173+
174+
Java 5 添加了几种 `PrintWriter` 构造器,以便在将输出写入时简化文件的创建过程,你马上就会见到它们。
175+
176+
其中一种 `PrintWriter` 构造器还有一个执行**自动 flush**[^2] 的选项。如果构造器设置了该选项,就会在每个 `println()` 调用之后,自动执行 flush。
177+
178+
### 未发生改变的类
179+
180+
有一些类在 Java 1.0 和 Java 1.1 之间未做改变。
181+
182+
| 以下这些 Java 1.0 类在 Java 1.1 中没有相应类 |
183+
| --- |
184+
| `DataOutputStream` |
185+
| `File` |
186+
| `RandomAccessFile` |
187+
| `SequenceInputStream` |
101188

189+
特别是 `DataOutputStream`,在使用时没有任何变化;因此如果想以可传输的格式存储和检索数据,请用 `InputStream``OutputStream` 继承体系。
102190

103191
<!-- Off By Itself: RandomAccessFile -->
104192
## RandomAccessFile类
105193

194+
`RandomAccessFile` 适用于由大小已知的记录组成的文件,所以我们可以使用 `seek()` 将文件指针从一条记录移动到另一条记录,然后对记录进行读取和修改。文件中记录的大小不一定都相同,只要我们能确定那些记录有多大以及它们在文件中的位置即可。
195+
196+
最初,我们可能难以相信 `RandomAccessFile` 不是 `InputStream` 或者 `OutputStream` 继承体系中的一部分。除了实现了 `DataInput``DataOutput` 接口(`DataInputStream``DataOutputStream` 也实现了这两个接口)之外,它和这两个继承体系没有任何关系。它甚至都不使用 `InputStream``OutputStream` 类中已有的任何功能。它是一个完全独立的类,其所有的方法(大多数都是 `native` 方法)都是从头开始编写的。这么做是因为 `RandomAccessFile` 拥有和别的 I/O 类型本质上不同的行为,因为我们可以在一个文件内向前和向后移动。在任何情况下,它都是自我独立的,直接继承自 `Object`
197+
198+
从本质上来讲,`RandomAccessFile` 的工作方式类似于把 `DataIunputStream``DataOutputStream` 组合起来使用。另外它还有一些额外的方法,比如使用 `getFilePointer()` 可以得到当前文件指针在文件中的位置,使用 `seek()` 可以移动文件指针,使用 `length()` 可以得到文件的长度,另外,其构造器还需要传入第二个参数(和 C 语言中的 `fopen()` 相同)用来表示我们是准备对文件进行 “随机读”(r)还是“读写”(rw)。它并不支持只写文件,从这点来看,如果当初 `RandomAccessFile` 能设计成继承自 `DataInputStream`,可能也是个不错的实现方式。
199+
200+
在 Java 1.4 中,`RandomAccessFile` 的大多数功能(但不是全部)都被 nio 中的**内存映射文件(mmap)**取代,详见[附录:新 I/O](./Appendix-New-IO.md)
106201

107202
<!-- Typical Uses of I/O Streams -->
108203
## IO流典型用途
109204

110205

206+
207+
### 缓冲输入文件
208+
209+
210+
211+
### 从内存输入
212+
213+
214+
215+
### 格式化内存输入
216+
217+
218+
219+
### 基本文件的输出
220+
221+
222+
223+
### 文本文件输出快捷方式
224+
225+
226+
227+
### 存储和恢复数据
228+
229+
230+
231+
### 读写随机访问文件
232+
233+
234+
111235
<!-- Summary -->
112236
## 本章小结
113237

238+
239+
114240
[^1]: 很难说这就是一个很好的设计选择,尤其是与其它编程语言中简单的 I/O 类库相比较。但它确实是如此选择的一个正当理由。
115241

116-
[^2]: XML 是另一种方式,可以解决在不同计算平台之间移动数据,而不依赖于所有平台上都有 Java 这一问题。XML 将在[附录:对象序列化](./Appendix-Object-Serialization.md)一章中进行介绍。
242+
[^2]: 译者注:“flush” 直译是“清空”,意思是把缓冲中的数据清空,输送到对应的目的地(如文件和屏幕)。
243+
244+
[^3]: XML 是另一种方式,可以解决在不同计算平台之间移动数据,而不依赖于所有平台上都有 Java 这一问题。XML 将在[附录:对象序列化](./Appendix-Object-Serialization.md)一章中进行介绍。
117245

118246
<!-- 分页 -->
119247

0 commit comments

Comments
 (0)