在Java中想调用外部程序,或者执行命令和可运行文件时,网上的典型实例一般都是通过Runtime.getTime().exec()
【 java.lang包】去执行相应的操作。看源码才发现还有Process和ProcessBuilder类,来具体看看它们的区别和用法。
一、Runtime类
Runtime类采用的饿汉式单例设计模式(定义了私有类变量和私有构造方法,通过静态方法返回该类的唯一实例)。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。应用程序不能创建自己的Runtime 类实例。只能通过 getRuntime 方法获取当前运行时对象的引用。
- 查看虚拟机相关状态
Runtime rt = Runtime.getRuntime(); //获取类的实例对象
System.out.println(rt.availableProcessors());// 返回可用于Java虚拟机的处理器数量
System.out.println(rt.freeMemory());// 返回Java虚拟机中的可用内存量 (单位:字节)
System.out.println(rt.maxMemory());// 返回Java虚拟机将尝试使用的最大内存量
System.out.println(rt.totalMemory());// 返回Java虚拟机中的内存总量
//--------------------------------------------------------------------------
// System中的gc(),exit(),load() 等都是调用了该类中的同名方法
rt.gc(); // 运行垃圾回收器
rt.exit(0); // 通过启动其关闭序列来终止当前正在运行的Java虚拟机,底层调用Shutdown.exit()实现
rt.halt(0); // 强制终止当前正在运行的Java虚拟机(杀进程)
- 通过钩子,保证java程序安全退出
Runtime rt = Runtime.getRuntime();
// java 1.8的写法
Thread t =new Thread(
() -> System.out.println(Thread.currentThread().getName()+" +++++++++ ")
);
rt.addShutdownHook(t); //注册新的虚拟机来关闭钩子
new Thread(
new Runnable() {
private int i;
@Override
public void run() {
for(;i<10;i++){
System.out.println(Thread.currentThread().getName()+" --- "+i);
}
}
}
).start();
//rt.exit(0); // 若使用exit() 会等待钩子运行后再进行退出
//rt.halt(0); // 若这使用此方法,直接强制终止当前虚拟机,不会执行钩子
//rt.removeShutdownHook(t); // 注册的钩子会被取消,不会打印 "+++++"
- 执行操作方法 Runtime.exec (返回用于管理子进程的新的Process对象 )
Process exec(String command)
在单独的进程中执行指定的字符串命令Peocess exec(String[] cmdarray)
在单独的进程中执行指定的命令和参数Process exec(String command, String[] envp)
在单独的进程中执行指定的字符串命令Process exec(String[] cmdarray, String[] envp)
在指定环境的单独进程中执行指定的命令和参数Process exec(String command, String[] envp, File dir)
在指定的环境和工作目录的单独进程中执行指定的字符串命令Process exec(String[] cmdarray, String[] envp, File dir)
在指定的环境和工作目录的单独进程中执行指定的命令和参数
这6个重载方法(方法名一样,参数个数不一样,参数类型不一样)。实际上2、4、5调用的都是6这个方法,而1、3调用的是方法5。先来看一下方法6中实现:
return new ProcessBuilder(cmdarray).environment(envp).directory(dir).start();
可以看出来,实际上Runtime.getRuntime().exec(...)是通过 ProcessBuilder.start()去实现创建进程的。再来看方法5:
if (command.length() == 0)
throw new IllegalArgumentException("Empty command");
StringTokenizer st = new StringTokenizer(command);
String[] cmdarray = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++)
cmdarray[i] = st.nextToken();
return exec(cmdarray, envp, dir);
首先是判断传入的命令参数长度是否等于0,是则抛出异常。然后通过StringTokenizer类对传入的命令字符串进行解析(这里使用的默认分隔符集,即" f" :空格字符,制表符,换行字符,回车字符和换页符)。并将解析后的Token使用for循环赋值给String数组的每个元素,最后将数组传给方法6。所以1-6方法实质上传入String类型的参数和传入String []数组参数是一样的。不过传入String [] 的优势在于:如果String类型输入的命令缺少空格,将会导致运行的命令错误。
实例:
import java.io.*;
import java.util.concurrent.TimeUnit;
public class Test002 {
public static void main(String [] args){
Runtime rt = Runtime.getRuntime();
try {
//Process p = rt.exec("cmd /c dir /s/b | findstr "Test[0-9]*.java"");// 当前文件中查找Test开头含多个数字的java文件
//Process p1 = rt.exec(new String[]{"cmd", "/c", "javac"});
Process p1 = rt.exec("cmd /c java -version"); // 将会通过 errorStream输出相关信息
PrintContext error = new PrintContext(p1.getErrorStream(), "Error");
PrintContext print = new PrintContext(p1.getInputStream(), "Print");
error.start();
print.start();
/*BufferedReader br = new BufferedReader(new InputStreamReader(p1.getInputStream(), "GBK"));
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}
System.out.println("---------");
p1.waitFor(1,TimeUnit.SECONDS); // 等待1 s
BufferedReader br1 = new BufferedReader(new InputStreamReader(p1.getErrorStream(), "GBK"));
String line1;
while((line1 = br1.readLine())!=null){
System.err.println(line1);
}*/
} catch (IOException e) {
e.printStackTrace();
}
}
}
class PrintContext extends Thread {
private InputStream is;
private String type;
public PrintContext(InputStream is, String type) {
this.is = is;
this.type = type;
}
public void run() {
BufferedReader br = null;
try {
// 控制台输出为GBK编码
br = new BufferedReader(new InputStreamReader(is,"GBK"));
String line;
while ((line = br.readLine()) != null) {
if (type.equals("Error")) {
System.out.println("Error:" + line);
} else {
System.out.println("Print:" + line);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br != null){
try{
br.close();
}catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
二、Process类
Process 类是一个抽象类,其内部提供了6个抽象方法。用于执行进程输入,执行到进程的输出,等待进程完成,检查进程的退出状态以及破坏(杀死)进程的方法。创建的子进程没有自己的终端或控制台。 其所有的标准I / O(即标准输入,标准输出,标准错误)操作将被重定向到父进程。 父进程使用流将输入提供给子进程并从子进程获取输出。 因为一些本地平台只为标准输入和输出流提供有限的缓冲区大小,因此无法及时写入输入流或读取子进程的输出流可能导致子进程阻塞甚至死锁。
Runtime rt = Runtime.getRuntime();
Process p,p1=null;
try {
p = rt.exec("notepad.exe");
if(p.isAlive()){ // 判断Process进程是否存活
System.out.println(" p start...");
try {
p.waitFor(3, TimeUnit.SECONDS); // 等待3秒
p1 = rt.exec("cmd /c dir");
System.out.println(" p1 start...");
} catch (InterruptedException e) {
e.printStackTrace();
}
p.destroy(); // 杀死子进程
}
p1.waitFor(); // 导致当前线程等待,如有必要,直到由此 Process对象表示的进程已终止
System.out.println("p1 wait done");
p1.destroy();
System.out.println("p:"+p.exitValue()); // 1 非正常退出
System.out.println("p1:"+p1.exitValue()); // 0 正常退出
} catch (IOException|InterruptedException e) {
e.printStackTrace();
}
三、ProcessBuilder类
该类是一个不可变类,用于创建操作系统进程。每个ProcessBuilder实例管理进程属性的集合,start()方法使用这些属性创建一个新的Process实例。ProcessBuilder有个带参构造方法:
ProcessBuilder(List<String> command) 构造具有指定操作系统程序和参数的进程构建器
ProcessBuilder(String... command) 构造具有指定操作系统程序和参数的进程构建器
其实这两个的构造方法区别也不是很大,一个是传入List<String>
类型的参数然后初始化,另一个是传入可变的String类型参数,初始化List对象,将参数add到ArraysList里。所以ProcessBuilder不能像Runtime一样直接将整句的命令作为参数传参。
ProcessBuilder pb = new ProcessBuilder("notepad.exe");
try {
pb.directory(new File("E:\WorkProjects")); // 设置进程构建器的工作目录
pb.command("cmd","/c","dir"); // 重新设置命令参数
Process p = pb.start(); // 运行进程
System.out.println(pb.directory().getPath()); // 获取进程构建器的工作目录路径
List<String> c = pb.command(); // 获取命令参数
Iterator<String> it = c.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");// cmd /c dir
}
p.destroy();
} catch (IOException e) {
e.printStackTrace();
}