Java中Process和Runtime的基础使用

Runtime

java.lang.Runtime 类是Java平台的一部分,用于提供与当前运行的Java应用程序相关的环境信息和操作方法。这个类主要用于执行与系统相关的一些任务,如执行外部程序、获取系统属性等

主要特点

1.单例模式

  • Runtime 类是一个单例类,意味着在整个Java应用程序中只能有一个 Runtime 实例。
  • 要获取 Runtime 的实例,通常使用Runtime.getRuntime() 方法。

单例模式的特点:单例模式更的详细介绍

  • 构造方法私有化:Runtime 类的构造方法是私有的,不允许外部类直接调用它来创建实例
  • 静态方法提供实例:提供了一个静态方法 getRuntime() 来获取 Runtime 类的唯一实例。
  • 如何获取 Runtime 实例:获取 Runtime 类的实例,应使用Runtime.getRuntime() 方法。该方法会返回当前JVM的 Runtime 实例。

2.不可被继承:Runtime 类是最终类(final),不能被继承

主要方法

exec(String command): 用于执行一个指定的命令行程序,并返回一个 Process 对象,可以用来与执行的程序进行交互

  • 例如:Process p = Runtime.getRuntime().exec("ls"); //linux中查看目录的命令

exec(String[] cmdarray):接受一个字符串数组作为参数,其中第一个元素是命令,其余元素是命令的参数

  • 例如:Process p = Runtime.getRuntime().exec(new String[]{"ls", "-l"});

exec(String[] cmdarray, String[] envp):接受一个字符串数组作为命令及其参数,并允许提供一个环境变量数组

  • 例如:Process p = Runtime.getRuntime().exec(new String[]{"ls", "-l"}, new String[]{"JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64"});

exec(String[] cmdarray, String[] envp, File dir): 接受一个字符串数组作为命令及其参数,一个环境变量数组,并允许指定执行命令的工作目录

  • 例如:Process p = Runtime.getRuntime().exec(new String[]{"ls", "-l"},new String[]{"JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64"}, new File("/home/user"));

Process

Runtime.getRuntime().exec(command) 返回一个 Process 类型的对象,这个对象提供了与所执行的外部进程进行交互的方法。
以下是使用这个 Process 对象可做的一些事情:

功能
获取输出通过 Process.getInputStream() 获取到新进程的标准输出流 (InputStream),然后读取输出的数据
发送输入如果外部进程需要输入,使用 Process.getOutputStream() 获取到一个输出流 (OutputStream),并通过这个流向进程发送数据。
等待进程结束Process.waitFor() 方法可以让当前线程阻塞直到外部进程完成,并返回该进程的退出码

两者的使用

首先明确两点:

  • Runtime.getRuntime().exec(command)并不是一个新的Java进程,而是启动了一个新的操作系统进程来执行指定的命令
  • Process 对象只是一个Java内部的封装,用于管理和与这个新启动的操作系统进程进行交互

再明确几个概念

  • Java虚拟机(JVM):当运行一个Java程序时,实际上是启动了一个Java虚拟机(JVM)实例,这个JVM实例本身是一个操作系统进程
  • 外部进程:当你用 Runtime.getRuntime().exec(command) 时,它会在当前的JVM进程之外启动一个新的操作系统进程来执行命令
  • Process 对象:Process 对象是由 Runtime.exec() 方法返回,前面说过它提供了一组方法来与新启动的操作系统进程进行交互。
  • 外部程序:不同的场景下可以代表多种类型的任务或程序,部分如下:

列出目录内容:使用 ls(Unix/Linux) 或 dir(Windows)命令来列出目录下的文件
Ping测试:使用 ping 命令来测试网络连接
过滤日志:使用 grep 或 awk(Unix/Linux)命令来过滤日志文件中的特定记录
监控CPU和内存:使用 top(Unix/Linux) 或 Task Manager(Windows)来监控系统的CPU和内存使用情况
编译代码:使用 gcc(Unix/Linux) 或 cl.exe(Windows)命令来编译C/C++代码

示例
public static void main(String[] args) {
        try {
            // 执行命令
            Process process = Runtime.getRuntime().exec("ls"); // 假设是在Unix/Linux环境中
            // 获取输出流
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            // 关闭流
            reader.close();
            // 等待进程结束并获取退出码
            int exitCode = process.waitFor();
            System.out.println("Process exited with code: " + exitCode);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
}

关于int exitCode = process.waitFor();

Runtime.getRuntime().exec 的退出码与外部程序的运行成功与否有关。外部程序的退出码(exit code)是一种常用的机制,用于指示程序执行的状态。通常情况下:

  • 退出码为0:表示程序正常结束,没有遇到错误。
  • 非零退出码:表示程序遇到了某种错误或异常情况。不同的非零值可能表示不同类型的错误

如果我们的程序需要根据外部进程的结果来进行下一步操作,或是外部进程的输出不需要实时处理,那么我们可以先等待外部进程完成并获取其结果

public static void main(String[] args) {
        try {
            // 执行命令
            Process process = Runtime.getRuntime().exec("ls"); // 假设是在Unix/Linux环境中

            // 等待外部进程完成并获取退出码
            int exitCode = process.waitFor();

            // 如果需要处理输出,现在可以读取
            if (exitCode == 0) {
                // 读取输出流
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
                // 关闭输入流
                reader.close();
            }else{
                //你可以在此对行外部程序出现错误进行处理
            }

        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

但是,需要注意:

如果外部进程会产生大量输出,并且这些输出数据量很大,那么等到外部进程完全结束之后再去读取输出流可能会导致数据积压,甚至可能导致流被关闭。为了避免这种情况,建议使用一个单独的线程来读取输出流。

改进示例,如何在等待外部进程完成之前读取输出流,并且确保数据不会积压

public static void main(String[] args) {
        try {
            // 执行命令
            Process process = Runtime.getRuntime().exec("ls"); // 假设是在Unix/Linux环境中

            // 创建线程来读取输出流
            Thread outputReader = 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();
                }
            });

            // 启动读取输出流的线程
            outputReader.start();

            // 等待外部进程完成并获取退出码
            int exitCode = process.waitFor();

            // 等待读取输出流的线程结束
            outputReader.join();

        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们创建了一个线程 outputReader 来专门读取标准输出流。这样做的好处是:

  • 防止数据积压:即使外部进程产生了大量的输出数据,也不会因为等待进程完成而导致数据积压。
  • 实时处理输出:可以实时处理外部进程的输出,这对于调试和监控非常有用。
  • 分离关注点:将读取输出流的操作与等待进程完成的操作分离,使得代码结构更加清晰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值