1. 程式人生 > >Java中Process和Runtime()使用,以及呼叫cmd命令阻塞在process.waitfor( )的問題解決

Java中Process和Runtime()使用,以及呼叫cmd命令阻塞在process.waitfor( )的問題解決

轉自:http://249wangmang.blog.163.com/blog/static/52630765201261334351635/

最近在java中呼叫perl程式,由於perl中使用斯坦福分詞器,有很多控制檯輸出,導致一直阻塞在process.waitfor( ),只有強制終止java程式後,結果檔案才會輸出。根據下面兩個部落格內容成功解決。


用Java編寫應用時,有時需要在程式中呼叫另一個現成的可執行程式或系統命令,這時可以通過組合使用Java提供的Runtime類和Process類的方法實現。下面是一種比較典型的程式模式:
  Process process = Runtime.getRuntime().exec("p.exe");
最近在java中呼叫perl程式,由於perl中使用斯坦福分詞器,有很多控制檯輸出,導致一直阻塞在process.waitfor( ),只有強制終止java程式後,結果檔案才會輸出。根據下面兩個部落格內容成功解決。


用Java編寫應用時,有時需要在程式中呼叫另一個現成的可執行程式或系統命令,這時可以通過組合使用Java提供的Runtime類和Process類的方法實現。下面是一種比較典型的程式模式:
  Process process = Runtime.getRuntime().exec("p.exe");
  process.waitfor( );
在上面的程式中,第一行的“p.exe”是要執行的程式名;Runtime.getRuntime()返回當前應用程式的Runtime物件,該物件的exec()方法指示Java虛擬機器建立一個子程序執行指定的可執行程式,並返回與該子程序對應的Process物件例項。通過Process可以控制該子程序的執行或獲取該子程序的資訊。第二條語句的目的等待子程序完成再往下執行。但在windows平臺上,如果處理不當,有時並不能得到預期的結果。下面是筆者在實際程式設計中總結的幾種需要注意的情況:
  1、執行DOS的內部命令如果要執行一條DOS內部命令,有兩種方法。一種方法是把命令直譯器包含在exec()的引數中。例如,執行dir命令,在NT上,可寫成exec ("cmd.exe /c dir"),在windows 95/98下,可寫成“command.exe/c dir”,其中引數“/c”表示命令執行後關閉Dos立即關閉視窗。另一種方法是,把內部命令放在一個批命令my_dir.bat檔案中,在Java程式中寫成exec("my_dir.bat")。如果僅僅寫成exec("dir"),Java虛擬機器則會報執行時錯誤。前一種方法要保證程式的可移植性,需要在程式中讀取執行的作業系統平臺,以呼叫不同的命令直譯器。後一種方法則不需要做更多的處理。
   2、開啟一個不可執行的檔案開啟一個不可執行的檔案,但該檔案存在關聯的應用程式,則可以有兩種方式。以開啟一個word文件a.doc檔案為例,Java中可以有以下兩種寫法:
exec("start a.doc");
exec(" c:\\Program Files\\MicrosoftOffice\\office winword.exe a.doc");
顯然,前一種方法更為簡捷方便。
   3、執行一個有標準輸出的DOS可執行程式在windows 平臺上,執行被呼叫程式的DOS視窗在程式執行完畢後往往並不會自動關閉,從而導致Java應用程式阻塞在waitfor( )。導致該現象的一個可能的原因是,該可執行程式的標準輸出比較多,而執行視窗的標準輸出緩衝區不夠大。解決的辦法是,利用Java提供的Process 類提供的方法讓Java虛擬機器截獲被呼叫程式的DOS執行視窗的標準輸出,在waitfor()命令之前讀出視窗的標準輸出緩衝區中的內容。一段典型的程式如下:
String str;
Process process =Runtime.getRuntime().exec("cmd /c dir windows");
BufferedReader bufferedReader = newBufferedReader( new InputStreamReader(process.getInputStream()));
while ( (str=bufferedReader.readLine()) !=null) System.out.println(str);  
process.waitfor(); 
示例
public static boolean  resize(String   pic,String   picTo,int width,int height)  {
       boolean result = true;
        String cmd = "cmd /c  convert -sample " + width + "x" + height + "   "" + pic + """ +"   "" + picTo+""";
        log.debug(cmd);
       try {
            Process process = Runtime.getRuntime().exec(cmd);
           if (process.getErrorStream().read() != -1) {
                 result = false;
                 process.destroy();
            }
        } catch (IOException e) {
            log.debug("creat icon pic fail!" + e);
            result = false;
        }
       /*BufferedReader bufferedReader = new BufferedReader( newInputStreamReader(process.getInputStream());
        while ( (str=bufferedReader.readLine()) != null)System.out.println(str);   */
       return result;
    }


####################################################################################
我使用上面的程式處理不好使。然後查到下面的blog看到了如下內容。問題被解決。^-^
####################################################################################


Process process = Runtime.getRuntime.exec(cmd); // 執行呼叫perl命令
InputStream is = process.getInputStream(); // 獲取perl程序的輸出流
BufferedReader br = new Buffered(new InputStreamReader(is)); // 緩衝讀入
StringBuilder buf = new StringBuilder(); // 儲存perl的輸出結果流
String line = null;
while((line = br.readLine()) != null) buf.append(line); // 迴圈等待程序結束
System.out.println("ffmpeg輸出內容為:" + buf);
……
    本來一般都是這樣來呼叫程式並獲取程序的輸出流的,但是我在windows上執行這樣的呼叫的時候卻總是在while那裡被堵塞了,結果造成ffmpeg程式在執行了一會後不再執行,這裡從官方的參考文件中我們可以看到這是由於緩衝區的問題,由於java程序沒有清空ffmpeg程式寫到緩衝區的內容,結果導致ffmpeg程式一直在等待。在網上也查找了很多這樣的問題,不過說的都是使用單獨的執行緒來進行控制,我也嘗試過很多網是所說的方法,可一直沒起什麼作用。下面就是我的解決方法了,注意到上述程式碼中的紅色部分了麼?這裡就是關鍵,我把它改成如下結果就可以正常運行了。
InputStream is = process.getErrorStream(); // 獲取ffmpeg程序的輸出流
    注意到沒?我把它改成獲取錯誤流這樣程序就不會被堵塞了,而我之前一直想的是同樣的命令我手動呼叫的時候可以完成,而java呼叫卻總是完成不了,一直認為是getInputStream的緩衝區沒有被清空,不過問題確實是緩衝區的內容沒有被清空,但不是getInputStream的,而是getErrorStream的緩衝區,這樣問題就得到解決了。所以我們在遇到java呼叫外部程式而導致執行緒阻塞的時候,可以考慮使用兩個執行緒來同時清空process獲取的兩個輸入流,如下這段程式:
……
  Process p = Runtime.getRuntime().exec("perl class.pl");
      //Process p = Runtime.getRuntime().exec("cmd.exe /c dir");
         final InputStream is1 = p.getInputStream();
         new Thread(new Runnable() {
             public void run() {
                 BufferedReader br = new BufferedReader(new InputStreamReader(is1));
                 try{
                 while(br.readLine() != null) ;
                 }
                 catch(Exception e) {
            e.printStackTrace();
                 }
             }
         }).start(); // 啟動單獨的執行緒來清空p.getInputStream()的緩衝區
         InputStream is2 = p.getErrorStream();
         BufferedReader br2 = new BufferedReader(new InputStreamReader(is2)); 
         StringBuilder buf = new StringBuilder(); // 儲存輸出結果流
         String line = null;
         while((line = br2.readLine()) != null) buf.append(line); // 
         System.out.println("輸出結果為:" + buf);
……
    通過這樣我們使用一個執行緒來讀取process.getInputStream()的輸出流,使用另外一個執行緒來獲取process.getErrorStream()的輸出流,這樣我們就可以保證緩衝區得到及時的清空而不擔心執行緒被阻塞了。當然根據需要你也可以保留process.getInputStream()流中的內容,這個就看呼叫的程式的處理了。
今天看了斯坦福分詞器的原始碼,發現用了大量System.err.print,怪不得需要使用getErrorStream()捕捉!關於System.err和System.out的區別,可以參考別的日誌。這兩個流走的是不同的管道。所以需要分別捕捉。