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();
}
}
}