ProcessBuilder如何获取进程的输入流、输出流和错误流?
下文笔者讲述Java代码中ProcessBuilder获取进程的输入流、输出流、错误流的方法及示例分享,如下所示
ProcessBuilder简介
`ProcessBuilder` 本身不直接提供输入流、输出流和错误流
而是通过其启动的 `Process` 对象来获取。
一个进程的标准流包括:
- 标准输入流 (`stdin`):`Process.getOutputStream()`
- 标准输出流 (`stdout`):`Process.getInputStream()`
- 标准错误流 (`stderr`):`Process.getErrorStream()`
1. `Process.getOutputStream()`
- 作用:
获取一个输出流,
通过这个流写入的数据会成为目标进程的标准输入(`stdin`)。
- 数据走向:
当前 Java 程序 → `OutputStream` → 目标进程
可用它向一个需要交互的命令或程序发送数据
如 `python` 或 `bc`。
2. `Process.getInputStream()`
- 作用:
获取一个输入流,这个流的数据来源是目标进程的标准输出(`stdout`)。
- 数据走向:
目标进程 → `InputStream` → 当前 Java 程序
如:获取命令执行结果(比如 `ls -l` 的输出)的主要方式
3. `Process.getErrorStream()`
- 作用:
获取一个输入流,这个流的数据来源是目标进程的标准错误输出(`stderr`)
- 数据走向:
目标进程 → `InputStream` → 当前 Java 程序**
当命令执行出错时(比如 `ls nonexistent_file`)
错误信息会通过这个流输出。
强烈建议始终处理这个流
否则当进程产生大量错误输出时,缓冲区可能会被填满,导致进程阻塞。
例:同时处理 `stdout` 和 `stderr`
import java.io.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class StreamGobblerExample {
public static void main(String[] args) {
// 示例:执行一个会同时产生标准输出和错误输出的命令
// Windows: "cmd", "/c", "dir && echo This is stderr 1>&2"
// Linux/macOS: "sh", "-c", "ls -l && echo This is stderr >&2"
ProcessBuilder pb = new ProcessBuilder("sh", "-c", "ls -l && echo This is stderr >&2");
try {
Process process = pb.start();
// 使用线程池来并发处理 stdout 和 stderr,避免互相阻塞
ExecutorService executor = Executors.newFixedThreadPool(2);
// 处理标准输出
executor.submit(new StreamGobbler(process.getInputStream(), "STDOUT"));
// 处理标准错误
executor.submit(new StreamGobbler(process.getErrorStream(), "STDERR"));
// 等待进程执行完成
int exitCode = process.waitFor();
System.out.println("\nProcess exited with code: " + exitCode);
executor.shutdown();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 一个辅助类,用于异步读取输入流并打印。
* 这可以防止因一个流的缓冲区满而导致进程阻塞。
*/
private static class StreamGobbler implements Runnable {
private final InputStream inputStream;
private final String streamType;
public StreamGobbler(InputStream inputStream, String streamType) {
this.inputStream = inputStream;
this.streamType = streamType;
}
@Override
public void run() {
// 使用 try-with-resources 确保流被正确关闭
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(streamType + ": " + line);
}
} catch (IOException e) {
// 在某些情况下,当进程被销毁时,流可能会提前关闭,这是正常的
// e.printStackTrace();
}
}
}
}
代码说明
1. `ProcessBuilder` 创建进程:
启动了一个 `sh` (或 `cmd`) 进程,执行了两个命令:`ls -l` (列出目录) 和 `echo This is stderr >&2` (将一条消息输出到标准错误)。
2. `StreamGobbler` 类:这是一个关键的辅助类,它实现了 `Runnable` 接口。
它的作用是在一个单独的线程中持续读取一个 `InputStream` 并打印其内容。
3. 并发处理:通过 `ExecutorService` 启动两个线程,分别处理 `stdout` 和 `stderr`。
这样做可以避免一个流的处理速度慢而阻塞另一个流。例如,如果 `stderr` 输出很多,而你的代码只在 `stdout` 读取完后才去读 `stderr`,那么进程可能会因为 `stderr` 缓冲区满而卡住。
4. `process.waitFor()`:主线程会阻塞在这里,直到子进程执行完毕。
5. 资源管理:在 `StreamGobbler` 中,
我们使用了 `try-with-resources` 语句来自动关闭 `BufferedReader`,这是一种良好的实践。
| 方法 | 返回值 | 对应进程的流 | 数据流向 | 用途 |
| `process.getOutputStream()` | `OutputStream` | 标准输入 (`stdin`) | Java → 外部进程 | 向外部进程发送数据(如输入密码、命令) |
| `process.getInputStream()` | `InputStream` | 标准输出 (`stdout`) | 外部进程 → Java | 读取外部进程的正常执行结果 |
| `process.getErrorStream()` | `InputStream` | 标准错误 (`stderr`) | 外部进程 → Java | 读取外部进程的错误信息 |
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。


