Java中在阻塞调用系统命令的时候,一般是使用Runtime.getRuntime().exec(command)返回一个process对象,再调用Process.waitFor()来等待命令执行结束,获取执行结果。
然而这样简单的调用也是有坑的,有几个地方需要小心留意。
一个是Runtime.getRuntime().exec(command)这个调用对于可执行文件路径或者参数中有空格等特殊字符的情况不能处理,必须手工对其进行转义。推荐使用ProcessBuilder这个新加入的类来替换Runtime.getRuntime().exec,可以自动处理特殊字符,并且接口也要更加丰富和方便使用。
然后,即使只是调用了很简单的脚本命令,在调用Process.waitFor()后都有可能发生无休止或者接近于无休止的阻塞,所以在代码中加入超时控制是必须的。但是Process.waitFor()本身并不支持超时时间设置,一个方法是改用非阻塞的Process.exitValue()方法,然后轮询检查进程状态,这种方式比较消耗CPU,以至于轮询间隔也不能设置得太小,总归不是很完美。另外就是另起一个线程来调用程序,在主线程中发现超时的时候,直接调用process.destroy()终止进程。
还有就是在捕获程序输出的问题。如果要读取程序的删除结果,一定要在调用Process.waitFor()前将程序的stdout和stderr都读完,否则就有可能因为pipe的缓冲区不够,被调用的系统命令阻塞在标准输出和标准错误输出上。Windows因为这个缓冲区的默认值比较小更容易出现这个问题。
需要注意读取程序的stdout和stderr都是阻塞的操作,这意味着必须在两个线程里分别读取,而不是在一个线程里一次读取,否则还是有可能出现阻塞的情况:比如先读取stdout再读取stderr,如果程序的stderr输出已经填满了缓冲区,程序就会阻塞不继续执行,但是java线程又阻塞在读取stdout上,只有stdout结束了才会去读取stderr。结果就是互相等待着的过程中哦给你程序卡死了。
为了不让程序变得过于复杂,我们可以把程序的stderr重定向到stdout中,这样只要读取stdout一个就好了。缺点就是没有区分出错的输出信息和正常的输出信息。
最后使用的代码如下:
/**
* 一个进程调用工具.
*
* @author dongliu
*
*/
public
class
ProcessUtils
{
/**
* 运行一个外部命令,返回状态.若超过指定的超时时间,抛出TimeoutException
*
*/
public
static
ProcessStatus
execute
(
final
long
timeout
,
final
String
...
command
)
throws
IOException
,
InterruptedException
,
TimeoutException
{
ProcessBuilder
pb
=
new
ProcessBuilder
(
command
);
pb
.
redirectErrorStream
(
true
);
Process
process
=
pb
.
start
();
Worker
worker
=
new
Worker
(
process
);
worker
.
start
();
ProcessStatus
ps
=
worker
.
getProcessStatus
();
try
{
worker
.
join
(
timeout
);
if
(
ps
.
exitCode
==
ProcessStatus
.
CODE_STARTED
)
{
// not finished
worker
.
interrupt
();
throw
new
TimeoutException
();
}
else
{
return
ps
;
}
}
catch
(
InterruptedException
e
)
{
// canceled by other thread.
worker
.
interrupt
();
throw
e
;
}
finally
{
process
.
destroy
();
}
}
private
static
class
Worker
extends
Thread
{
private
final
Process
process
;
private
ProcessStatus
ps
;
private
Worker
(
Process
process
)
{
this
.
process
=
process
;
this
.
ps
=
new
ProcessStatus
();
}
public
void
run
()
{
try
{
InputStream
is
=
process
.
getInputStream
();
try
{
ps
.
output
=
Utils
.
toString
(
is
);
}
catch
(
IOException
ignore
)
{
}
ps
.
exitCode
=
process
.
waitFor
();
}
catch
(
InterruptedException
e
)
{
Thread
.
currentThread
().
interrupt
();
}
}
public
ProcessStatus
getProcessStatus
()
{
return
this
.
ps
;
}
}
public
static
class
ProcessStatus
{
public
static
final
int
CODE_STARTED
=
-
257
;
public
volatile
int
exitCode
;
public
volatile
String
output
;
}
}
希望这个方法有用,在调一个把word转成pdf pdf转成swf的项目。。
在晚上找的 ,还没有测试 一会回家测试看看。