Java进程调用外部程序的几种方法
扫地生在之前有记录通过Java程序控制远程服务器进而调用指定程序的笔记。使用java代码连接到局域网内的Windows服务器中的软件并执行指令
现在在简单总结一下Java进程调用外部程序的几种方法,期间会简单的通过源码来看一下。
通过Java执行系统命令,与cmd中或Linux终端上一种执行Shell命令,最典型的用法就是使用Runtime,getRuntime().exec(command)或者new ProcessBuilder(cmdArray).start().
1 Runtime
Runtime类是Java程序的运行时环境。不能new出一个Runtime对象,只能通过getRuntime()方法获取当前Runtime运行时对象的引用。然后可以调用Runtime的方法查看和修改Java虚拟机的状态。
通过上图可以看出,Runtime类并没有提供构造函数给我们,所以说我们是无法new一个Runtime对象的。这是简单单例,可以看到它的getRuntime()方法是没有做并行处理的,但整个环境中只允许出现一个Runtime对象。
Runtime和ProcessBuilder的不同点就是,启动子进程时的命令形式不同,Runtime.getRuntime.exec()可以把命令和参数写在一个String中,用空格分开,ProcessBuilder则是构造函数的参数中,传递一个由命令和参数组成的list或数组。
Runtime类主要的方法如下:
- exec方法接收一个命令然后执行,通过该方法可以执行和cmd中用法一样命令。比如,Runtime.getRuntime().exec(“ls”),就和cmd中执行ls效果一样了。
- freeMemory()可以查看当前虚拟机内存中空闲内存还有多少。
- totalMemory()可以查看当前虚拟机使用的总内存大小。
- maxMemory()可以查看JVM的最终可以使用的最大内存是多少。
- availableProcessors()可以查看本机有多少处理器,即本机处理器是多少核。
- exit(int)方法可以退出当前Java程序的运行,System.exit(int)方法就是调用了Runtime.exit(int)方法来退出运行的。
以上这些在我们做分布式并行化编程查看计算机参数时也会使用到。
@Test
public void test2() throws IOException {
Runtime.getRuntime().exec("java -version");
System.out.println(Runtime.getRuntime().freeMemory());
System.out.println(Runtime.getRuntime().totalMemory());
System.out.println(Runtime.getRuntime().maxMemory());
}
2 Process
Process是一个抽象类,主要方法如下:
- waitFor()是让当前主进程等待这个process指向的子进程执行完成。
- exitValue()可以查看process指向的子进程执行完的退出值,0代表是正常运行结束。
- destroy()和destroyForcibly()可以终止process子进程的运行,后者是强制终止,前者与平台终止进程的具体实现有关。
通过Runtime.getRuntime().exe(...)可以创建一个本地进程执行传入的命令,这个方法返回Process的一个实例:
process指向一个本地进程,相对于main进程来说,process指向的称为子进程。其中的is是为了获取子进程的输出信息。
明明是获取输出信息,为什么是InputStream呢?因为相对于main进程来说,子进程的输出就是main进程的输入,所以是InputStream。vice verse,如果要向子进程传递参数或者输入信息,则应该用OutputStream。但是不推荐用java 1.0引入的Process,而是用java 5.0的ProcessBuilder替代。
3 ProcessBuilder
ProcessBuilder是java 5.0引入的,start()方法返回Process的一个实例,如:
private static boolean processMp4(String oldfilepath) {
if (!checkfile(inputPath)) {
log.error(oldfilepath + "不是文件路径");
return false;
}
List<String> command = new ArrayList<String>();
command.add("ffmpeg");
command.add("-i");
command.add(oldfilepath);
command.add("-c:v");
command.add("libx264");
command.add("-mbd");
command.add("0");
command.add("-c:a");
command.add("aac");
command.add("-strict");
command.add("-2");
command.add("-pix_fmt");
command.add("yuv420p");
command.add("-movflags");
command.add("faststart");
command.add(outputPath + "a.mp4");
try {
Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();
new PrintStream(videoProcess.getErrorStream()).start();
new PrintStream(videoProcess.getInputStream()).start();
videoProcess.waitFor();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
创建ProcessBuilder不需要通过Runtime,而Runtime.getRimtime().exec(string)正是调用了ProcessBuilder的构造方法来创建子进程并执行的。
ProcessBuilder的构造方法接收一个命令参数的数组形式,其中,第一个元素代表要执行的系统命令,后面的元素代表要传给该命令的参数。
调用.start()方法运行之后,就可以获得该子进程的Process引用了,然后就可以调用Process的方法进行处理。
在用Runtime.getRuntime().exec()或ProcessBuilder(array).start()创建子进程Process之后,一定要及时取走子进程的输出信息和错误信息,否则输出信息流和错误信息流很可能因为信息太多导致被填满,最终导致子进程阻塞住,然后执行不下去。
比如上面扫地生的代码中使用线程及时取走了输出信息和错误信息:
new PrintStream(videoProcess.getErrorStream()).start();
/**
* 在用Runtime.getRuntime().exec()或ProcessBuilder(array).start()创建子进程Process之后,
* 一定要及时取走子进程的输出信息和错误信息,否则输出信息流和错误信息流很可能因为信息太多导致被填满,
* 最终导致子进程阻塞住,然后执行不下去。
*/
class PrintStream extends Thread {
InputStream is = null;
public PrintStream(InputStream is)
{
this.is = is;
}
@Override
public void run() {
try{
while(true) {
int ch = is.read();
if(ch != -1) {
System.out.print((char)ch);
} else {
break;
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}