重要参考文章(强力推荐,使用Process类调用外部程序必看的文章):
http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
这篇文章讲的大概是Runtime.getRuntime()调用外部程序可能潜在的问题并给出如何解决的方法,逐步推进,是一篇不错的文章。
我的使用场景是在java中调用/bin/sh执行一些命令,并获取命令的执行结果。
Process p=Runtime.getRuntime().exec(String[] cmdArr);
因为我想要执行一些文本处理,涉及到多个程序,不可避免的需要管道操作,所以选择了上面的形式让/bin/sh代理执行管道功能。
不妨把p看成一个Java创建的与执行命令的外部进程(由/bin/sh创建)进行交互的进程:
|----------------| |-------------
p.getInputStream()-------| Java创建的 |<-----Normal Output---| /bin/sh创建的 |
| 交互进程P | | 外部进程 |
p.getErrorStream()------ | |<------Error Output---| |
|----------------| |-------------|
从上图可以看到p的inputStream其实就是外部进程的标准输出,p的errorStream对应外部进程的标准错误。
引文建议使用两个同时处理进程P的inputStream和errorStream,这是必要的。
我第一次使用时,并没有同时处理,而是先处理错误输出,如果错误输出不为空,就直接结束。
否则再处理标准输出。
------这种做法会出现阻塞的现象---当进程P的输出很大时---不知道是怎么回事,据引文的说法,似乎是
不采用同时读时,你无法判断哪个流的数据最先开始,所以可能出现无法读取,一直阻塞的现象。
然而,当你采用同时读取的方法时,也有一些要注意的,而这在引文中并没有提到:
以下是引文给出的做法:
Listing 4.7 GoodWinRedirect.java
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();
}
}
}
Listing 4.8 TestExec.java
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();
}
}
}
使用这种方法,有时候会出现异常----提示流已经被关闭了,
BufferedReader br = new BufferedReader(isr);
String line=null;
//流被关闭,无法读取,这里会抛出异常
while ( (line = br.readLine()) != null)
{
if (pw != null)
pw.println(line);
System.out.println(type + ">" + line);
}
为什么呢?
我们可以想到,因为进程P相当于给我们创建了两个管道,但是对于管道的读取,作读取的线程是不知道流什么时候关闭的,当进程P关闭管道后,线程仍然在读取,所以会抛出异常。
合理的方法是主线程等待两个读取线程的结束。
即是在
// kick them off
errorGobbler.start();
outputGobbler.start();
// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
//加入
errorGobbler.join();
outputGobbler.join();
int exitVal = proc.waitFor();//这句只是等待进程P的结束,而此时两个线程仍然在读取的,如果就此跳过,可能会抛出异常
Java调用shell参考文章
最新推荐文章于 2024-10-10 09:56:47 发布