|
|
@@ -1,6 +1,6 @@
|
|
|
# JAVA Process 使用教程
|
|
|
|
|
|
-本教程通过 3 个可运行的示例,讲解 `ProcessBuilder` / `Process` 的核心用法。所有示例都以
|
|
|
+本教程通过 5 个可运行的示例,讲解 `ProcessBuilder` / `Process` 的核心用法。所有示例都以
|
|
|
`src/main/resources/code/` 下的 `.java` 程序作为子进程,先编译再运行。
|
|
|
每个**案例点**由独立的 `static` method 演示,在 `main()` 中依次调用。
|
|
|
|
|
|
@@ -78,7 +78,82 @@ try (OutputStream stdin = process.getOutputStream()) {
|
|
|
会把主线程钉住整个子进程生命周期,静默破坏超时语义(本仓库的踩坑点)。
|
|
|
- 若不读子进程输出且输出量超过管道缓冲区(Linux 约 64KB),子进程会因管道写满而阻塞、永不退出。
|
|
|
后台异步读取 + 超时后 `destroy()` 的设计可以兜住这种情况。
|
|
|
-- 想合并 stdout/stderr 时调用 `processBuilder.redirectErrorStream(true)`,两条流合到 `getInputStream()`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二-1、标准输出与错误输出的重定向 —— `ProcessRedirectExample`
|
|
|
+
|
|
|
+案例点:`errorStreamMerged()` / `redirectToFile()` / `redirectEnum()`(示例类:`space.anyi.process.ProcessRedirectExample`)
|
|
|
+
|
|
|
+子进程复用 `code/io/Main.java`。三个案例点:
|
|
|
+
|
|
|
+### 相互重定向 —— `errorStreamMerged()`
|
|
|
+
|
|
|
+```java
|
|
|
+// stderr 合并写入 stdout 管道,合并后的整体从 getInputStream() 读取,不再有独立 stderr 流
|
|
|
+Process process = new ProcessBuilder(Jdk.java(), "-Dfile.encoding=UTF-8", "Main")
|
|
|
+ .directory(WORK_DIR)
|
|
|
+ .redirectErrorStream(true)
|
|
|
+ .start();
|
|
|
+```
|
|
|
+演示结果里 `child: started...`(stdout)与 `child: stdin EOF...`(stderr)都出现在同一路 `[merged]` 日志中。
|
|
|
+
|
|
|
+### 重定向到文件 —— `redirectToFile()`
|
|
|
+
|
|
|
+```java
|
|
|
+.redirectInput(input.toFile()) // stdin 从文件读
|
|
|
+.redirectOutput(stdout.toFile()) // stdout 写到文件
|
|
|
+.redirectError(stderr.toFile()) // stderr 写到文件
|
|
|
+```
|
|
|
+子进程结束后读回文件校验:`stdout.txt` 含两行回显,`stderr.txt` 含一行 EOF 统计。可用于把子进程输出落盘、日志归档等场景。
|
|
|
+
|
|
|
+### Redirect 枚举 —— `redirectEnum()`
|
|
|
+
|
|
|
+| 枚举值 | 含义 | 演示 |
|
|
|
+|---|---|---|
|
|
|
+| `ProcessBuilder.Redirect.PIPE` | 默认值,子进程输出进入管道,父进程用 `getInputStream()` 读取 | 逐行读回子进程输出 |
|
|
|
+| `ProcessBuilder.Redirect.INHERIT` | 子进程 stdout/stderr 直接打印到父进程的控制台,不进管道 | 两行内容直接出现在父控制台 |
|
|
|
+| `ProcessBuilder.Redirect.DISCARD` | 子进程输出被直接丢弃 | 进程正常退出但读不到任何输出 |
|
|
|
+
|
|
|
+> `Redirect` 还提供 `to(File)`(输出重定向到文件,覆盖)与 `appendTo(File)`(追加)两个工厂方法,
|
|
|
+> 可用于 `redirectInput/Output/Error(Redirect)`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二-2、异步处理输入和输出 —— `ProcessAsyncExample`
|
|
|
+
|
|
|
+案例点:`asyncByThread()` / `asyncByCompletableFuture()`(示例类:`space.anyi.process.ProcessAsyncExample`)
|
|
|
+
|
|
|
+### 多线程 —— `asyncByThread()`
|
|
|
+
|
|
|
+```java
|
|
|
+// 每个流一个独立线程读取,主线程不阻塞在读取上,也避免管道写满导致子进程卡死
|
|
|
+Thread stdoutDrain = drain("thread-stdout", process.getInputStream());
|
|
|
+Thread stderrDrain = drain("thread-stderr", process.getErrorStream());
|
|
|
+...
|
|
|
+stdoutDrain.join(); // 读到 EOF(子进程退出)后汇合
|
|
|
+stderrDrain.join();
|
|
|
+```
|
|
|
+
|
|
|
+### CompletableFuture —— `asyncByCompletableFuture()`
|
|
|
+
|
|
|
+```java
|
|
|
+// stdout / stderr 读取各封装成一个异步任务
|
|
|
+CompletableFuture<Void> stdoutFuture = CompletableFuture.runAsync(() ->
|
|
|
+ new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))
|
|
|
+ .lines().forEach(line -> log.info("[cf-stdout] {}", line)));
|
|
|
+CompletableFuture<Void> stderrFuture = CompletableFuture.runAsync(() -> ...);
|
|
|
+
|
|
|
+// 进程结束时 onExit() 自动完成该 Future,注册回调即可在退出时拿到退出码
|
|
|
+CompletableFuture<Void> exitFuture = process.onExit()
|
|
|
+ .thenAccept(p -> log.info("onExit 回调: 退出码 {}", p.exitValue()));
|
|
|
+
|
|
|
+// 等待所有异步任务结束
|
|
|
+CompletableFuture.allOf(stdoutFuture, stderrFuture, exitFuture).join();
|
|
|
+```
|
|
|
+
|
|
|
+要点:`process.onExit()` 返回 `CompletableFuture<Process>`,不依赖 `destroy()`/阻塞读取,
|
|
|
+适合与函数式回调、并发编排(`allOf`/`thenAccept`)结合。
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -123,6 +198,7 @@ int exitCode = process.exitValue(); // 被信号终止时通常为 143(SIGTER
|
|
|
| `directory(File dir)` | 子进程工作目录 | 构建器自身 | 不设置则继承父进程目录 |
|
|
|
| `environment()` | 无 | `Map<String,String>` | 返回可变的子进程环境变量视图(增删改影响子进程) |
|
|
|
| `redirectInput/Output/Error(File)` | 文件 | 构建器自身 | 把子进程标准流重定向到文件 |
|
|
|
+| `redirectInput/Output/Error(Redirect)` | `Redirect` 枚举 | 构建器自身 | `PIPE`/`INHERIT`/`DISCARD`/`to(file)`/`appendTo(file)` |
|
|
|
| `redirectErrorStream(boolean)` | 是否合并 stderr 到 stdout | 构建器自身 | `true` 时子进程 stderr 写入 stdout 管道 |
|
|
|
| `start()` | 无 | `Process` | 启动子进程;目录不存在/命令找不到抛 `IOException` |
|
|
|
|