10.7 Java 1.1的IO流.md 15.2 KB
Newer Older
Q
quanke 已提交
1 2
# 10.7 Java 1.1的IO流

Q
quanke 已提交
3

W
ch10  
wizardforcel 已提交
4
到这个时候,大家或许会陷入一种困境之中,怀疑是否存在IO流的另一种设计模式,并可能要求更大的代码量。还有人能提出一种更古怪的设计吗?事实上,Java 1.1对IO流库进行了一些重大的改进。看到`Reader``Writer`类时,大多数人的第一个印象(就象我一样)就是它们用来替换原来的`InputStream``OutputStream`类。但实情并非如此。尽管不建议使用原始数据流库的某些功能(如使用它们,会从编译器收到一条警告消息),但原来的数据流依然得到了保留,以便维持向后兼容,而且:
Q
quanke 已提交
5

Q
quanke 已提交
6
(1) 在老式层次结构里加入了新类,所以Sun公司明显不会放弃老式数据流。
Q
quanke 已提交
7 8 9

(2) 在许多情况下,我们需要与新结构中的类联合使用老结构中的类。为达到这个目的,需要使用一些“桥”类:

W
10.6~8  
wizardforcel 已提交
10
`InputStreamReader`将一个`InputStream`转换成`Reader``OutputStreamWriter`将一个`OutputStream`转换成`Writer`
W
ch10  
wizardforcel 已提交
11
所以与原来的IO流库相比,经常都要对新IO流进行层次更多的封装。同样地,这也属于装饰器方案的一个缺点——需要为额外的灵活性付出代价。
Q
quanke 已提交
12

W
ch10  
wizardforcel 已提交
13
之所以在Java 1.1里添加了`Reader``Writer`层次,最重要的原因便是国际化的需求。老式IO流层次结构只支持8位字节流,不能很好地控制16位Unicode字符。由于Unicode主要面向的是国际化支持(Java内含的`char`是16位的Unicode),所以添加了`Reader``Writer`层次,以提供对所有IO操作中的Unicode的支持。除此之外,新库也对速度进行了优化,可比旧库更快地运行。
W
wizardforcel 已提交
14

Q
quanke 已提交
15 16 17
与本书其他地方一样,我会试着提供对类的一个概述,但假定你会利用联机文档搞定所有的细节,比如方法的详尽列表等。

10.7.1 数据的发起与接收
Q
quanke 已提交
18

W
ch10  
wizardforcel 已提交
19
Java 1.0的几乎所有IO流类都有对应的Java 1.1类,用于提供内建的Unicode管理。似乎最容易的事情就是“全部使用新类,再也不要用旧的”,但实际情况并没有这么简单。有些时候,由于受到库设计的一些限制,我们不得不使用Java 1.0的IO流类。特别要指出的是,在旧流库的基础上新加了`java.util.zip`库,它们依赖旧的流组件。所以最明智的做法是“尝试性”地使用`Reader``Writer`类。若代码不能通过编译,便知道必须换回老式库。
Q
quanke 已提交
20

Q
quanke 已提交
21
下面这张表格分旧库与新库分别总结了信息发起与接收之间的对应关系。
Q
quanke 已提交
22 23

```
Q
quanke 已提交
24 25 26 27 28 29 30
Sources & Sinks:
Java 1.0 class

Corresponding Java 1.1 class

InputStream

W
10.6~8  
wizardforcel 已提交
31
Reader
Q
quanke 已提交
32 33 34 35
converter: InputStreamReader

OutputStream

W
10.6~8  
wizardforcel 已提交
36
Writer
Q
quanke 已提交
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
converter: OutputStreamWriter

FileInputStream

FileReader

FileOutputStream

FileWriter

StringBufferInputStream

StringReader

(no corresponding class)

StringWriter

ByteArrayInputStream

CharArrayReader

ByteArrayOutputStream

CharArrayWriter

PipedInputStream

PipedReader

PipedOutputStream

PipedWriter

Q
quanke 已提交
71
```
Q
quanke 已提交
72 73 74 75

我们发现即使不完全一致,但旧库组件中的接口与新接口通常也是类似的。

10.7.2 修改数据流的行为
Q
quanke 已提交
76

W
10.6~8  
wizardforcel 已提交
77
在Java 1.0中,数据流通过`FilterInputStream``FilterOutputStream`的“装饰器”(Decorator)子类适应特定的需求。Java 1.1的IO流沿用了这一思想,但没有继续采用所有装饰器都从相同`filter`(过滤器)基类中派生这一做法。若通过观察类的层次结构来理解它,这可能令人出现少许的困惑。
Q
quanke 已提交
78

W
10.6~8  
wizardforcel 已提交
79
在下面这张表格中,对应关系比上一张表要粗糙一些。之所以会出现这个差别,是由类的组织造成的:尽管`BufferedOutputStream``FilterOutputStream`的一个子类,但是`BufferedWriter`并不是`FilterWriter`的子类(对后者来说,尽管它是一个抽象类,但没有自己的子类或者近似子类的东西,也没有一个“占位符”可用,所以不必费心地寻找)。然而,两个类的接口是非常相似的,而且不管在什么情况下,显然应该尽可能地使用新版本,而不应考虑旧版本(也就是说,除非在一些类中必须生成一个`Stream`,不可生成`Reader`或者`Writer`)。
Q
quanke 已提交
80 81

```
Q
quanke 已提交
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
Filters:
Java 1.0 class

Corresponding Java 1.1 class

FilterInputStream

FilterReader

FilterOutputStream

FilterWriter (abstract class with no subclasses)

BufferedInputStream

BufferedReader
(also has readLine( ))

BufferedOutputStream

BufferedWriter

DataInputStream

use DataInputStream
(Except when you need to use readLine( ), when you should use a BufferedReader)

PrintStream

PrintWriter

LineNumberInputStream

LineNumberReader

StreamTokenizer

StreamTokenizer
(use constructor that takes a Reader instead)

PushBackInputStream

PushBackReader
Q
quanke 已提交
125
```
Q
quanke 已提交
126 127 128 129


过滤器:Java 1.0类 对应的Java 1.1类

Q
quanke 已提交
130
```
Q
quanke 已提交
131 132 133 134 135 136 137
FilterInputStream FilterReader
FilterOutputStream FilterWriter(没有子类的抽象类)
BufferedInputStream BufferedReader(也有readLine())
BufferedOutputStream BufferedWriter
DataInputStream 使用DataInputStream(除非要使用readLine(),那时需要使用一个BufferedReader)
PrintStream PrintWriter
LineNumberInputStream LineNumberReader
W
wizardforcel 已提交
138
StreamTokenizer StreamTokenizer(用构造器取代Reader)
Q
quanke 已提交
139
PushBackInputStream PushBackReader
Q
quanke 已提交
140
```
Q
quanke 已提交
141

W
10.6~8  
wizardforcel 已提交
142
有一条规律是显然的:若想使用`readLine()`,就不要再用一个`DataInputStream`来实现(否则会在编译期得到一条出错消息),而应使用一个`BufferedReader`。但除这种情况以外,`DataInputStream`仍是Java 1.1 IO库的“首选”成员。
Q
quanke 已提交
143

W
10.6~8  
wizardforcel 已提交
144
为了将向`PrintWriter`的过渡变得更加自然,它提供了能采用任何`OutputStream`对象的构造器。`PrintWriter`提供的格式化支持没有`PrintStream`那么多;但接口几乎是相同的。
Q
quanke 已提交
145 146

10.7.3 未改变的类
Q
quanke 已提交
147

Q
quanke 已提交
148 149 150 151
显然,Java库的设计人员觉得以前的一些类毫无问题,所以没有对它们作任何修改,可象以前那样继续使用它们:

没有对应Java 1.1类的Java 1.0类

Q
quanke 已提交
152
```
Q
quanke 已提交
153 154 155 156
DataOutputStream
File
RandomAccessFile
SequenceInputStream
Q
quanke 已提交
157
```
Q
quanke 已提交
158

W
10.6~8  
wizardforcel 已提交
159
特别未加改动的是`DataOutputStream`,所以为了用一种可转移的格式保存和获取数据,必须沿用`InputStream``OutputStream`层次结构。
Q
quanke 已提交
160 161

10.7.4 一个例子
Q
quanke 已提交
162

W
10.6~8  
wizardforcel 已提交
163
为体验新类的效果,下面让我们看看如何修改`IOStreamDemo.java`示例的相应区域,以便使用`Reader``Writer`类:
Q
quanke 已提交
164 165

```
Q
quanke 已提交
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 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 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
//: NewIODemo.java
// Java 1.1 IO typical usage
import java.io.*;

public class NewIODemo {
  public static void main(String[] args) {
    try {
      // 1. Reading input by lines:
      BufferedReader in =
        new BufferedReader(
          new FileReader(args[0]));
      String s, s2 = new String();
      while((s = in.readLine())!= null)
        s2 += s + "\n";
      in.close();

      // 1b. Reading standard input:
      BufferedReader stdin =
        new BufferedReader(
          new InputStreamReader(System.in));      
      System.out.print("Enter a line:");
      System.out.println(stdin.readLine());

      // 2. Input from memory
      StringReader in2 = new StringReader(s2);
      int c;
      while((c = in2.read()) != -1)
        System.out.print((char)c);

      // 3. Formatted memory input
      try {
        DataInputStream in3 =
          new DataInputStream(
            // Oops: must use deprecated class:
            new StringBufferInputStream(s2));
        while(true)
          System.out.print((char)in3.readByte());
      } catch(EOFException e) {
        System.out.println("End of stream");
      }

      // 4. Line numbering & file output
      try {
        LineNumberReader li =
          new LineNumberReader(
            new StringReader(s2));
        BufferedReader in4 =
          new BufferedReader(li);
        PrintWriter out1 =
          new PrintWriter(
            new BufferedWriter(
              new FileWriter("IODemo.out")));
        while((s = in4.readLine()) != null )
          out1.println(
            "Line " + li.getLineNumber() + s);
        out1.close();
      } catch(EOFException e) {
        System.out.println("End of stream");
      }

      // 5. Storing & recovering data
      try {
        DataOutputStream out2 =
          new DataOutputStream(
            new BufferedOutputStream(
              new FileOutputStream("Data.txt")));
        out2.writeDouble(3.14159);
        out2.writeBytes("That was pi");
        out2.close();
        DataInputStream in5 =
          new DataInputStream(
            new BufferedInputStream(
              new FileInputStream("Data.txt")));
        BufferedReader in5br =
          new BufferedReader(
            new InputStreamReader(in5));
        // Must use DataInputStream for data:
        System.out.println(in5.readDouble());
        // Can now use the "proper" readLine():
        System.out.println(in5br.readLine());
      } catch(EOFException e) {
        System.out.println("End of stream");
      }

      // 6. Reading and writing random access
      // files is the same as before.
      // (not repeated here)

    } catch(FileNotFoundException e) {
      System.out.println(
        "File Not Found:" + args[1]);
    } catch(IOException e) {
      System.out.println("IO Exception");
    }
  }
} ///:~
Q
quanke 已提交
262
```
Q
quanke 已提交
263 264

大家一般看见的是转换过程非常直观,代码看起来也颇相似。但这些都不是重要的区别。最重要的是,由于随机访问文件已经改变,所以第6节未再重复。
Q
quanke 已提交
265

W
10.6~8  
wizardforcel 已提交
266
第1节收缩了一点儿,因为假如要做的全部事情就是读取行输入,那么只需要将一个`FileReader`封装到`BufferedReader`之内即可。第`1b`节展示了封装`System.in`,以便读取控制台输入的新方法。这里的代码量增多了一些,因为`System.in`是一个`DataInputStream`,而且`BufferedReader`需要一个`Reader`参数,所以要用`InputStreamReader`来进行转换。
Q
quanke 已提交
267

W
10.6~8  
wizardforcel 已提交
268 269
在2节,可以看到如果有一个字符串,而且想从中读取数据,只需用一个`StringReader`替换`StringBufferInputStream`,剩下的代码是完全相同的。

W
ch10  
wizardforcel 已提交
270
第3节揭示了新IO流库设计中的一个错误。如果有一个字符串,而且想从中读取数据,那么不能再以任何形式使用`StringBufferInputStream`。若编译一个涉及`StringBufferInputStream`的代码,会得到一条“反对”消息,告诉我们不要用它。此时最好换用一个`StringReader`。但是,假如要象第3节这样进行格式化的内存输入,就必须使用`DataInputStream`——没有什么`DataReader`可以代替它——而`DataInputStream`很不幸地要求用到一个`InputStream`参数。所以我们没有选择的余地,只好使用编译器不赞成的`StringBufferInputStream`类。编译器同样会发出反对信息,但我们对此束手无策(注释②)。
W
10.6~8  
wizardforcel 已提交
271
`StringReader`替换`StringBufferInputStream`,剩下的代码是完全相同的。
Q
quanke 已提交
272 273 274

②:到你现在正式使用的时候,这个错误可能已经修正。

W
10.6~8  
wizardforcel 已提交
275
第4节明显是从老式数据流到新数据流的一个直接转换,没有需要特别指出的。在第5节中,我们被强迫使用所有的老式数据流,因为`DataOutputStream``DataInputStream`要求用到它们,而且没有可供替换的东西。然而,编译期间不会产生任何“反对”信息。若不赞成一种数据流,通常是由于它的构造器产生了一条反对消息,禁止我们使用整个类。但在`DataInputStream`的情况下,只有`readLine()`是不赞成使用的,因为我们最好为`readLine()`使用一个`BufferedReader`(但为其他所有格式化输入都使用一个`DataInputStream`)。
Q
quanke 已提交
276

W
10.6~8  
wizardforcel 已提交
277
若比较第5节和`IOStreamDemo.java`中的那一小节,会注意到在这个版本中,数据是在文本之前写入的。那是由于Java 1.1本身存在一个错误,如下述代码所示:
Q
quanke 已提交
278 279

```
Q
quanke 已提交
280 281 282 283 284
//: IOBug.java
// Java 1.1 (and higher?) IO Bug
import java.io.*;

public class IOBug {
W
10.6~8  
wizardforcel 已提交
285
  public static void main(String[] args)
Q
quanke 已提交
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
  throws Exception {
    DataOutputStream out =
      new DataOutputStream(
        new BufferedOutputStream(
          new FileOutputStream("Data.txt")));
    out.writeDouble(3.14159);
    out.writeBytes("That was the value of pi\n");
    out.writeBytes("This is pi/2:\n");
    out.writeDouble(3.14159/2);
    out.close();

    DataInputStream in =
      new DataInputStream(
        new BufferedInputStream(
          new FileInputStream("Data.txt")));
    BufferedReader inbr =
      new BufferedReader(
        new InputStreamReader(in));
    // The doubles written BEFORE the line of text
    // read back correctly:
    System.out.println(in.readDouble());
    // Read the lines of text:
    System.out.println(inbr.readLine());
    System.out.println(inbr.readLine());
    // Trying to read the doubles after the line
    // produces an end-of-file exception:
    System.out.println(in.readDouble());
  }
} ///:~
Q
quanke 已提交
315
```
Q
quanke 已提交
316

W
10.6~8  
wizardforcel 已提交
317
看起来,我们在对一个`writeBytes()`的调用之后写入的任何东西都不是能够恢复的。这是一个十分有限的错误,希望在你读到本书的时候已获得改正。为检测是否改正,请运行上述程序。若没有得到一个异常,而且值都能正确打印出来,就表明已经改正。
Q
quanke 已提交
318

W
ch10  
wizardforcel 已提交
319
10.7.5 重导向标准IO
Q
quanke 已提交
320

W
10.6~8  
wizardforcel 已提交
321
Java 1.1在`System`类中添加了特殊的方法,允许我们重新定向标准输入、输出以及错误IO流。此时要用到下述简单的静态方法调用:
Q
quanke 已提交
322 323

```
Q
quanke 已提交
324 325 326
setIn(InputStream)
setOut(PrintStream)
setErr(PrintStream)
Q
quanke 已提交
327 328
```

Q
quanke 已提交
329
如果突然要在屏幕上生成大量输出,而且滚动的速度快于人们的阅读速度,输出的重定向就显得特别有用。在一个命令行程序中,如果想重复测试一个特定的用户输入序列,输入的重定向也显得特别有价值。下面这个简单的例子展示了这些方法的使用:
Q
quanke 已提交
330 331

```
Q
quanke 已提交
332
//: Redirecting.java
W
10.6~8  
wizardforcel 已提交
333
// Demonstrates the use of redirection for
Q
quanke 已提交
334 335 336 337 338 339
// standard IO in Java 1.1
import java.io.*;

class Redirecting {
  public static void main(String[] args) {
    try {
W
10.6~8  
wizardforcel 已提交
340
      BufferedInputStream in =
Q
quanke 已提交
341 342 343 344 345 346 347 348 349 350 351 352
        new BufferedInputStream(
          new FileInputStream(
            "Redirecting.java"));
      // Produces deprecation message:
      PrintStream out =
        new PrintStream(
          new BufferedOutputStream(
            new FileOutputStream("test.out")));
      System.setIn(in);
      System.setOut(out);
      System.setErr(out);

W
10.6~8  
wizardforcel 已提交
353
      BufferedReader br =
Q
quanke 已提交
354 355 356 357 358 359 360 361 362 363 364 365
        new BufferedReader(
          new InputStreamReader(System.in));
      String s;
      while((s = br.readLine()) != null)
        System.out.println(s);
      out.close(); // Remember this!
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
} ///:~

Q
quanke 已提交
366 367
```

Q
quanke 已提交
368
这个程序的作用是将标准输入同一个文件连接起来,并将标准输出和错误重定向至另一个文件。
W
10.6~8  
wizardforcel 已提交
369
这是不可避免会遇到“反对”消息的另一个例子。用`-deprecation`标志编译时得到的消息如下:
Q
quanke 已提交
370

Q
quanke 已提交
371

Q
quanke 已提交
372

W
10.6~8  
wizardforcel 已提交
373 374
> Note:The constructor `java.io.PrintStream(java.io.OutputStream)` has been deprecated.
注意:不推荐使用构造器`java.io.PrintStream(java.io.OutputStream)`
Q
quanke 已提交
375

W
10.6~8  
wizardforcel 已提交
376
然而,无论`System.setOut()`还是`System.setErr()`都要求用一个`PrintStream`作为参数使用,所以必须调用`PrintStream`构造器。所以大家可能会觉得奇怪,既然Java 1.1通过反对构造器而反对了整个`PrintStream`,为什么库的设计人员在添加这个反对的同时,依然为`System`添加了新方法,且指明要求用`PrintStream`,而不是用`PrintWriter`呢?毕竟,后者是一个崭新和首选的替换措施呀?这真令人费解。