首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 執行外部程式完整指南:Runtime.exec、ProcessBuilder、輸入輸出流控制與阻塞解決

     編輯:Java 相關知識

Java 應用常需啟動外部程式(Shell 指令、其他可執行檔、甚至另一個 JVM)。本文詳細比較 Runtime.exec() 與 ProcessBuilder,深入解析 Process 流控制、阻塞成因與解法,並提供完整程式碼範例。

兩種啟動方式

核心類別:Process

啟動後回傳 Process 實例,提供:

  • getInputStream():讀取標準輸出
  • getErrorStream():讀取錯誤輸出
  • getOutputStream():寫入標準輸入
  • waitFor():阻塞等待結束,回傳結束碼
  • exitValue():非阻塞取得結束碼(未結束拋異常)
  • destroy():嘗試終止子進程(不保證成功)

完整範例:ProcessBuilder + 流合併 + 執行緒讀取

public class ExternalProcessRunner {
    public static void main(String[] args) throws Exception {
        List cmd = Arrays.asList("cmd.exe", "/c", "dir");
        ProcessBuilder pb = new ProcessBuilder(cmd);
        pb.directory(new File("C:\\"));
        pb.redirectErrorStream(true); // 關鍵:合併錯誤流到標準輸出

        Process p = pb.start();

        // 非阻塞讀取輸出
        new Thread(() -> {
            try (BufferedReader br = new BufferedReader(
                    new InputStreamReader(p.getInputStream(), "GBK"))) {
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println("[OUT] " + line);
                }
            } catch (IOException e) { e.printStackTrace(); }
        }).start();

        int exitCode = p.waitFor();
        System.out.println("Process exited with code: " + exitCode);
    }
}

阻塞成因與三大解法

特性Runtime.exec()ProcessBuilder
API 風格靜態方法、多載豐富Builder 模式、鏈式設定
環境變數控制陣列傳遞,較死板environment() 回傳 Map 可動態修改
工作目錄參數傳遞directory(File)
錯誤流合併不支援redirectErrorStream(true)
推薦度舊版相容JDK 5+ 首選

最佳實務模板

ProcessBuilder pb = new ProcessBuilder(commandList)
    .directory(workDir)
    .redirectErrorStream(true); // 方案一:合併流

Process p = pb.start();

// 方案二:雙執行緒讀取(不合併流時)
StreamGobbler outGobbler = new StreamGobbler(p.getInputStream(), "OUT");
StreamGobbler errGobbler = new StreamGobbler(p.getErrorStream(), "ERR");
outGobbler.start(); errGobbler.start();

int exitCode = p.waitFor(); // 最後等待

在 Java 中啟動另一個 Java 程式

String javaExe = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
String classpath = System.getProperty("java.class.path");

List cmd = new ArrayList<>();
cmd.add(javaExe);
cmd.add("-classpath");
cmd.add(classpath);
cmd.add("com.example.MyClass");
cmd.add("arg1", "arg2");

ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);
Process p = pb.start();
// ... 讀取輸出、waitFor()

關鍵提醒

  • 僅能啟動實際可執行檔,Shell 內建指令(如 cd、set)需透過 cmd.exe /c 或 bash -c
  • 編碼一致性:讀取流時指定正確 Charset(Windows 常用 GBK/CP950,Linux 用 UTF-8)
  • 超時控制:Java 9+ 可用 p.waitFor(timeout, TimeUnit.SECONDS)
  • 資源釋放:try-with-resources 確保流關閉

掌握 ProcessBuilder 與流控制模式,能讓 Java 應用可靠地整合外部工具、腳本與舊系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
成因現象解法
只讀錯誤流,標準輸出緩衝區滿進程卡住先讀標準輸出,再讀錯誤流;或合併流
大量輸出未及時讀取緩衝區滿導致死鎖獨立執行緒持續讀取兩條流
先呼叫 waitFor() 再讀流進程未結束因緩衝區滿,主執行緒永久等待先讀流,最後呼叫 waitFor()