| 特性 | Runtime.exec() | ProcessBuilder (推薦) |
|---|---|---|
| API 風格 | 靜態方法,簡單直接 | 建造者模式,靈活配置 |
| 參數處理 | 單字符串,需手動分割 | List |
| 環境變量 | 繼承父進程,難以定制 | environment() 精確控制 |
| 工作目錄 | 固定為 JVM 啟動目錄 | directory(File) 任意指定 |
| 輸出重定向 | 需手動讀取流 | redirectOutput() 直接導向文件 |
| 管道/鏈式命令 | 極其困難 | startPipeline() 支持 |
// 最簡單寫法 (Windows)
Runtime.getRuntime().exec("calc");
// ProcessBuilder 等價寫法
new ProcessBuilder("calc").start();
public static Process executeCommand(String... command) throws IOException {
return new ProcessBuilder(command)
.inheritIO() // 直接繼承父進程標準輸入/輸出/錯誤流
.start();
}
// 使用示例
// Windows: executeCommand("cmd", "/c", "dir");
// Linux/Mac: executeCommand("ls", "-la");
public static String captureOutput(String... command) throws IOException, InterruptedException {
Process process = new ProcessBuilder(command)
.redirectErrorStream(true) // 合併 stderr 到 stdout
.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append(System.lineSeparator());
}
int exitCode = process.waitFor(); // 等待進程結束
if (exitCode != 0) {
throw new RuntimeException("命令執行失敗,退出碼: " + exitCode + "\n輸出: " + output);
}
return output.toString();
}
}
// 使用
String javaVersion = captureOutput("java", "-version");
System.out.println(javaVersion);
// 錯誤:Runtime.exec("C:\\Program Files\\app.exe") 會被拆分為多個參數
// 正確:ProcessBuilder 自動處理
new ProcessBuilder("C:\\Program Files\\MyApp\\app.exe", "--arg=value").start();
// 或手動構建列表
List cmd = Arrays.asList(
"C:\\Program Files\\MyApp\\app.exe",
"--input", "file with spaces.txt",
"--output", "result.txt"
);
new ProcessBuilder(cmd).start();
ProcessBuilder pb = new ProcessBuilder("mvn", "clean", "package");
pb.environment().put("JAVA_HOME", "C:\\Java\\jdk-17");
pb.environment().remove("HTTP_PROXY"); // 移除代理
pb.directory(new File("D:\\projects\\my-app")); // 指定工作目錄
pb.redirectOutput(ProcessBuilder.Redirect.to(new File("build.log"))); // 輸出重定向到文件
pb.redirectError(ProcessBuilder.Redirect.INHERIT); // 錯誤輸出繼承控制台
Process process = pb.start();
Process process = new ProcessBuilder("long-running-task").start();
// 異步等待完成
CompletableFuture future = process.onExit();
future.thenAccept(p -> {
System.out.println("進程結束,退出碼: " + p.exitValue());
});
// 或輪詢
while (process.isAlive()) {
Thread.sleep(1000);
System.out.println("運行中...");
}
// 錯誤:只讀 stdout,stderr 緩衝區滿則進程阻塞
Process p = Runtime.getRuntime().exec("cmd /c dir /s");
BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
// 如果 stderr 有大量輸出,進程會卡死在這裡
// 正確:合併流 或 多線程同時讀取 stdout/stderr
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "dir", "/s");
pb.redirectErrorStream(true); // 方案一:合併
// 方案二:多線程讀取 (通用)
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(() -> consumeStream(p.getInputStream(), System.out::print));
es.submit(() -> consumeStream(p.getErrorStream(), System.err::print));
p.waitFor();
es.shutdown();
// 內部命令 (dir, copy, del) 需 cmd /c
new ProcessBuilder("cmd", "/c", "dir").start();
// 外部程序可直接調用
new ProcessBuilder("notepad.exe").start();
// 路徑含空格時
new ProcessBuilder("cmd", "/c", "\"C:\\Program Files\\app.exe\"").start();
// Windows 默認 GBK,Linux 默認 UTF-8
// 明確指定編碼
String os = System.getProperty("os.name").toLowerCase();
Charset charset = os.contains("win") ? Charset.forName("GBK") : StandardCharsets.UTF_8;
new InputStreamReader(process.getInputStream(), charset)
// 必須調用 waitFor() 或 onExit() 回收進程資源
// 長期運行的服務中,定期清理已結束但未回收的 Process 對象
// 服務端 (電腦端)
@SpringBootApplication
@RestController
public class RemoteShutdownServer {
@PostMapping("/shutdown")
public String shutdown(@RequestParam String token) {
if (!"secret123".equals(token)) return "Unauthorized";
try {
// Windows 關機命令
new ProcessBuilder("shutdown", "/s", "/t", "60", "/c", "遠程定時關機").start();
return "已發送關機指令,60秒後執行";
} catch (Exception e) {
return "失敗: " + e.getMessage();
}
}
public static void main(String[] args) {
SpringApplication.run(RemoteShutdownServer.class, args);
}
}
// 客戶端 (手機端發送 HTTP POST 即可)
ProcessBuilder 是現代 Java 執行系統命令的標準方式,提供了豐富的配置選項解決了 Runtime.exec() 的諸多痛點。掌握流處理、編碼處理、異步等待、安全防範等核心技能,能構建健壯的系統集成功能。記住:能用 Java API 解決的,絕不調用外部命令——跨平台性、安全性、可維護性均優於命令調用。