Java中调用外部脚本的方法总结

方案

Runtime

直接通过 Runtime 类的 exec() 方法执行命令来创建进程,执行外部程序。

  • 字符串形式:如果使用单个字符串作为命令,需要确保命令格式正确,特别是空格和引号的使用。
String command = "java -version";
Process process = Runtime.getRuntime().exec(command);
  • 字符串数组形式:使用字符串数组可以更清晰地分隔命令和参数,避免因空格等字符导致的问题。
String[] command = {"java", "-version"};
Process process = Runtime.getRuntime().exec(command);
int exitCode = process.waitFor();//等待进程结束
System.out.println("Process exited with code: " + exitCode);

ProcessBuilder

通过创建 ProcessBuilder 类的实例来使用。可以先设置各种参数,如运行命令、工作目录、环境变量等,然后再启动进程。

  • 无参构造
    使用无参构造函数创建 ProcessBuilder 对象后,可以通过 command() 方法设置要执行的命令和参数。command()支持可变长参数和List。此外command()方法也可以获取ProcessBuilder的命令和参数。
ProcessBuilder pb = new ProcessBuilder();
pb.command("ls", "-l", "/home/user")
  • 可变长参数 String… command。命令和参数必须分开传递,不能将它们合并成一个字符串(如 “ls -l /home/user”)。ProcessBuilder 会将每个字符串作为独立的参数传递给操作系统。
ProcessBuilder pb = new ProcessBuilder("notepad.exe");
ProcessBuilder pb = new ProcessBuilder("ls", "-l", "/home/user");

// 3.List或Array

List<String> command = new ArrayList<>();
command.add("java");
command.add("-jar");
command.add("myapp.jar");
ProcessBuilder processBuilder = new ProcessBuilder(command);
// 设置工作目录
pb.directory(new File("C:\\Users\\username\\Desktop")); 
// 设置环境变量
pb.environment().put("PATH", "C:\\Program Files\\Java\\jdk-11\\bin"); 
// 启动
Process p = pb.start();
int exitCode = process.waitFor();//等待进程结束
System.out.println("Process exited with code: " + exitCode);

方案对比

参数设置灵活性

  • ProcessBuilder参数设置更为灵活和详细。可以方便地设置工作目录、环境变量等。可以单独设置某个环境变量的值,或者完全替换整个环境变量集合。还可以设置运行命令的各个参数,以列表的形式传入,这样可以避免因空格等特殊字符导致的命令解析错误。
  • Runtime.getRuntime().exec()参数设置相对简单,主要通过字符串形式传入命令及参数。对于一些复杂的命令,尤其是包含多个参数和特殊字符的命令,可能需要额外的处理来确保正确执行。

进程控制

  • ProcessBuilder:提供了更丰富的进程控制方法。可以通过
    redirectErrorStream() 方法将错误输出流重定向到标准输出流,方便统一处理输出信息。还可以通过inheritIO() 方法让子进程继承 Java 程序的标准输入、输出和错误流。
  • Runtime.getRuntime().exec():在进程控制方面相对简单,主要通过
    Process 类的getInputStream()、getOutputStream()、getErrorStream() 方法来获取进程的输入、输出和错误流,对进程的控制和交互相对较为基础。

异常处理

  • ProcessBuilder:在创建ProcessBuilder 实例和设置参数时不会抛出异常,只有在调用start() 方法启动进程时,才会抛出IOException。这使得异常处理相对较为集中,便于在启动进程时统一处理可能出现的错误。
  • Runtime.getRuntime().exec():在执行 exec() 方法时会抛出
    IOException。如果命令字符串格式错误、找不到可执行文件等情况都会导致异常。需要在调用 exec() 方法时进行异常捕获和处理。

适用场景

  • ProcessBuilder:适用于需要精细控制外部进程的情况,如需要设置特定的工作目录、环境变量,或者对进程的输入输出流进行复杂处理的场景。例如在开发一些需要调用外部命令行工具,并且对工具的运行环境和输出结果有严格要求的应用程序时,ProcessBuilder 是更好的选择。
  • Runtime.getRuntime().exec():适用于简单的外部命令执行场景,当只需要快速执行一个命令,并且对命令的参数、运行环境等没有太多特殊要求时,使用Runtime.getRuntime().exec() 更为简洁方便。可以在一些简单的脚本执行、快速调用系统命令等场景中。

输出流(标准/错误)处理

如果不处理输出流将会有如下风险

  • 缓冲区溢出
    当外部进程向标准输出或错误输出写入大量数据时,这些数据会被放入缓冲区。如果缓冲区满了而没有读取,进程可能会被阻塞,无法继续执行,直到缓冲区中有空间可以写入新的数据.
  • 资源占用
    即使进程结束了,如果缓冲区中的数据没有被读取,这些数据仍然占用着内存资源。虽然在进程结束后,操作系统最终会回收这些资源,但在进程结束之前,这些资源是无法被释放的.
  • 错误信息丢失
    如果不读取错误输出流,将无法获取外部进程执行过程中可能出现的错误信息。这会导致调试和问题排查变得困难,因为你无法了解外部进程为何失败或出现问题.
  • 影响程序稳定性
    在某些情况下,缓冲区溢出可能导致Java程序的不稳定,甚至出现异常或崩溃。这是因为外部进程的输出可能会不断累积,最终导致Java程序的资源耗尽.

输出流处理方法

  • 通用处理方法
    获取Process对象后,直接读取InputStream,ErrorStream流处理
new Thread(() -> {
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}).start();

new Thread(() -> {
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.err.println(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}).start();
  • ProcessBuilder的特殊处理方法
    1.将错误流重定向到标准输出流
    redirectErrorStream方法用于将进程的错误输出流(stderr)重定向到标准输出流(stdout)。这样,错误信息和正常输出信息都会通过同一个流输出,方便统一处理。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class RedirectErrorStreamExample {
    public static void main(String[] args) {
        // 创建 ProcessBuilder 对象并设置命令和参数
        ProcessBuilder processBuilder = new ProcessBuilder("java", "-jar", "nonexistent.jar");

        // 将错误流重定向到标准输出流
        processBuilder.redirectErrorStream(true);

        try {
            // 启动进程
            Process process = processBuilder.start();

            // 读取标准输出流(同时包含错误信息)
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("进程输出: " + line);
            }

            // 等待进程结束并获取退出码
            int exitCode = process.waitFor();
            System.out.println("进程退出码: " + exitCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}

2.重定向到文件
可以将输出和错误流重定向到文件,以便后续查看:

ProcessBuilder processBuilder = new ProcessBuilder("command", "arg1", "arg2");
processBuilder.redirectOutput(new File("output.log")); // 重定向标准输出
processBuilder.redirectError(new File("error.log"));   // 重定向标准错误
Process process = processBuilder.start();

3.丢弃输出
如果完全不关心输出内容,可以将输出和错误流重定向到 null 设备(在 Unix 系统中是 /dev/null,在 Windows 系统中是 NUL):

ProcessBuilder processBuilder = new ProcessBuilder("command", "arg1", "arg2");
processBuilder.redirectOutput(ProcessBuilder.Redirect.DISCARD); // 丢弃标准输出
processBuilder.redirectError(ProcessBuilder.Redirect.DISCARD);  // 丢弃标准错误
Process process = processBuilder.start();

愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值