|
3 | 3 | <!-- Appendix: Standard I/O -->
|
4 | 4 | # 附录:标准IO
|
5 | 5 |
|
| 6 | +>*标准 I/O*这个术语参考Unix中的概念,指程序所使用的单一信息流(这种思想在大多数操作系统中,也有相似形式的实现)。 |
| 7 | +
|
| 8 | +程序的所有输入都可以来自于*标准输入*,其所有输出都可以流向*标准输出*,并且其所有错误信息均可以发送到*标准错误*。*标准 I/O* 的意义在于程序之间可以很容易地连接起来,一个程序的标准输出可以作为另一个程序的标准输入。这是一个非常强大的工具。 |
| 9 | + |
| 10 | +## 从标准输入中读取 |
| 11 | + |
| 12 | +遵循标准 I/O 模型,Java 提供了标准输入流 `System.in`、标准输出流 `System.out` 和标准错误流 `System.err`。在本书中,你已经了解到如何使用 `System.out`将数据写到标准输出。 `System.out` 已经预先包装[^1]成了 `PrintStream` 对象。标准错误流 `System.err` 也预先包装为 `PrintStream` 对象,但是标准输入流 `System.in` 是原生的没有经过包装的 `InputStream`。这意味着尽管可以直接使用标准输出流 `System.in` 和标准错误流 `System.err`,但是在读取 `System.in` 之前必须先对其进行包装。 |
| 13 | + |
| 14 | +我们通常一次一行地读取输入。为了实现这个功能,将 `System.in` 包装成 `BufferedReader` 来使用,这要求我们用 `InputStreamReader` 把 `System.in` 转换[^2]成 `Reader` 。下面这个例子将键入的每一行显示出来: |
| 15 | + |
| 16 | +```java |
| 17 | +// standardio/Echo.java |
| 18 | +// How to read from standard input |
| 19 | +import java.io.*; |
| 20 | +import onjava.TimedAbort; |
| 21 | + |
| 22 | +public class Echo { |
| 23 | + public static void main(String[] args) { |
| 24 | + TimedAbort abort = new TimedAbort(2); |
| 25 | + new BufferedReader( |
| 26 | + new InputStreamReader(System.in)) |
| 27 | + .lines() |
| 28 | + .peek(ln -> abort.restart()) |
| 29 | + .forEach(System.out::println); |
| 30 | + // Ctrl-Z or two seconds inactivity |
| 31 | + // terminates the program |
| 32 | + } |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +`BufferedReader` 提供了 `lines()` 方法,返回类型是 `Stream<String>` 。这显示出流模型的的灵活性:仅使用标准输入就能很好地工作。 `peek()` 方法重启 `TimeAbort`,只要保证至少每隔两秒有输入就能够使程序保持开启状态。 |
| 37 | + |
| 38 | +## 将`System.out` 转换成 `PrintWriter` |
| 39 | + |
| 40 | +`System.out` 是一个 `PrintStream`,而 `PrintStream` 是一个`OutputStream`。 `PrintWriter` 有一个把 `OutputStream` 作为参数的构造器。因此,如果你需要的话,可以使用这个构造器把 `System.out` 转换成 `PrintWriter` 。 |
| 41 | + |
| 42 | +```java |
| 43 | +// standardio/ChangeSystemOut.java |
| 44 | +// Turn System.out into a PrintWriter |
| 45 | + |
| 46 | +import java.io.*; |
| 47 | + |
| 48 | +public class ChangeSystemOut { |
| 49 | + public static void main(String[] args) { |
| 50 | + PrintWriter out = |
| 51 | + new PrintWriter(System.out, true); |
| 52 | + out.println("Hello, world"); |
| 53 | + } |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +输出结果: |
| 58 | + |
| 59 | +``` |
| 60 | +Hello, world |
| 61 | +``` |
| 62 | + |
| 63 | +要使用 `PrintWriter` 带有两个参数的构造器,并设置第二个参数为 `true`,从而使能自动刷新到输出缓冲区的功能;否则,可能无法看到打印输出。 |
| 64 | + |
| 65 | +## 重定向标准 I/O |
| 66 | + |
| 67 | +Java的 `System` 类提供了简单的 `static` 方法调用,从而能够重定向标准输入流、标准输出流和标准错误流: |
| 68 | +- setIn(InputStream) |
| 69 | +- setOut(PrintStream) |
| 70 | +- setErr(PrintStream) |
| 71 | + |
| 72 | +如果我们突然需要在显示器上创建大量的输出,而这些输出滚动的速度太快以至于无法阅读时,重定向输出就显得格外有用,可把输出内容重定向到文件中供后续查看。对于我们想重复测试特定的用户输入序列的命令行程序来说,重定向输入就很有价值。下例简单演示了这些方法的使用: |
| 73 | + |
| 74 | +```java |
| 75 | +// standardio/Redirecting.java |
| 76 | +// Demonstrates standard I/O redirection |
| 77 | +import java.io.*; |
| 78 | + |
| 79 | +public class Redirecting { |
| 80 | + public static void main(String[] args) { |
| 81 | + PrintStream console = System.out; |
| 82 | + try ( |
| 83 | + BufferedInputStream in = new BufferedInputStream( |
| 84 | + new FileInputStream("Redirecting.java")); |
| 85 | + PrintStream out = new PrintStream( |
| 86 | + new BufferedOutputStream( |
| 87 | + new FileOutputStream("Redirecting.txt"))) |
| 88 | + ) { |
| 89 | + System.setIn(in); |
| 90 | + System.setOut(out); |
| 91 | + System.setErr(out); |
| 92 | + new BufferedReader( |
| 93 | + new InputStreamReader(System.in)) |
| 94 | + .lines() |
| 95 | + .forEach(System.out::println); |
| 96 | + } catch (IOException e) { |
| 97 | + throw new RuntimeException(e); |
| 98 | + } finally { |
| 99 | + System.setOut(console); |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +该程序将文件中内容载入到标准输入,并把标准输出和标准错误重定向到另一个文件。它在程序的开始保存了最初对 `System.out` 对象的引用,并且在程序结束时将系统输出恢复到了该对象上。 |
| 106 | + |
| 107 | +I/O重定向操作的是字节流而不是字符流,因此使用 `InputStream` 和 `OutputStream`,而不是 `Reader` 和 `Writer`。 |
| 108 | + |
6 | 109 | <!-- Process Control -->
|
7 | 110 | ## 执行控制
|
8 | 111 |
|
| 112 | +你经常需要在Java内部直接执行操作系统的程序,并控制这些程序的输入输出,Java类库提供了执行这些操作的类。 |
| 113 | + |
| 114 | +一项常见的任务是运行程序并将输出结果发送到控制台。本节包含了一个可以简化此任务的实用工具。 |
| 115 | + |
| 116 | +在使用这个工具时可能会产生两种类型的错误:导致异常的普通错误——对于这些错误我们只需要重新抛出一个 `RuntimeException` 即可,以及进程自身的执行过程中导致的错误——我们需要用单独的异常来报告这些错误: |
| 117 | + |
| 118 | +```java |
| 119 | +// onjava/OSExecuteException.java |
| 120 | +package onjava; |
| 121 | + |
| 122 | +public class OSExecuteException extends RuntimeException { |
| 123 | + public OSExecuteException(String why) { |
| 124 | + super(why); |
| 125 | + } |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +为了运行程序,我们需要传递给 `OSExecute.command()` 一个 `String command`,我们可以在控制台键入同样的指令运行程序。该命令传递给 `java.lang.ProcessBuilder` 的构造器(需要将其作为 `String` 对象的序列),然后启动生成的 `ProcessBuilder` 对象。 |
| 130 | + |
| 131 | +```java |
| 132 | +// onjava/OSExecute.java |
| 133 | +// Run an operating system command |
| 134 | +// and send the output to the console |
| 135 | +package onjava; |
| 136 | +import java.io.*; |
| 137 | + |
| 138 | +public class OSExecute { |
| 139 | + public static void command(String command) { |
| 140 | + boolean err = false; |
| 141 | + try { |
| 142 | + Process process = new ProcessBuilder( |
| 143 | + command.split(" ")).start(); |
| 144 | + try ( |
| 145 | + BufferedReader results = new BufferedReader( |
| 146 | + new InputStreamReader( |
| 147 | + process.getInputStream())); |
| 148 | + BufferedReader errors = new BufferedReader( |
| 149 | + new InputStreamReader( |
| 150 | + process.getErrorStream())) |
| 151 | + ) { |
| 152 | + results.lines() |
| 153 | + .forEach(System.out::println); |
| 154 | + err = errors.lines() |
| 155 | + .peek(System.err::println) |
| 156 | + .count() > 0; |
| 157 | + } |
| 158 | + } catch (IOException e) { |
| 159 | + throw new RuntimeException(e); |
| 160 | + } |
| 161 | + if (err) |
| 162 | + throw new OSExecuteException( |
| 163 | + "Errors executing " + command); |
| 164 | + } |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +为了捕获在程序执行时产生的标准输出流,我们可以调用 `getInputStream()`。这是因为 `InputStream` 是我们可以从中读取信息的流。 |
| 169 | + |
| 170 | +这里这些行只是被打印了出来,但是你也可以从 `command()` 捕获和返回它们。 |
| 171 | + |
| 172 | +该程序的错误被发送到了标准错误流,可以调用 `getErrorStream()` 捕获。如果存在任何错误,它们都会被打印并且抛出 `OSExcuteException` ,以便调用程序处理这个问题。 |
| 173 | + |
| 174 | +下面是展示如何使用 `OSExecute` 的示例: |
| 175 | + |
| 176 | +```java |
| 177 | +// standardio/OSExecuteDemo.java |
| 178 | +// Demonstrates standard I/O redirection |
| 179 | +// {javap -cp build/classes/main OSExecuteDemo} |
| 180 | +import onjava.*; |
| 181 | + |
| 182 | +public class OSExecuteDemo {} |
| 183 | +``` |
| 184 | + |
| 185 | +这里使用 `javap` 反编译器(随JDK发布)来反编译程序,编译结果: |
| 186 | + |
| 187 | +``` |
| 188 | +Compiled from "OSExecuteDemo.java" |
| 189 | +public class OSExecuteDemo { |
| 190 | + public OSExecuteDemo(); |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +[^1]: 译者注:这里用到了**装饰器模式**。 |
| 195 | + |
| 196 | +[^2]: 译者注:这里用到了**适配器模式**。 |
| 197 | + |
9 | 198 | <!-- 分页 -->
|
10 | 199 |
|
11 | 200 | <div style="page-break-after: always;"></div>
|
0 commit comments