Java中使用Runtime和Process類運行外部程序
使用Runtime.getRuntime().exec()方法可以在java程序裏運行外部程序。
1. exec(String command)
2. exec(String command, String envp[], File dir)
3. exec(String cmd, String envp[])
4. exec(String cmdarray[])
5. exec(String cmdarray[], String envp[])
6. exec(String cmdarray[], String envp[], File dir)
一般的應用程序可以直接使用第一版本,當有環境變量傳遞的時候使用後面的版本。其中2和6版本可以傳遞一個目錄,標識當前目錄,因爲有些程序是使用相對目錄的,所以就要使用這個版本。
cmd.exe /c start <FileName>
使用DOS命令(比如dir)時也要使用到調用。如果想與調用的程序進行交互,那麼就要使用該方法的返回對象Process了,通過Process的getInputStream(),getOutputStream()和getErrorStream()方法可以得到輸入輸出流,然後通過InputStream可以得到程序對控制檯的輸出信息,通過OutputStream可以給程序輸入指令,這樣就達到了程序的交換功能。
用Java編寫應用時,有時需要在程序中調用另一個現成的可執行程序或系統命令,這時可以通過組合使用Java提供的Runtime類和Process類的方法實現。下面是一種比較典型的程序模式:
2 Process process = Runtime.getRuntime().exec(".\\p.exe");
3 process.waitfor();
4
在上面的程序中,第一行的“.\\p.exe”是要執行的程序名,Runtime.getRuntime()返回當前應用程序的Runtime對象,該對象的exec()方法指示Java虛擬機創建一個子進程執行指定的可執行程序,並返回與該子進程對應的Process對象實例。通過Process可以控制該子進程的執行或獲取該子進程的信息。第二條語句的目的等待子進程完成再往下執行。
但在windows平臺上,如果處理不當,有時並不能得到預期的結果。下面是筆者在實際編程中總結的幾種需要注意的情況:
1、執行DOS的內部命令
如果要執行一條DOS內部命令,有兩種方法。一種方法是把命令解釋器包含在exec()的參數中。例如,執行dir命令,在NT上,可寫成exec("cmd.exe /c dir"),在windows95/98下,可寫成“command.exe /c dir”,其中參數“/c”表示命令執行後關閉DOS立即關閉窗口。另一種方法是,把內部命令放在一個批命令my_dir.bat文件中,在Java程序中寫成exec("my_dir.bat")。如果僅僅寫成exec("dir"),Java虛擬機則會報運行時錯誤。前一種方法要保證程序的可移植性,需要在程序中讀取運行的操作系統平臺,以調用不同的命令解釋器。後一種方法則不需要做更多的處理。
2、打開一個不可執行的文件
打開一個不可執行的文件,但該文件存在關聯的應用程序,則可以有兩種方式。以打開一個word文檔a.doc文件爲例,Java中可以有以下兩種寫法:
2 exec("Files\\Microsoft Office\\office\\winword.exe .\\a.doc");
3、執行一個有標準輸出的DOS可執行程序
在Windows平臺上,運行被調用程序的DOS窗口在程序執行完畢後往往並不會自動關閉,從而導致Java應用程序阻塞在waitfor()語句。導致該現象的一個可能的原因是,該可執行程序的標準輸出比較多,而運行窗口的標準輸出緩衝區不夠大。解決的辦法是,利用Java中Process類提供的方法讓Java虛擬機截獲被調用程序的DOS運行窗口的標準輸出,在waitfor()命令之前讀出窗口的標準輸出緩衝區中的內容。一段典型的程序如下:
2 String s;
3 Process process = Runtime.getRuntime().exec("cmd /c dir \\windows");
4 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream());
5 while((s=bufferedReader.readLine()) != null)
6 System.out.println(s);
7 process.waitfor();
Java調用外部程序解決方案
關鍵字 Java 外部程序 CMD 進程 調用 Process
最近接觸一個需求,是利用Java調用本地命令行程序,並希望Java程序能與該命令行程序進行交互,Java對該程序的操作如同在終端中對程序的操縱一樣。
在技術調研的過程中,遇到了幾個問題:
- 如何Java調用命令行程序
- 如何利用Java向命令行程序的標準輸入寫入字符流
- 如何利用Java即時地得到命令行程序的標準輸出流。
- 如何利用Java即時地得到命令行程序的標準錯誤流
一、調用命令行程序
這個很簡單,Java調用的方法爲
Process process = Runtime.getRuntime().exec(“command name”);
Process的JavaDoc地址:http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Process.html
二、標準輸出
注意,在這裏標準輸出指的是Java程序以標準輸出的方式發出字節流,這些字節流會以標準輸入的方式進入被調用的命令行程序
OutputStream pOutputStream = process.getOutputStream();
PrintWriter outputWriter = new PrintWriter(pOutputStream, true);
outputWriter.print(string);
PrintWriter的第二個構造參數一定要選爲true,這樣才能自動flush進入外部程序,不然,沒有Flush,你向被調用程序所寫的輸入,只有在下一次緩衝被Flush的時候才能發揮作用,這樣,當你的輸入很少時,你雖然在代碼裏print了命令,但是外部程序並沒有得到他,就一直阻塞,這是開發者經常會遇到的問題。
三、標準輸入和錯誤輸入
private InputStream pErrorStream = process.getErrorStream();
private InputStream pInputStream =process.getInputStream();
這兩個輸入是用來接受外部程序的反饋的,外部程序通常會向標準終端打印字符,這些字符會通過這兩個流得到,經過測試,我們發現一個問題,如果外部程序在輸出信息時,沒有用flush也會出現問題,比如C語言的程序
scanf(“%d”, &i);
printf(“%d”, i);
這段代碼在運行時,雖然在終端裏會即時的顯示出來,但是卻不能及時地發送給pInputStream,這是因爲程序輸出使用了緩衝機製造成的,所以,這造成的困難是如果你沒有外部程序的源碼,你就很難將輸出即時顯示出來,我目前還沒有找到解決方案,如果你有源碼就好辦了,在源碼中設置輸出爲即時flush就好了,我用笨辦法來說明:
scanf(“%d”, &i);
printf(“%d”, i);
fflush(stdout);
這樣,fflush(stdout)之後,pInputStream就會得到輸入了。
四、綜合
下面我們用三個線程來進行一個簡單的與外部程序的交互過程的設計
線程一、
process.waitFor(),負責建立線程並等待線程結束
線程二、
for (int i = 0; i > -1; i = pInputStream.read(inBuffer)) {
// We have a new segment of input, so process it as a String..
System.out.print(new String(inBuffer, 0, i));
}
負責接收外部程序的輸出信息
線程三、
// Read the ErrorStream in a loop until we find no more bytes to read..
for (int i = 0; i > -1; i = pErrorStream.read(errBuffer)) {
// We have a new segment of error, so process it as a String..
Systerm.err.print(new String(errBuffer, 0, i));
}
負責接收外部程序的錯誤輸出信息
在適當的地方,調用outputWriter.print(string);向程序寫入字符流。
===========================================================================================================
1 java調用外部程序的方法
在一個java應用中,可能會遇到這樣的需求,就是需要調用一些外部的應用做一些處理,比如調用excel,然後在繼續程序的運行。
下面就開始進入java調用外部程序的一些演示,讓java應用更加靈活。
1:最簡單的演示:
Runtime.getRuntime().exec("notepad.exe");
記事本被打開了是吧。
2:傳遞應用程序的參數:
Runtime runtime=Runtime.getRuntime();
String[] commandArgs={"notepad.exe","c:/boot.ini"};
runtime.exec(commandArgs);
現在不單單打開了記事本,而且還裝載了boot.ini文件是吧。
現在已經完全解決了調用外部程序的問題,不是嗎,但是大家會發現exec方法是有返回值,那麼繼續我們的演示吧。
1:Process的waitFor:
Runtime runtime=Runtime.getRuntime();
String[] commandArgs={"notepad.exe","c:/boot.ini"};
Process process=runtime.exec(commandArgs);
int exitcode=process.waitFor();
System.out.println("finish:"+exitcode);
執行上面的代碼以後發現不同的地方了嗎,waitFor會使線程阻塞,只有外部程序退出後纔會執行System.out.println("finish:"+exitcode);
這個功能很有用是吧,因爲多數時候你都需要等待用戶處理完外部程序以後才繼續你的java應用。
2:Process的destroy:
Runtime runtime=Runtime.getRuntime();
String[] commandArgs={"notepad.exe","c:/boot.ini"};
final Process process=runtime.exec(commandArgs);
new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
process.destroy();
}}).start();
int exitcode=process.waitFor();
System.out.println("finish:"+exitcode);
這個演示稍微複雜了一些,如果你等待5秒,就會發現記事本自動關閉了,是的,這個就是destroy方法的作用,強制關閉調用的外部程序。
不用我解釋了吧,這是非常有用的方法。
以上的部分已經足夠你調用並控制你的外部應用了。如果需要更詳細的信息,看javadoc文檔吧。
最後的說明:ProcessBuilder這個1.5新增的類也可以完成同樣的任務,Runtime就是調用了這個類。
============================================================================================================
Java調用外部程序命令主要用到兩個類:
java.lang.Runtime
每個 Java 應用程序都有一個 Runtime 類實例,使應用程序能夠與其運行的環境相連接。可以通過 getRuntime 方法獲取當前運行時。應用程序不能創建自己的 Runtime 類實例。
java.lang.Process
ProcessBuilder.start() 和 Runtime.exec 方法創建一個本機進程,並返回 Process 子類的一個實例,該實例可用來控制進程並獲取相關信息。Process 類提供了執行從進程輸入、執行輸出到進程、等待進程完成、檢查進程的退出狀態以及銷燬(殺掉)進程的方法。 對於帶有 Process 對象的 Java 進程,沒有必要異步或併發執行由 Process 對象表示的進程。
下面Java調用Windows命令的例子:
- import java.io.IOException;
- import java.io.BufferedReader;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- /**
- * Created by IntelliJ IDEA.
- * User: leizhimin
- * Date: 2008-7-18
- * Time: 14:18:27
- * Java調用Windows命令測試
- */
- public class TestCmd {
- public static void main(String args[]) {
- testWinCmd();
- dirOpt();
- }
- public static void testWinCmd() {
- System.out.println("------------------testWinCmd()--------------------");
- Runtime runtime = Runtime.getRuntime();
- System.out.println(runtime.totalMemory());
- System.out.println(runtime.freeMemory());
- System.out.println(runtime.maxMemory());
- System.out.println(runtime.availableProcessors()); //處理器數
- try {
- //執行一個exe文件
- runtime.exec("notepad");
- runtime.exec("C://Program Files//Microsoft Office//OFFICE11//winword.exe c://test.doc");
- //執行批處理
- runtime.exec("c://x.bat");
- //執行系統命令
- runtime.exec("cmd /c dir ");
- runtime.exec("cmd /c dir c://");
- // //-------------- 文件操作 --------------
- runtime.exec("cmd /c copy c://x.bat d://x.txt"); //copy並改名
- runtime.exec("cmd /c rename d://x.txt x.txt.bak"); //重命名
- runtime.exec("cmd /c move d://x.txt.bak c://"); //移動
- runtime.exec("cmd /c del c://x.txt.bak"); //刪除
- //-------------- 目錄操作 --------------
- runtime.exec("cmd /c md c://_test"); //刪除
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- /**
- * 執行批處理文件,並獲取輸出流重新輸出到控制檯
- */
- public static void dirOpt() {
- System.out.println("------------------dirOpt()--------------------");
- Process process;
- try {
- //執行命令
- process = Runtime.getRuntime().exec("c://x.bat");
- //取得命令結果的輸出流
- InputStream fis = process.getInputStream();
- //用一個讀輸出流類去讀
- BufferedReader br = new BufferedReader(new InputStreamReader(fis));
- String line = null;
- //逐行讀取輸出到控制檯
- while ((line = br.readLine()) != null) {
- System.out.println(line);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
下面Java調用Perl命令的例子:
- //PerlExecResult is a user-defined class, It just saves execPerl's results
- public static PerlExecResult execPerl(){
- String[] cmd = { "perl", "pprogram.pl", "param1", "param2" };
- StringBuffer resultStringBuffer = new StringBuffer();
- String lineToRead = "";
- //get Process to execute perl, get the output and exitValue
- int exitValue = 0;
- try{
- Process proc = Runtime.getRuntime().exec( cmd );
- InputStream inputStream = proc.getInputStream();
- BufferedReader bufferedRreader =
- new BufferedReader( new InputStreamReader( inputStream ) );
- //save first line
- if( ( lineToRead = bufferedRreader.readLine() ) != null ){
- resultStringBuffer.append( lineToRead );
- }
- //save next lines
- while( ( lineToRead = bufferedRreader.readLine() ) != null ){
- resultStringBuffer.append( "/r/n" );
- resultStringBuffer.append( lineToRead );
- }
- //Always reading STDOUT first, then STDERR, exitValue last
- proc.waitFor(); //wait for reading STDOUT and STDERR over
- exitValue = proc.exitValue();
- }catch( Exception ex ){
- resultStringBuffer = new StringBuffer( "" );
- exitValue = 2;
- }
- PerlExecResult perlExecResult = new PerlExecResult( resultStringBuffer.toString(), exitValue );
- return perlExecResult;
- }
=================================================================================================
幾乎所有的Java 集成開發環境都需要調用外部進程進行Java程序的構建,編譯,運行和調試,Eclipse,NetBeans,JBuilder和Intellij IDLE概莫例外。在執行過程中,將提示信息以黑色全部打印在控制檯裏,將異常和錯誤以紅色方式打印。以非常醒目交互體驗讓程序員遠離枯燥和乏味。
現在讓我們以Eclipse爲例來看看它如何工作的,以揭開它神祕面紗,探究隱藏在後面的祕密。
上篇主要介紹了JAVA IDE Console通過採用Runtime.getRuntime.exec()執行外部程序後,將返回一個Process對象. Process對象能返回三個流:
getInputStream(),對應Process程序的標準輸出流。
getErrorStream(), 對應Process程序的標準錯誤輸出流。
getOutputStream();對應Process程序的標準輸入流。
函數名之所以與Process程序的方向相反,原因是站在Java Host程序的角度講的。
現在我們應用此原理來仿真IDE 執行外部程序的過程。
列表1:ConsoleSimulator.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
/**
* Class for console simulation
*
* @author lewhwa
*/
public class ConsoleSimulator implements Runnable {
private volatile boolean isStop = false ;
private static final int INFO = 0 ;
private static final int ERROR = 1 ;
private InputStream is;
private int type;
/** Creates a new instance of StreamInterceptor */
public ConsoleSimulator(InputStream is, int type) {
this .is = is;
this .type = type;
}
public void run() {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr);
String s;
try {
while (( ! isStop) && (s = reader.readLine()) != null ) {
if (s.length() != 0 ) {
if (type == INFO) {
System.out.println( " INFO> " + s);
} else {
System.err.println( " ERROR> " + s);
}
try {
Thread.sleep( 10 );
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void stop() {
isStop = true ;
}
public static void main(String[] args) throws IOException,
InterruptedException {
// Process child = Runtime.getRuntime().exec("run.bat");
Process child = Runtime.getRuntime().exec( " java -classpath bin helloworld.Test " );
OutputStream os = child.getOutputStream();
InputStream stdin = child.getInputStream(); //
InputStream stderr = child.getErrorStream();
Thread tIn = new Thread( new ConsoleSimulator(stdin, INFO));
Thread tErr = new Thread( new ConsoleSimulator(stderr, ERROR));
tIn.start();
tErr.start();
int result = child.waitFor();
tIn.join();
tErr.join();
if (result == 0 ) {
System.out.println( " SUCCESS! " );
} else {
System.out.println( " FAILED! " );
}
}
}
外部Bat文件:
列表2
cmd.exe / C / Q copy
javac
cmd.exe / C tree
rem c:\Designer_v5. 1 .0_win32_x86.exe c:\Designer_v5. 1 .0_win32_x861.exe
time / t
列表3:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**Test Class
* @author lewhwa
*
*/
public class Test {
public static void main(String[] args) throws IOException {
FileReader fir = new FileReader("src/helloworld/Test1.java");
BufferedReader br = new BufferedReader(fir);
String s;
while((s=br.readLine())!=null){
System.out.println(s);
}
fir.close();
}
}
當ConsoleSimulator程序執行外部的run.bat時,輸出如圖1所示:
圖1
當ConsoleSimulator程序執行外部的java test正常時,輸出如圖2所示:
圖2
當ConsoleSimulator程序執行外部的java test發生異常時,輸出如圖3所示:
圖3
綜上,雖然沒有在自己的GUI裏將stdout和stderr進行說明,只是用ERROR>提示符和INFO>提示符進行演示,但是完全IDE Console的原理。對ConsoleSimulator稍加修改,完全放入到自己的應用程序當中去。
在我們進行Java程序開發的過程當中,可能涉及到其它的應用程序,藉助這種技術,可以很好利用它們,將它們集成到自己的應用當中,將極大地縮短開發週期,何樂而不爲呢!