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

JDK 5 新特性實戰:ProcessBuilder 替代 Runtime.exec 執行外部進程

     編輯:J2SE

為什麼棄用 Runtime.exec?

  • 參數處理繁瑣:命令與參數需手動拆分數組,空格/引號轉義易出錯。
  • 環境變量不可控:無法為子進程單獨設置環境變量。
  • 工作目錄固定:無法指定子進程工作目錄。
  • 輸出流處理易死鎖:未及時讀取 stdout/stderr 導致緩衝區滿阻塞。

ProcessBuilder 核心優勢

能力Runtime.execProcessBuilder
命令構建String 或 String[]List / 可變參數,自動處理空格引號
環境變量繼承父進程,不可改environment() 返回 Map 可增刪改
工作目錄不可指定directory(File) 指定
輸入/輸出重定向手動管道redirectInput/Output/Error(File/ProcessBuilder.Redirect)
啟動方式立即啟動start() 延遲啟動,可復用配置

實戰案例:跨平台獲取物理網卡 MAC 地址


import java.io.*;
import java.util.*;

public class MacAddressExtractor {
    public static List getPhysicalAddresses() {
        List addresses = new ArrayList<>();
        ProcessBuilder pb = new ProcessBuilder(
            System.getProperty("os.name").toLowerCase().startsWith("win") 
                ? new String[]{"ipconfig", "/all"} 
                : new String[]{"ifconfig", "-a"}
        );
        // 可選:設置環境變量、工作目錄
        // pb.environment().put("LANG", "C");
        // pb.directory(new File("/tmp"));

        try {
            Process process = pb.start();
            // 異步讀取 stdout/stderr 避免死鎖 (Java 8+ 可用 CompletableFuture)
            String output = readStream(process.getInputStream());
            int exitCode = process.waitFor();
            if (exitCode != 0) {
                String err = readStream(process.getErrorStream());
                System.err.println("命令執行失敗: " + err);
                return addresses;
            }
            parseAddresses(output, addresses);
        } catch (IOException | InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
        return addresses;
    }

    private static String readStream(InputStream is) throws IOException {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) sb.append(line).append('\n');
            return sb.toString();
        }
    }

    private static void parseAddresses(String output, List addresses) {
        // Windows: "Physical Address. . . . . . . . . : 00-11-22-33-44-55"
        // Linux:   "ether 00:11:22:33:44:55 "
        String regex = System.getProperty("os.name").toLowerCase().startsWith("win")
            ? "Physical Address[.\\s]*:\\s*([0-9A-F-]{17})"
            : "ether\\s+([0-9a-f:]{17})";
        java.util.regex.Pattern p = java.util.regex.Pattern.compile(regex, java.util.regex.Pattern.CASE_INSENSITIVE);
        java.util.regex.Matcher m = p.matcher(output);
        while (m.find()) addresses.add(m.group(1).toUpperCase().replace('-', ':'));
    }

    public static void main(String[] args) {
        List macs = getPhysicalAddresses();
        for (String mac : macs) System.out.printf("物理網卡地址: %s%n", mac);
    }
}

進階用法:管道鏈與異步處理


// 管道鏈:ps -ef | grep java
ProcessBuilder pb1 = new ProcessBuilder("ps", "-ef");
ProcessBuilder pb2 = new ProcessBuilder("grep", "java");
pb2.redirectInput(ProcessBuilder.Redirect.PIPE); // 將 stdout 作為下一個 stdin
Process p1 = pb1.start();
Process p2 = pb2.start();
// 需手動將 p1.getInputStream() 複製到 p2.getOutputStream() (Java 9+ 支援 pb1.startPipeline(List))

// 異步非阻塞 (Java 8+)
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
    try { return readStream(pb.start().getInputStream()); }
    catch (IOException e) { throw new UncheckedIOException(e); }
});
future.thenAccept(System.out::println).exceptionally(e -> { e.printStackTrace(); return null; });

避坑指南

  1. 必須消費輸出流:無論是否關心輸出,必須在單獨線程讀取 stdout/stderr 至 EOF,否則子進程阻塞導致 waitFor() 永不返回。
  2. 檢查退出碼:process.waitFor() 非 0 表示異常,需讀取 errorStream 診斷。
  3. 跨平台命令差異:Windows/Linux/macOS 命令參數不同,建議抽象工廠模式封裝平台差異。
  4. 安全性:拒絕用戶輸入直接拼接命令,防止命令注入;使用 List 參數化傳遞。
  5. 資源釋放:try-with-resources 管理 InputStream,Java 9+ Process 實現 AutoCloseable 可自動銷毀子進程。

結語

ProcessBuilder 以流式 API、環境隔離、重定向支援全面超越 Runtime.exec,是 JDK 5+ 執行外部進程的標準選擇。配合 Java 8+ CompletableFuture、Java 9+ startPipeline/ProcessHandle,可構建健壯、非阻塞、跨平台的進程編排能力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved