Java 通過 Runtime.getRuntime().exec() 呼叫外部程序是常見需求,但在 Windows 平台上隱藏著諸多「坑」:內部命令無法直接執行、輸出緩衝區導致阻塞、路徑含空格失效等。本文系統總結實戰經驗,提供完整解決方案。
Process process = Runtime.getRuntime().exec(command);
process.waitFor(); // 阻塞等待子進程結束
exec() 返回 Process 對象,可控制子進程、獲取輸出流。waitFor() 同步等待,異步場景請使用 CompletableFuture 或線程池。錯誤寫法:exec("dir") → 报 IOException。
正確寫法:通過命令解釋器包裹:
// Windows NT/2000/XP/7/10/11
exec("cmd.exe /c dir")
// Windows 95/98 (極少見)
exec("command.exe /c dir")
/c 參數:執行完畢關閉窗口;/k 保留窗口(調試用)。os.name 動態選擇解釋器,或統一寫入 .bat 腳本調用。// 方式一:start 命令(推薦,簡潔)
exec("start .\\readme.pdf")
// 方式二:顯式指定程序完整路徑(不穩定,路徑易變)
exec("\"C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE\" .\\readme.pdf")
💡 start 會調用系統默認關聯程序,無需硬編碼路徑。
現象:子進程輸出過多,緩衝區滿 → 子進程阻塞 → waitFor() 永不返回。
解決:在 waitFor() 前主動讀取輸出流:
Process process = Runtime.getRuntime().exec("cmd /c dir \\Windows");
try (BufferedReader br = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line); // 或寫入日志、丟棄
}
}
int exitCode = process.waitFor();
getErrorStream() 防錯誤輸出阻塞。ProcessHandle 異步監控。場景:自動更新下載 ZIP → 解壓 → 刪除 ZIP,但解壓流未關閉導致刪除失敗。
暴力方案:調用系統 del 命令強刪(需 cmd /c):
// 正確
Runtime.getRuntime().exec("cmd /c del /f /q f:\\update.zip");
// 錯誤:缺少 cmd /c
Runtime.getRuntime().exec("del f:\\update.zip");
💡 根治:try-with-resources 確保流關閉,或使用 Files.deleteIfExists() (NIO.2)。
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "dir", "\\Windows");
pb.redirectErrorStream(true); // 合併錯誤流到標準輸出
Map env = pb.environment();
env.put("CUSTOM_VAR", "value");
Process process = pb.start();
// 後續流處理同前
| 場景 | 關鍵點 |
|---|---|
| 內部命令 | 必須包裹 cmd.exe /c |
| 大量輸出 | 先讀流,後 waitFor |
| 路徑含空格 | 用 ProcessBuilder 或雙引號包裹 |
| 文件刪除失敗 | 關閉流 / 用 cmd /c del /f |
| 編碼亂碼 | 指定 InputStreamReader(..., StandardCharsets.UTF_8) |