在上一篇(
Java动态编译(一))中我们提到了动态编译的三种方法,在这篇文章中讲解一些扩展的知识。
public abstract class Process extends Object
ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获得相关信息。
子进程没有自己的终端或控制台。它的所有标准io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。
waitFor() 导致当前线程等待,如有必要,一直要等到由该Process对象表示的进程已经终止。但是如果我们在调用此方法时,如果不注意的话,很容易出现主线程阻塞,Process也挂起的情况。在调用waitFor() 的时候,Process需要向主线程汇报运行状况,所以要注意清空缓存区,即InputStream和ErrorStream,在网上,很多只提到处理InputStream,忽略了ErrorStream。
举例1.
public class RuntimeExecDemo {
public static void main(String[] args) throws InterruptedException {
Runtime runtime = Runtime.getRuntime();
Process process = null;
try {
process = runtime.exec("cmd /c dir", null, new File("g:/"));
} catch (IOException e) {
e.printStackTrace();
}
int exitVal = process.waitFor();
System.out.println("Process exitValue: " + exitVal);
}
}
上面的程序并没有按照我们预想的那样列出g盘下的目录,而是挂起了,为什么会出现这种情况呢?java文档上说,创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为
有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。
当进程启动后,就会打开标准输出流和错误输出流准备输出,当进程结束时,就会关闭他们。在
举例1中,由于标准输出流有数据需要输出,但是我们却没有读取其中的数据,标准输出流就会一直等待数据被读取,程序挂起。
举例2.处理标准输出流
public class RuntimeExecDemo {
public static void main(String[] args) throws InterruptedException {
Runtime runtime = Runtime.getRuntime();
Process process = null;
try {
process = runtime.exec("cmd /c dir", null, new File("g:/"));
} catch (IOException e) {
e.printStackTrace();
}
try {
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
int exitVal = process.waitFor();
System.out.println("Process exitValue: " + exitVal);
}
}
当我们处理了标准输出后,则程序可以正常运行了。但是,如果我们的命令是错误的,那么情况又会如何呢?即这里将dir修改成dira,然后执行。程序仍然可以正常运行,只不过结束的时候的exitVal不再是0。
为什么会出现这种情况呢?这里就是当我们执行程序的时候,先打开的是标准输出流,然后等待我们从中读取数据,而我们对需要读取的数据做了读取处理。那么当接下来打开错误输出流的时候,尽管没有被读取,但是却不再阻塞,程序异常终止。
但是,如果命令是正确的(即dira已经改回dir)再将process.getInputStream()修改成process.getErrorStream()呢?这次程序又一起挂起了。这是由于标准输出流是先打开并需要输出而,但是却并没有被读取;读取的是错误输出流,那么此时标准输出流的数据没有被读取导致程序不会结束。
为了解决这个问题,可以根据输出的实际先后,先读取标准输出流,然后读取错误输出流。
但是,很多时候不能很明确的知道输出的先后,特别是要操作标准输入的时候,情况就会更为复杂。这时候可以
采用线程来对标准输出、错误输出和标准输入进行分别处理,根据他们之间在业务逻辑上的关系决定读取那个流或者写入数据。
针对标准输出流和错误输出流所造成的问题,还可以
使用ProcessBuilder的redirectErrorStream()方法将他们合二为一,这时候只要读取标准输出的数据就可以了。
举例3.使用多线程
public class RuntimeExecDemo {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
Process process = null;
try {
process = runtime.exec("cmd /c dir", null, new File("g:/"));
} catch (IOException e) {
e.printStackTrace();
}
InputStream is = process.getInputStream();
InputStream es = process.getErrorStream();
StringBuffer out = new StringBuffer(128);
StreamGobbler isGobbler = new StreamGobbler(is, "gbk", out);
StringBuffer err = new StringBuffer(128);
StreamGobbler errorGobbler = new StreamGobbler(es, "gbk", err);
isGobbler.start();
errorGobbler.start();
int exitVal = -1;
try {
exitVal = process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("out : \r\n" + out.toString());
System.out.println("err : \r\n" + err.toString());
System.out.println("Process exitValue: " + exitVal);
}
}
class StreamGobbler extends Thread {
InputStream is;
String encoding = "utf-8";
StringBuffer result = new StringBuffer();
/**
*
* @param is process.getErrorStream() or process.getInputStream()
* @param encoding default is "utf-8", if the result include chinese, need to use "gbk"
*/
StreamGobbler(InputStream is, String encoding, StringBuffer result) {
this.is = is;
this.encoding = encoding;
this.result = result;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is, encoding);
BufferedReader br = new BufferedReader(isr);
String line=null;
while ( (line = br.readLine()) != null) {
result.append(line).append("\r\n");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
举例4.是用ProcessBuilder.redirectErrorStream()
public boolean redirectErrorStream()
通知进程生成器是否合并标准错误和标准输出。
如果此属性为 true,则任何由通过此对象的 start() 方法启动的后续子进程生成的错误输出都将与标准输出合并,因此两者均可使用 Process.getInputStream() 方法读取。这使得关联错误消息和相应的输出变得更容易。初始值为 false。
public class ProcessBuilderDemo {
public static void main(String[] args) {
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "dira");
pb.directory(new File("g:\\"));
pb.redirectErrorStream(true);
Process process = null;
try {
process = pb.start();
} catch (IOException e1) {
e1.printStackTrace();
}
try {
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
int exitVal = -1;
try {
exitVal = process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Process exitValue: " + exitVal);
}
}
<<OVER>>