一、英文原地址
http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html
二、差不多意思的英文翻譯
那就首先說點Runtime類吧,他是一個與JVM運行時環境有關的類,這個類是Singleton的。我說幾個自己覺得重要的地方。
1、Runtime.getRuntime()可以取得當前JVM的運行時環境,這也是在Java中唯一一個得到運行時環境的方法。
2、Runtime上其他大部分的方法都是實例方法,也就是說每次進行運行時調用時都要用到getRuntime方法。
3、Runtime中的exit方法是退出當前JVM的方法,估計也是唯一的一個吧,因爲我看到System類中的exit實際上也是通過調用Runtime.exit()來退出JVM的,這裏說明一下Java對Runtime返回值的一般規則(後邊也提到了),0代表正常退出,非0代表異常中止,這只是Java的規則,在各個操作系統中總會發生一些小的混淆。
4、Runtime.addShutdownHook()方法可以註冊一個hook在JVM執行shutdown的過程中,方法的參數只要是一個初始化過但是沒有執行的Thread實例就可以。(注意,Java中的Thread都是執行過了就不值錢的哦)
5、說到addShutdownHook這個方法就要說一下JVM運行環境是在什麼情況下shutdown或者abort的。文檔上是這樣寫的,當最後一個非精靈進程退出或者收到了一個用戶中斷信號、用戶登出、系統shutdown、Runtime的exit方法被調用時JVM會啓動shutdown的過程,在這個過程開始後,他會並行啓動所有登記的shutdown hook(注意是並行啓動,這就需要線程安全和防止死鎖)。當shutdown過程啓動後,只有通過調用halt方法才能中止shutdown的過程並退出JVM。
那什麼時候JVM會abort退出那?首先說明一下,abort退出時JVM就是停止運行但並不一定進行shutdown。這隻有JVM在遇到SIGKILL信號或者windows中止進程的信號、本地方法發生類似於訪問非法地址一類的內部錯誤時會出現。這種情況下並不能保證shutdown hook是否被執行。
現在開始看這篇文章,呵呵。
首先講的是Runtime.exec()方法的所有重載。這裏要注意的有一點,就是public Process exec(String [] cmdArray, String [] envp);這個方法中cmdArray是一個執行的命令和參數的字符串數組,數組的第一個元素是要執行的命令往後依次都是命令的參數,envp我個人感覺應該和C中的execve中的環境變量是一樣的,envp中使用的是name=value的方式。
1、 一個很糟糕的調用程序,代碼如下,這個程序用exec調用了一個外部命令之後馬上使用exitValue就對其返回值進行檢查,讓我們看看會出現什麼問題。
import java.util.*;
import java.io.*;
public class BadExecJavac
{
public static void main(String args[])
{
try
{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("javac");
int exitVal = proc.exitValue();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
A run of BadExecJavac produces:
E:classescomjavaworldjpitfallsarticle2>java BadExecJavac
java.lang.IllegalThreadStateException: process has not exited
at java.lang.Win32Process.exitValue(Native Method)
at BadExecJavac.main(BadExecJavac.java:13)
這裏看原文就可以瞭解,這裏主要的問題就是錯誤的調用了exitValue來取得外部命令的返回值(呵呵,這個錯誤我也曾經犯過),因爲exitValue這個方法是不阻塞的,程序在調用這個方法時外部命令並沒有返回所以造成了異常的出現,這裏是由另外的方法來等待外部命令執行完畢的,就是waitFor方法,這個方法會一直阻塞直到外部命令執行結束,然後返回外部命令執行的結果,作者在這裏一頓批評設計者的思路有問題,呵呵,反正我是無所謂阿,能用就可以拉。但是作者在這裏有一個說明,就是exitValue也是有好多用途的。因爲當你在一個Process上調用waitFor方法時,當前線程是阻塞的,如果外部命令無法執行結束,那麼你的線程就會一直阻塞下去,這種意外會影響我們程序的執行。所以在我們不能判斷外部命令什麼時候執行完畢而我們的程序還需要繼續執行的情況下,我們就應該循環的使用exitValue來取得外部命令的返回狀態,並在外部命令返回時作出相應的處理。
2、對exitValue處改進了的程序
import java.util.*;
import java.io.*;
public class BadExecJavac2
{
public static void main(String args[])
{
try
{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("javac");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
不幸的是,這個程序也無法執行完成,它沒有輸出但卻一直懸在那裏,這是爲什麼那?
JDK文檔中對此有如此的解釋:因爲本地的系統對標準輸入和輸出所提供的緩衝池有效,所以錯誤的對標準輸出快速的寫入和從標準輸入快速的讀入都有可能造成子進程的鎖,甚至死鎖。
文檔引述完了,作者又開始批評了,他說JDK僅僅說明爲什麼問題會發生,卻並沒有說明這個問題怎麼解決,這的確是個問題哈。緊接着作者說出自己的做法,就是在執行完外部命令後我們要控制好Process的所有輸入和輸出(視情況而定),在這個例子裏邊因爲調用的是Javac,而他在沒有參數的情況下會將提示信息輸出到標準出錯,所以在下面的程序中我們要對此進行處理。
import java.util.*;
import java.io.*;
public class MediocreExecJavac
{
public static void main(String args[])
{
try
{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("javac");
InputStream stderr = proc.getErrorStream();
InputStreamReader isr = new InputStreamReader(stderr);
BufferedReader br = new BufferedReader(isr);
String line = null;
System.out.println("<error></error>");
while ( (line = br.readLine()) != null)
System.out.println(line);
System.out.println("");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
程序的運行結果爲
E:classescomjavaworldjpitfallsarticle2>java MediocreExecJavac
<error></error>
Usage: javac <options></options> <source files=""></source>
where <options></options> includes:
-g Generate all debugging info
-g:none Generate no debugging info
-g:{lines,vars,source} Generate only some debugging info
-O Optimize; may hinder debugging or enlarge class files
-nowarn Generate no warnings
-verbose Output messages about what the compiler is doing
-deprecation Output source locations where deprecated APIs are used
-classpath Specify where to find user class files
-sourcepath Specify where to find input source files
-bootclasspath Override location of bootstrap class files
-extdirs <dirs></dirs>Override location of installed extensions
-d <directory></directory>Specify where to place generated class files
-encoding <encoding></encoding>Specify character encoding used by source files
-target <release></release>Generate class files for specific VM version
Process exitValue: 2
哎,不管怎麼說還是出來了結果,作者作了一下總結,就是說,爲了處理好外部命令大量輸出的情況,你要確保你的程序處理好外部命令所需要的輸入或者輸出。
下一個題目,當我們調用一個我們認爲是可執行程序的時候容易發生的錯誤(今天晚上我剛剛犯這個錯誤,沒事做這個練習時候發生的)
import java.util.*;
import java.io.*;
public class BadExecWinDir
{
public static void main(String args[])
{
try
{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("dir");
InputStream stdin = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdin);
BufferedReader br = new BufferedReader(isr);
String line = null;
System.out.println("<output></output>");
while ( (line = br.readLine()) != null)
System.out.println(line);
System.out.println("");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
A run of BadExecWinDir produces:
E:classescomjavaworldjpitfallsarticle2>java BadExecWinDir
java.io.IOException: CreateProcess: dir error=2
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.<init></init>(Unknown Source)
at java.lang.Runtime.execInternal(Native Method)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at BadExecWinDir.main(BadExecWinDir.java:12)
說實在的,這個錯誤還真是讓我摸不着頭腦,我覺得在windows中返回2應該是沒有找到這個文件的緣故,可能windows 2000中只有cmd命令,dir命令不是當前環境變量能夠解釋的吧。我也不知道了,慢慢往下看吧。
嘿,果然和作者想的一樣,就是因爲dir命令是由windows中的解釋器解釋的,直接執行dir時無法找到dir.exe這個命令,所以會出現文件未找到這個2的錯誤。如果我們要執行這樣的命令,就要先根據操作系統的不同執行不同的解釋程序command.com 或者cmd.exe。
作者對上邊的程序進行了修改
import java.util.*;
import java.io.*;
class StreamGobbler extends Thread
{
InputStream is;
String type;
StreamGobbler(InputStream is, String type)
{
this.is = is;
this.type = type;
}
public void run()
{
try
{
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line=null;
while ( (line = br.readLine()) != null)
System.out.println(type + ">" + line);
} catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
public class GoodWindowsExec
{
public static void main(String args[])
{
if (args.length < 1)
{
System.out.println("USAGE: java GoodWindowsExec <cmd></cmd>");
System.exit(1);
}
try
{
String osName = System.getProperty("os.name" );
String[] cmd = new String[3];
if( osName.equals( "Windows NT" ) )
{
cmd[0] = "cmd.exe" ;
cmd[1] = "/C" ;
cmd[2] = args[0];
}
else if( osName.equals( "Windows 95" ) )
{
cmd[0] = "command.com" ;
cmd[1] = "/C" ;
cmd[2] = args[0];
}
Runtime rt = Runtime.getRuntime();
System.out.println("Execing " + cmd[0] + " " + cmd[1]
+ " " + cmd[2]);
Process proc = rt.exec(cmd);
// any error message?
StreamGobbler errorGobbler = new
StreamGobbler(proc.getErrorStream(), "ERROR");
// any output?
StreamGobbler outputGobbler = new
StreamGobbler(proc.getInputStream(), "OUTPUT");
// kick them off
errorGobbler.start();
outputGobbler.start();
// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
Running GoodWindowsExec with the dir command generates:
E:classescomjavaworldjpitfallsarticle2>java GoodWindowsExec "dir *.java"
Execing cmd.exe /C dir *.java
OUTPUT> Volume in drive E has no label.
OUTPUT> Volume Serial Number is 5C5F-0CC9
OUTPUT>
OUTPUT> Directory of E:classescomjavaworldjpitfallsarticle2
OUTPUT>
OUTPUT>10/23/00 09:01p 805 BadExecBrowser.java
OUTPUT>10/22/00 09:35a 770 BadExecBrowser1.java
OUTPUT>10/24/00 08:45p 488 BadExecJavac.java
OUTPUT>10/24/00 08:46p 519 BadExecJavac2.java
OUTPUT>10/24/00 09:13p 930 BadExecWinDir.java
OUTPUT>10/22/00 09:21a 2,282 BadURLPost.java
OUTPUT>10/22/00 09:20a 2,273 BadURLPost1.java
... (some output omitted for brevity)
OUTPUT>10/12/00 09:29p 151 SuperFrame.java
OUTPUT>10/24/00 09:23p 1,814 TestExec.java
OUTPUT>10/09/00 05:47p 23,543 TestStringReplace.java
OUTPUT>10/12/00 08:55p 228 TopLevel.java
OUTPUT> 22 File(s) 46,661 bytes
OUTPUT> 19,678,420,992 bytes free
ExitValue: 0
這裏作者教了一個windows中很有用的方法,呵呵,至少我是不知道的,就是cmd.exe /C +一個windows中註冊了後綴的文檔名,windows會自動地調用相關的程序來打開這個文檔,我試了一下,的確很好用,但是好像文件路徑中有空格的話就有點問題,我加上引號也無法解決。
這裏作者強調了一下,不要假設你執行的程序是可執行的程序,要清楚自己的程序是單獨可執行的還是被解釋的,本章的結束作者會介紹一個命令行工具來幫助我們分析。
這裏還有一點,就是得到process的輸出的方式是getInputStream,這是因爲我們要從Java 程序的角度來看,外部程序的輸出對於Java來說就是輸入,反之亦然。
最後的一個漏洞的地方就是錯誤的認爲exec方法會接受所有你在命令行或者Shell中輸入並接受的字符串。這些錯誤主要出現在命令作爲參數的情況下,程序員錯誤的將所有命令行中可以輸入的參數命令加入到exec中(這段翻譯的不好,湊合看吧)。下面的例子中就是一個程序員想重定向一個命令的輸出。
import java.util.*;
import java.io.*;
// StreamGobbler omitted for brevity
public class BadWinRedirect
{
public static void main(String args[])
{
try
{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("java jecho 'Hello World' > test.txt");
// any error message?
StreamGobbler errorGobbler = new
StreamGobbler(proc.getErrorStream(), "ERROR");
// any output?
StreamGobbler outputGobbler = new
StreamGobbler(proc.getInputStream(), "OUTPUT");
// kick them off
errorGobbler.start();
outputGobbler.start();
// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
Running BadWinRedirect produces:
E:classescomjavaworldjpitfallsarticle2>java BadWinRedirect
OUTPUT>'Hello World' > test.txt
ExitValue: 0
程序員的本意是將Hello World這個輸入重訂向到一個文本文件中,但是這個文件並沒有生成,jecho僅僅是將命令行中的參數輸出到標準輸出中,用戶覺得可以像dos中重定向一樣將輸出重定向到一個文件中,但這並不能實現,用戶錯誤的將exec認爲是一個shell解釋器,但它並不是,如果你想將一個程序的輸出重定向到其他的程序中,你必須用程序來實現他。可用java.io中的包。
import java.util.*;
import java.io.*;
class StreamGobbler extends Thread
{
InputStream is;
String type;
OutputStream os;
StreamGobbler(InputStream is, String type)
{
this(is, type, null);
}
StreamGobbler(InputStream is, String type, OutputStream redirect)
{
this.is = is;
this.type = type;
this.os = redirect;
}
public void run()
{
try
{
PrintWriter pw = null;
if (os != null)
pw = new PrintWriter(os);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line=null;
while ( (line = br.readLine()) != null)
{
if (pw != null)
pw.println(line);
System.out.println(type + ">" + line);
}
if (pw != null)
pw.flush();
} catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
public class GoodWinRedirect
{
public static void main(String args[])
{
if (args.length < 1)
{
System.out.println("USAGE java GoodWinRedirect <outputfile></outputfile>");
System.exit(1);
}
try
{
FileOutputStream fos = new FileOutputStream(args[0]);
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("java jecho 'Hello World'");
// any error message?
StreamGobbler errorGobbler = new
StreamGobbler(proc.getErrorStream(), "ERROR");
// any output?
StreamGobbler outputGobbler = new
StreamGobbler(proc.getInputStream(), "OUTPUT", fos);
// kick them off
errorGobbler.start();
outputGobbler.start();
// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
fos.flush();
fos.close();
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
Running GoodWinRedirect produces:
E:classescomjavaworldjpitfallsarticle2>java GoodWinRedirect test.txt
OUTPUT>'Hello World'
ExitValue: 0
這裏就不多說了,看看就明白,緊接着作者給出了一個監測命令的小程序
import java.util.*;
import java.io.*;
// class StreamGobbler omitted for brevity
public class TestExec
{
public static void main(String args[])
{
if (args.length < 1)
{
System.out.println("USAGE: java TestExec "cmd"");
System.exit(1);
}
try
{
String cmd = args[0];
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(cmd);
// any error message?
StreamGobbler errorGobbler = new
StreamGobbler(proc.getErrorStream(), "ERR");
// any output?
StreamGobbler outputGobbler = new
StreamGobbler(proc.getInputStream(), "OUT");
// kick them off
errorGobbler.start();
outputGobbler.start();
// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
對這個程序進行運行:
E:classescomjavaworldjpitfallsarticle2>java TestExec "e:javadocsindex.html"
java.io.IOException: CreateProcess: e:javadocsindex.html error=193
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.<init></init>(Unknown Source)
at java.lang.Runtime.execInternal(Native Method)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at TestExec.main(TestExec.java:45)
193在windows中是說這不是一個win32程序,這說明路徑中找不到這個網頁的關聯程序,下面作者決定用一個絕對路徑來試一下。
E:classescomjavaworldjpitfallsarticle2>java TestExec
"e:program filesnetscapeprogramnetscape.exe e:javadocsindex.html"
ExitValue: 0
好用了,這個我也試了一下,用的是IE。
最後,作者總結了幾條規則,防止我們在進行Runtime.exec()調用時出現錯誤。
1、 在一個外部進程執行完之前你不能得到他的退出狀態
2、 在你的外部程序開始執行的時候你必須馬上控制輸入、輸出、出錯這些流。
3、 你必須用Runtime.exec()去執行程序
4、 你不能象命令行一樣使用Runtime.exec()。
三、另一篇講到Runtime.exec相關的
在編寫Java程序時,有時候需要在Java程序中執行另外一個程序。
1、啓動程序Java提供了兩種方法用來啓動其它程序:
(1)使用Runtime的exec()方法
(2)使用ProcessBuilder的start()方法
不管在哪種操作系統下,程序具有基本類似的一些屬性。一個程序啓動後就程序操作系統的一個進程,進程在執行的時候有自己的環境變量、有自己的工作目錄。Runtime和ProcessBuilder提供了不同的方式來啓動程序,設置啓動參數、環境變量和工作目錄。
能夠在Java中執行的外部程序,必須是一個實際存在的可執行文件,對於shell下的內嵌命令是不能直接執行的。
採用Runtime的exec執行程序時,首先要使用Runtime的靜態方法得到一個Runtime,然後調用Runtime的exec方法。可以將要執行的外部程序和啓動參數、環境變量、工作目錄作爲參數傳遞給exec方法,該方法執行後返回一個Process代表所執行的程序。
Runtime有六個exec方法,其中兩個的定義爲:
public Process exec(String[] cmdarray, String[] envp, File dir)
public Process exec(String command, String[] envp, File dir)
cmdarray和command爲要執行的命令,可以將命令和參數作爲一個字符串command傳遞給exec()方法,也可以將命令和參數一個一個的方在數組cmdarray裏傳遞給exec()方法。
envp爲環境變量,以name=value的形式放在數組中。dir爲工作目錄。
可以不要dir參數,或者不要envp和dir參數,這樣就多出了其它4個exec()方法。如果沒有dir參數或者爲null,那麼新啓動的進程就繼承當前java進程的工作目錄。如果沒有envp參數或者爲null,那麼新啓動的進程就繼承當前java進程的環境變量。
也可以使用ProcessBuilder類啓動一個新的程序,該類是後來添加到JDK中的,而且被推薦使用。通過構造函數設置要執行的命令以及參數,或者也可以通過command()方法獲取命令信息後在進行設置。通過directory(File directory)方法設置工作目錄,通過environment()獲取環境變量信息來修改環境變量。
在使用ProcessBuilder構造函數創建一個新實例,設置環境變量、工作目錄後,可以通過start()方法來啓動新程序,與Runtime的exec()方法一樣,該方法返回一個Process對象代表啓動的程序。
ProcessBuilder與Runtime.exec()方法的不同在於ProcessBuilder提供了 redirectErrorStream(boolean redirectErrorStream)方法,該方法用來將進程的錯誤輸出重定向到標準輸出裏。即可以將錯誤輸出都將與標準輸出合併。
2、Process
不管通過那種方法啓動進程後,都會返回一個Process類的實例代表啓動的進程,該實例可用來控制進程並獲得相關信息。Process 類提供了執行從進程輸入、執行輸出到進程、等待進程完成、檢查進程的退出狀態以及銷燬(殺掉)進程的方法:
(1) void destroy()
殺掉子進程。
一般情況下,該方法並不能殺掉已經啓動的進程,不用爲好。
(2) int exitValue()
返回子進程的出口值。
只有啓動的進程執行完成、或者由於異常退出後,exitValue()方法纔會有正常的返回值,否則拋出異常。
(3)InputStream getErrorStream()
獲取子進程的錯誤流。
如果錯誤輸出被重定向,則不能從該流中讀取錯誤輸出。
(4)InputStream getInputStream()
獲取子進程的輸入流。
可以從該流中讀取進程的標準輸出。
(5)OutputStream getOutputStream()
獲取子進程的輸出流。
寫入到該流中的數據作爲進程的標準輸入。
(6) int waitFor()
導致當前線程等待,如有必要,一直要等到由該 Process 對象表示的進程已經終止。
通過該類提供的方法,可以實現與啓動的進程之間通信,達到交互的目的。
3、從標準輸出和錯誤輸出流讀取信息
從啓動其他程序的Java進程看,已啓動的其他程序輸出就是一個普通的輸入流,可以通過getInputStream()和getErrorStream來獲取。
對於一般輸出文本的進程來說,可以將InputStream封裝成BufferedReader,然後就可以一行一行的對進程的標準輸出進行處理。
4、舉例
(1)Runtime.exec()
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
public class Test1 {
public static void main(String[] args) {
try {
Process p = null;
String line = null;
BufferedReader stdout = null;
// list the files and directorys under C:\
p = Runtime.getRuntime().exec("CMD.exe /C dir", null,
new File("C:\\"));
stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();
// echo the value of NAME
p = Runtime.getRuntime().exec("CMD.exe /C echo %NAME%",
new String[] { "NAME=TEST" });
stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
(2)ProcessBuilder
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class Test2 {
public static void main(String[] args) {
try {
List list = new ArrayList();
ProcessBuilder pb = null;
Process p = null;
String line = null;
BufferedReader stdout = null;// list the files and directorys
// under C:\
list.add("CMD.EXE");
list.add("/C");
list.add("dir");
pb = new ProcessBuilder(list);
pb.directory(new File("C:\\"));
p = pb.start();
stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();// echo the value of NAME
pb = new ProcessBuilder();
pb.command(new String[] { "CMD.exe", "/C", "echo %NAME%" });
pb.environment().put("NAME", "TEST");
p = pb.start();
stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
5、獲取進程的返回值
通常,一個程序/進程在執行結束後會向操作系統返回一個整數值,0一般代表執行成功,非0表示執行出現問題。有兩種方式可以用來獲取進程的返回值。一是利用waitFor(),該方法是阻塞的,執導進程執行完成後再返回。該方法返回一個代表進程返回值的整數值。另一個方法是調用 exitValue()方法,該方法是非阻塞的,調用立即返回。但是如果進程沒有執行完成,則拋出異常。
6、阻塞的問題
由Process代表的進程在某些平臺上有時候並不能很好的工作,特別是在對代表進程的標準輸入流、輸出流和錯誤輸出進行操作時,如果使用不慎,有可能導致進程阻塞,甚至死鎖。
如果將以上事例中的從標準輸出重讀取信息的語句修改爲從錯誤輸出流中讀取:
stdout = new BufferedReader(new InputStreamReader(p.getErrorStream()));
那麼程序將發生阻塞,不能執行完成,而是hang在那裏。
當進程啓動後,就會打開標準輸出流和錯誤輸出流準備輸出,當進程結束時,就會關閉他們。在以上例子中,錯誤輸出流沒有數據要輸出,標準輸出流中有數據輸出。由於標準輸出流中的數據沒有被讀取,進程就不會結束,錯誤輸出流也就不會被關閉,因此在調用readLine()方法時,整個程序就會被阻塞。爲了解決這個問題,可以根據輸出的實際先後,先讀取標準輸出流,然後讀取錯誤輸出流。
但是,很多時候不能很明確的知道輸出的先後,特別是要操作標準輸入的時候,情況就會更爲複雜。這時候可以採用線程來對標準輸出、錯誤輸出和標準輸入進行分別處理,根據他們之間在業務邏輯上的關係決定讀取那個流或者寫入數據。
針對標準輸出流和錯誤輸出流所造成的問題,可以使用ProcessBuilder的redirectErrorStream()方法將他們合二爲一,這時候只要讀取標準輸出的數據就可以了。
當在程序中使用Process的waitFor()方法時,特別是在讀取之前調用waitFor()方法時,也有可能造成阻塞。可以用線程的方法來解決這個問題,也可以在讀取數據後,調用waitFor()方法等待程序結束。
總之,解決阻塞的方法應該有兩種:
(1)使用ProcessBuilder類,利用redirectErrorStream方法將標準輸出流和錯誤輸出流合二爲一,在用start()方法啓動進程後,先從標準輸出中讀取數據,然後調用waitFor()方法等待進程結束。
如:
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class Test3 {
public static void main(String[] args) {
try {
List list = new ArrayList();
ProcessBuilder pb = null;
Process p = null;
String line = null;
BufferedReader stdout = null;
// list the files and directorys under C:\
list.add("CMD.EXE");
list.add("/C");
list.add("dir");
pb = new ProcessBuilder(list);
pb.directory(new File("C:\\"));
// merge the error output with the standard output
pb.redirectErrorStream(true);
p = pb.start();
// read the standard output
stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
int ret = p.waitFor();
System.out.println("the return code is " + ret);
stdout.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
(2)使用線程
import java.util.*;
import java.io.*;
class StreamWatch extends Thread {
InputStream is;
String type;
List output = new ArrayList();
boolean debug = false;
StreamWatch(InputStream is, String type) {
this(is, type, false);
}
StreamWatch(InputStream is, String type, boolean debug) {
this.is = is;
this.type = type;
this.debug = debug;
}
public void run() {
try {
PrintWriter pw = null;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
output.add(line);
if (debug)
System.out.println(type + ">" + line);
}
if (pw != null)
pw.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
public List getOutput() {
return output;
}
}
public class Test4 {
public static void main(String args[]) {
try {
List list = new ArrayList();
ProcessBuilder pb = null;
Process p = null;
// list the files and directorys under C:\
list.add("CMD.EXE");
list.add("/C");
list.add("dir");
pb = new ProcessBuilder(list);
pb.directory(new File("C:\\"));
p = pb.start();
// process error and output message
StreamWatch errorWatch = new StreamWatch(p.getErrorStream(),
"ERROR");
StreamWatch outputWatch = new StreamWatch(p.getInputStream(),
"OUTPUT");
// start to watch
errorWatch.start();
outputWatch.start();
// wait for exit
int exitVal = p.waitFor();
// print the content from ERROR and OUTPUT
System.out.println("ERROR: " + errorWatch.getOutput());
System.out.println("OUTPUT: " + outputWatch.getOutput());
System.out.println("the return code is " + exitVal);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
7、在Java中執行Java程序
執行一個Java程序的關鍵在於:
(1)知道JAVA虛擬機的位置,即java.exe或者java的路徑
(2)知道要執行的java程序的位置
(3)知道該程序所依賴的其他類的位置
(1)待執行的Java類
public class MyTest {
public static void main(String[] args) {
System.out.println("OUTPUT one");
System.out.println("OUTPUT two");
System.err.println("ERROR 1");
System.err.println("ERROR 2");
for(int i = 0; i < args.length; i++)
{
System.out.printf("args[%d] = %s.", i, args[i]);
}
}
}
(2)執行該類的程序
import java.util.*;
import java.io.*;
class StreamWatch extends Thread {
InputStream is;
String type;
List output = new ArrayList();
boolean debug = false;
StreamWatch(InputStream is, String type) {
this(is, type, false);
}
StreamWatch(InputStream is, String type, boolean debug) {
this.is = is;
this.type = type;
this.debug = debug;
}
public void run() {
try {
PrintWriter pw = null;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
output.add(line);
if (debug)
System.out.println(type + ">" + line);
}
if (pw != null)
pw.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
public List getOutput() {
return output;
}
}
public class Test6 {
public static void main(String args[]) {
try {
List list = new ArrayList();
ProcessBuilder pb = null;
Process p = null;
String java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
String classpath = System.getProperty("java.class.path");
// list the files and directorys under C:\
list.add(java);
list.add("-classpath");
list.add(classpath);
list.add(MyTest.class.getName());
list.add("hello");
list.add("world");
list.add("good better best");
pb = new ProcessBuilder(list);
p = pb.start();
System.out.println(pb.command());
// process error and output message
StreamWatch errorWatch = new StreamWatch(p.getErrorStream(),
"ERROR");
StreamWatch outputWatch = new StreamWatch(p.getInputStream(),
"OUTPUT");
// start to watch
errorWatch.start();
outputWatch.start();
//wait for exit
int exitVal = p.waitFor();
//print the content from ERROR and OUTPUT
System.out.println("ERROR: " + errorWatch.getOutput());
System.out.println("OUTPUT: " + outputWatch.getOutput());
System.out.println("the return code is " + exitVal);
} catch (Throwable t) {
t.printStackTrace();
}
}
}