在编写Java程序时,有时候需要在Java程序中执行另外一个程序。
Java提供了两种方法用来启动其它程序:
1. 使用Runtime的exec()方法
2. 使用ProcessBuilder的start()方法
不管在哪种操作系统下,程序具有基本类似的一些属性。一个程序启动后就是程序操作系统的一个进程,进程在执行的时候有自己的环境变量、工作目录。
能够在Java中执行的外部程序,必须是一个实际存在的可执行文件,对于cmd/shell下的内嵌命令是不能直接执行的,参见#执行shell中的命令示例#小节。
采用Runtime的exec执行程序时,首先要使用java.lang.Runtime#getRuntime得到一个Runtime实例,然后调用Runtime的exec方法,该方法执行后返回一个Process实例,代表所执行的程序。
Runtime提供了多个重载的exec方法,其余的方法都是调用如下核心方法实现的:
public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
cmdarray:包含要调用的命令及其参数的数组。
envp:环境变量,其中每个元素的环境变量设置格式为name = value,如果子进程应该继承当前进程的环境,则为null。
dir:子进程的工作目录,如果子进程应该继承当前进程的工作目录,则为null。
也可以使用ProcessBuilder类启动一个新的程序,该类是在JDK1.5中新增的,而且被推荐使用。
通过构造函数设置要执行的命令以及参数,或者也可以通过java.lang.ProcessBuilder#command方法获取命令信息后在进行设置。
通过java.lang.ProcessBuilder#directory(java.io.File)方法设置工作目录。
在使用ProcessBuilder构造函数创建一个新实例,设置环境变量、工作目录后,可以通过java.lang.ProcessBuilder#start方法来启动新程序,与Runtime的exec方法一样,该方法返回一个Process实例,代表启动的程序。
ProcessBuilder与Runtime.exec方法的不同在于ProcessBuilder提供了java.lang.ProcessBuilder#redirectErrorStream(boolean)方法,该方法用来将进程的错误输出重定向到标准输出里。即可将进程的将错误输出都将与标准输出合并。
Process
不管通过那种方法启动进程后,都会返回一个Process抽象类的实例代表启动的进程,该实例可用来控制进程并获得相关信息。
Process类是一个抽象类(所有的方法均是抽象的),封装了一个进程(即一个执行程序)。
Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法:
public abstract void destroy();
杀掉子进程。一般情况下,该方法并不能杀掉已经启动的进程,不用为好。public abstract int exitValue();
返回子进程的出口值。只有启动的进程执行完成、或者由于异常退出后,exitValue方法才会有正常的返回值,否则抛出异常。public abstract InputStream getErrorStream();
获取子进程的错误输出流。如果错误输出被重定向,则不能从该流中读取错误输出。public abstract InputStream getInputStream();
可以从该流中读取子进程的标准输出。public abstract OutputStream getOutputStream();
写入到该流中的数据作为子进程的标准输入。public abstract int waitFor() throws InterruptedException;
public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException
导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。通过该类提供的方法,可以实现与启动的进程之间通信,达到交互的目的。
1.从标准输出和错误输出流读取信息
从启动其他进程的Java进程看,已启动的其他进程的输出就是一个普通的输入流,可以通过getInputStream和getErrorStream来获取。对于一般输出文本的进程来说,可以将InputStream封装成BufferedReader,然后就可以一行一行的对进程的标准输出进行处理。
举例
通过Runtime实现
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
class ProcessTest1 {
public static void main(String[] args) {
try {
String line = null;
//list the files and directorys under C:\
Process p = Runtime.getRuntime().exec("CMD.exe /C dir", null, new File("C:\\"));
BufferedReader stdout = new BufferedReader(new InputStreamReader(p.getInputStream(), "GBK"));
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();
}
}
}
通过ProcessBuilder实现
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class ProcessTest2 {
public static void main(String[] args) {
try {
String line = null;
BufferedReader stdout = null;
List list = new ArrayList();
//list the files and directorys under C:\
list.add("CMD.EXE");
list.add("/C");
list.add("dir");
ProcessBuilder pb = new ProcessBuilder(list);
pb.directory(new File("C:\\"));
Process p = pb.start();
stdout = new BufferedReader(new InputStreamReader(p.getInputStream(),"GBK"));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();
//echo the value of NAME
pb = new ProcessBuilder();
pbmand(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();
}
}
}
2.获取进程的返回值
通常,一个程序/进程在执行结束后会向操作系统返回一个整数值,0一般代表执行成功,非0表示执行出现问题。
有两种方式可以用来获取进程的返回值:
一是利用waitFor(),该方法是阻塞的,直到进程执行完成后再返回。该方法返回一个代表进程返回值的整数值;
另一个方法是调用exitValue()方法,该方法是非阻塞的,调用立即返回。但是如果进程没有执行完成,则抛出异常。
3.阻塞的问题
由Process代表的进程在某些平台上有时候并不能很好的工作,特别是在对代表进程的标准输入流、输出流和错误输出流进行操作时,如果使用不慎,有可能导致进程阻塞,甚至死锁。
如果将上述示例中的从标准输出流中读取信息的语句修改为从错误输出流中读取:
stdout = new BufferedReader(new InputStreamReader(p.getErrorStream()));
那么程序将发生阻塞,不能执行完成,而是hang在那里。
当进程启动后,就会打开标准输出流和错误输出流准备输出,当进程结束时,就会关闭他们。在以上例子中,错误输出流没有数据要输出,标准输出流中有数据输出。由于标准输出流中的数据没有被读取,进程就不会结束,错误输出流也就不会被关闭,因此在调用readLine()方法时,整个程序就会被阻塞。为了解决这个问题,可以根据输出的实际先后,先读取标准输出流,然后读取错误输出流。
但是,很多时候不能很明确的知道输出的先后,特别是要操作标准输入的时候,情况就会更为复杂。这时候可以采用线程来对标准输出、错误输出和标准输入进行分别处理,根据他们之间在业务逻辑上的关系决定读取哪个流或者写入数据。
针对标准输出流和错误输出流所造成的问题,可以使用java.lang.ProcessBuilder#redirectErrorStream(boolean)方法将他们合二为一,这时候只要读取标准输出的数据就可以了。
当在程序中使用Process的waitFor()方法时,特别是在读取之前调用waitFor()方法时,也有可能造成阻塞。可以用线程的方法来解决这个问题,也可以在读取数据后,调用waitFor()方法等待程序结束。
总之,解决阻塞的方法应该有两种:
(1)使用ProcessBuilder类,利用java.lang.ProcessBuilder#redirectErrorStream(boolean)方法将标准输出流和错误输出流合二为一,在用start()方法启动进程后,先从标准输出中读取数据,然后调用waitFor()方法等待进程结束。
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class ProcessTest3 {
public static void main(String[] args) {
try {
String line = null;
List list = new ArrayList();
//list the files and directorys under C:\
list.add("CMD.EXE");
list.add("/C");
list.add("dir");
ProcessBuilder pb = new ProcessBuilder(list);
pb.directory(new File("C:\\"));
//合并错误输出流和标准输出流
pb.redirectErrorStream(true);
Process p = pb.start();
//read the standard output
BufferedReader 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.io.*;
import java.util.ArrayList;
import java.util.List;
public class StreamWatch extends Thread {
InputStream is;
String type;
List<String> 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;
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;
}
}
import java.util.*;
import java.io.*;
public class ProcessTest4 {
public static void main(String args[]) {
try {
List<String> 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();
}
}
}
4.在Java中执行Java程序
执行一个Java程序的关键在于:
(1)知道JAVA虚拟机的位置,即java.exe或者java的路径
(2)知道要执行的java程序的位置
(3)知道该程序所依赖的其他类的位置
举一个例子,一目了然。
待执行的Java类
/**
* 被其他Java程序执行的Java类
*/
public class ProcessJavaTestEd {
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]);
}
}
}
执行该类的程序
import java.util.*;
import java.io.*;
public class ProcessJavaTest {
public static void main(String args[]) {
try {
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<String> list = new ArrayList();
list.add(java);
list.add("-classpath");
list.add(classpath);
list.add(ProcessJavaTestEd.class.getName());
list.add("hello");
list.add("world");
list.add("good better best");
ProcessBuilder pb = new ProcessBuilder(list);
Process p = pb.start();
System.out.println(pbmand());
// 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();
}
}
}
5.执行shell中的命令示例
是无法直接执行shell中的命令的,如果直接执行ls /home命令会报‘java.io.IOException: Cannot run program "ls /home": error=2, No such file or directory’的错误,参见如下代码示例。
public class ProcessTest3 {
public static void main(String[] args) {
try {
String line;
ProcessBuilder pb = new ProcessBuilder();
/*
* pbmand("ls /home");
* 如上执行报错:java.io.IOException: Cannot run program "ls /home": error=2, No such file or directory
*
* pbmand("bash", "-c", "ls ", "/home");
* 如上执行,查看的不是/home路径下的文件,而是当前运行目录下的文件
* */
pbmand("bash", "-c", "ls /home");
//merge the error output with the standard output
pb.redirectErrorStream(true);
Process p = pb.start();
//read the standard output
BufferedReader 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();
}
}
}
更多推荐
Process
发布评论