Kaynağa Gözat

doc: 补充重定向与异步处理输入输出章节及 Redirect API

yangyi 1 hafta önce
ebeveyn
işleme
2411c45d73
1 değiştirilmiş dosya ile 78 ekleme ve 2 silme
  1. 78 2
      doc.md

+ 78 - 2
doc.md

@@ -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` |