Java中使用Runtime和Process类运行外部程序
使用Runtime.getRuntime().exec()方法可以在java程序里运行外部程序。
1. exec(String command)
2. exec(String command, String envp[], File dir)
3. exec(String cmd, String envp[])
4. exec(String cmdarray[])
5. exec(String cmdarray[], String envp[])
6. exec(String cmdarray[], String envp[], File dir)
一般的应用程序可以直接使用第一版本,当有环境变量传递的时候使用后面的版本。其中2和6版本可以传递一个目录,标识当前目录,因为有些程序是使用相对目录的,所以就要使用这个版本。
cmd.exe /c start <FileName>
使用DOS命令(比如dir)时也要使用到调用。如果想与调用的程序进行交互,那么就要使用该方法的返回对象Process了,通过Process的getInputStream(),getOutputStream()和getErrorStream()方法可以得到输入输出流,然后通过InputStream可以得到程序对控制台的输出信息,通过OutputStream可以给程序输入指令,这样就达到了程序的交换功能。
用Java编写应用时,有时需要在程序中调用另一个现成的可执行程序或系统命令,这时可以通过组合使用Java提供的Runtime类和Process类的方法实现。下面是一种比较典型的程序模式:
2 Process process = Runtime.getRuntime().exec(".\\p.exe" );
3 process.waitfor();
4
在上面的程序中,第一行的“.\\p.exe”是要执行的程序名,Runtime.getRuntime()返回当前应用程序的Runtime对象,该对象的exec()方法指示Java虚拟机创建一个子进程执行指定的可执行程序,并返回与该子进程对应的Process对象实例。通过Process可以控制该子进程的执行或获取该子进程的信息。第二条语句的目的等待子进程完成再往下执行。
但在windows平台上,如果处理不当,有时并不能得到预期的结果。下面是笔者在实际编程中总结的几种需要注意的情况:
1、执行DOS的内部命令
如果要执行一条DOS内部命令,有两种方法。一种方法是把命令解释器包含在exec()的参数中。例如,执行dir命令,在NT上,可写成exec("cmd.exe /c dir"),在windows95/98下,可写成“command.exe /c dir”,其中参数“/c”表示命令执行后关闭DOS立即关闭窗口。另一种方法是,把内部命令放在一个批命令my_dir.bat文件中,在Java程序中写成exec("my_dir.bat")。如果仅仅写成exec("dir"),Java虚拟机则会报运行时错误。前一种方法要保证程序的可移植性,需要在程序中读取运行的操作系统平台,以调用不同的命令解释器。后一种方法则不需要做更多的处理。
2、打开一个不可执行的文件
打开一个不可执行的文件,但该文件存在关联的应用程序,则可以有两种方式。以打开一个word文档a.doc文件为例,Java中可以有以下两种写法:
2 exec("Files\\Microsoft Office\\office\\winword.exe .\\a.doc");
3、执行一个有标准输出的DOS可执行程序
在Windows平台上,运行被调用程序的DOS窗口在程序执行完毕后往往并不会自动关闭,从而导致Java应用程序阻塞在waitfor()语句。导致该现象的一个可能的原因是,该可执行程序的标准输出比较多,而运行窗口的标准输出缓冲区不够大。解决的办法是,利用Java中Process类提供的方法让Java虚拟机截获被调用程序的DOS运行窗口的标准输出,在waitfor()命令之前读出窗口的标准输出缓冲区中的内容。一段典型的程序如下:
2 String s;
3 Process process = Runtime.getRuntime().exec("cmd /c dir \\windows" );
4 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream());
5 while((s=bufferedReader.readLine()) != null )
6 System.out.println(s);
7 process.waitfor();
Java调用外部程序解决方案
关键字 Java 外部程序 CMD 进程 调用 Process
最近接触一个需求,是利用Java调用本地命令行程序,并希望Java程序能与该命令行程序进行交互,Java对该程序的操作如同在终端中对程序的操纵一样。
在技术调研的过程中,遇到了几个问题:
- 如何Java调用命令行程序
- 如何利用Java向命令行程序的标准输入写入字符流
- 如何利用Java即时地得到命令行程序的标准输出流。
- 如何利用Java即时地得到命令行程序的标准错误流
一、调用命令行程序
这个很简单,Java调用的方法为
Process process = Runtime.getRuntime().exec(“command name”);
Process的JavaDoc地址:http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Process.html
二、标准输出
注意,在这里标准输出指的是Java程序以标准输出的方式发出字节流,这些字节流会以标准输入的方式进入被调用的命令行程序
OutputStream pOutputStream = process.getOutputStream();
PrintWriter outputWriter = new PrintWriter(pOutputStream, true);
outputWriter.print(string);
PrintWriter的第二个构造参数一定要选为true,这样才能自动flush进入外部程序,不然,没有Flush,你向被调用程序所写的输入,只有在下一次缓冲被Flush的时候才能发挥作用,这样,当你的输入很少时,你虽然在代码里print了命令,但是外部程序并没有得到他,就一直阻塞,这是开发者经常会遇到的问题。
三、标准输入和错误输入
private InputStream pErrorStream = process.getErrorStream();
private InputStream pInputStream =process.getInputStream();
这两个输入是用来接受外部程序的反馈的,外部程序通常会向标准终端打印字符,这些字符会通过这两个流得到,经过测试,我们发现一个问题,如果外部程序在输出信息时,没有用flush也会出现问题,比如C语言的程序
scanf(“%d”, &i);
printf(“%d”, i);
这段代码在运行时,虽然在终端里会即时的显示出来,但是却不能及时地发送给pInputStream,这是因为程序输出使用了缓冲机制造成的,所以,这造成的困难是如果你没有外部程序的源码,你就很难将输出即时显示出来,我目前还没有找到解决方案,如果你有源码就好办了,在源码中设置输出为即时flush就好了,我用笨办法来说明:
scanf(“%d”, &i);
printf(“%d”, i);
fflush(stdout);
这样,fflush(stdout)之后,pInputStream就会得到输入了。
四、综合
下面我们用三个线程来进行一个简单的与外部程序的交互过程的设计
线程一、
process.waitFor(),负责建立线程并等待线程结束
线程二、
for (int i = 0; i > -1; i = pInputStream.read(inBuffer)) {
// We have a new segment of input, so process it as a String..
System.out.print(new String(inBuffer, 0, i));
}
负责接收外部程序的输出信息
线程三、
// Read the ErrorStream in a loop until we find no more bytes to read..
for (int i = 0; i > -1; i = pErrorStream.read(errBuffer)) {
// We have a new segment of error, so process it as a String..
Systerm.err.print(new String(errBuffer, 0, i));
}
负责接收外部程序的错误输出信息
在适当的地方,调用outputWriter.print(string);向程序写入字符流。
===========================================================================================================
1 java调用外部程序的方法
在一个java应用中,可能会遇到这样的需求,就是需要调用一些外部的应用做一些处理,比如调用excel,然后在继续程序的运行。
下面就开始进入java调用外部程序的一些演示,让java应用更加灵活。
1:最简单的演示:
Runtime.getRuntime().exec("notepad.exe");
记事本被打开了是吧。
2:传递应用程序的参数:
Runtime runtime=Runtime.getRuntime();
String[] commandArgs={"notepad.exe","c:/boot.ini"};
runtime.exec(commandArgs);
现在不单单打开了记事本,而且还装载了boot.ini文件是吧。
现在已经完全解决了调用外部程序的问题,不是吗,但是大家会发现exec方法是有返回值,那么继续我们的演示吧。
1:Process的waitFor:
Runtime runtime=Runtime.getRuntime();
String[] commandArgs={"notepad.exe","c:/boot.ini"};
Process process=runtime.exec(commandArgs);
int exitcode=process.waitFor();
System.out.println("finish:"+exitcode);
执行上面的代码以后发现不同的地方了吗,waitFor会使线程阻塞,只有外部程序退出后才会执行System.out.println("finish:"+exitcode);
这个功能很有用是吧,因为多数时候你都需要等待用户处理完外部程序以后才继续你的java应用。
2:Process的destroy:
Runtime runtime=Runtime.getRuntime();
String[] commandArgs={"notepad.exe","c:/boot.ini"};
final Process process=runtime.exec(commandArgs);
new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
process.destroy();
}}).start();
int exitcode=process.waitFor();
System.out.println("finish:"+exitcode);
这个演示稍微复杂了一些,如果你等待5秒,就会发现记事本自动关闭了,是的,这个就是destroy方法的作用,强制关闭调用的外部程序。
不用我解释了吧,这是非常有用的方法。
以上的部分已经足够你调用并控制你的外部应用了。如果需要更详细的信息,看javadoc文档吧。
最后的说明:ProcessBuilder这个1.5新增的类也可以完成同样的任务,Runtime就是调用了这个类。
============================================================================================================
Java调用外部程序命令主要用到两个类:
java.lang.Runtime
每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时。应用程序不能创建自己的 Runtime 类实例。
java.lang.Process
ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获取相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。 对于带有 Process 对象的 Java 进程,没有必要异步或并发执行由 Process 对象表示的进程。
下面Java调用Windows命令的例子:
- import java.io.IOException;
- import java.io.BufferedReader;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- /**
- * Created by IntelliJ IDEA.
- * User: leizhimin
- * Date: 2008-7-18
- * Time: 14:18:27
- * Java调用Windows命令测试
- */
- public class TestCmd {
- public static void main(String args[]) {
- testWinCmd();
- dirOpt();
- }
- public static void testWinCmd() {
- System.out.println("------------------testWinCmd()--------------------");
- Runtime runtime = Runtime.getRuntime();
- System.out.println(runtime.totalMemory());
- System.out.println(runtime.freeMemory());
- System.out.println(runtime.maxMemory());
- System.out.println(runtime.availableProcessors()); //处理器数
- try {
- //执行一个exe文件
- runtime.exec("notepad");
- runtime.exec("C://Program Files//Microsoft Office//OFFICE11//winword.exe c://test.doc");
- //执行批处理
- runtime.exec("c://x.bat");
- //执行系统命令
- runtime.exec("cmd /c dir ");
- runtime.exec("cmd /c dir c://");
- // //-------------- 文件操作 --------------
- runtime.exec("cmd /c copy c://x.bat d://x.txt"); //copy并改名
- runtime.exec("cmd /c rename d://x.txt x.txt.bak"); //重命名
- runtime.exec("cmd /c move d://x.txt.bak c://"); //移动
- runtime.exec("cmd /c del c://x.txt.bak"); //删除
- //-------------- 目录操作 --------------
- runtime.exec("cmd /c md c://_test"); //删除
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- /**
- * 执行批处理文件,并获取输出流重新输出到控制台
- */
- public static void dirOpt() {
- System.out.println("------------------dirOpt()--------------------");
- Process process;
- try {
- //执行命令
- process = Runtime.getRuntime().exec("c://x.bat");
- //取得命令结果的输出流
- InputStream fis = process.getInputStream();
- //用一个读输出流类去读
- BufferedReader br = new BufferedReader(new InputStreamReader(fis));
- String line = null;
- //逐行读取输出到控制台
- while ((line = br.readLine()) != null) {
- System.out.println(line);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
下面Java调用Perl命令的例子:
- //PerlExecResult is a user-defined class, It just saves execPerl's results
- public static PerlExecResult execPerl(){
- String[] cmd = { "perl", "pprogram.pl", "param1", "param2" };
- StringBuffer resultStringBuffer = new StringBuffer();
- String lineToRead = "";
- //get Process to execute perl, get the output and exitValue
- int exitValue = 0;
- try{
- Process proc = Runtime.getRuntime().exec( cmd );
- InputStream inputStream = proc.getInputStream();
- BufferedReader bufferedRreader =
- new BufferedReader( new InputStreamReader( inputStream ) );
- //save first line
- if( ( lineToRead = bufferedRreader.readLine() ) != null ){
- resultStringBuffer.append( lineToRead );
- }
- //save next lines
- while( ( lineToRead = bufferedRreader.readLine() ) != null ){
- resultStringBuffer.append( "/r/n" );
- resultStringBuffer.append( lineToRead );
- }
- //Always reading STDOUT first, then STDERR, exitValue last
- proc.waitFor(); //wait for reading STDOUT and STDERR over
- exitValue = proc.exitValue();
- }catch( Exception ex ){
- resultStringBuffer = new StringBuffer( "" );
- exitValue = 2;
- }
- PerlExecResult perlExecResult = new PerlExecResult( resultStringBuffer.toString(), exitValue );
- return perlExecResult;
- }
=================================================================================================
几乎所有的Java 集成开发环境都需要调用外部进程进行Java程序的构建,编译,运行和调试,Eclipse,NetBeans,JBuilder和Intellij IDLE概莫例外。在执行过程中,将提示信息以黑色全部打印在控制台里,将异常和错误以红色方式打印。以非常醒目交互体验让程序员远离枯燥和乏味。
现在让我们以Eclipse为例来看看它如何工作的,以揭开它神秘面纱,探究隐藏在后面的秘密。
上篇主要介绍了JAVA IDE Console通过采用Runtime.getRuntime.exec()执行外部程序后,将返回一个Process对象. Process对象能返回三个流:
getInputStream(),对应Process程序的标准输出流。
getErrorStream(), 对应Process程序的标准错误输出流。
getOutputStream();对应Process程序的标准输入流。
函数名之所以与Process程序的方向相反,原因是站在Java Host程序的角度讲的。
现在我们应用此原理来仿真IDE 执行外部程序的过程。
列表1:ConsoleSimulator.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
/**
* Class for console simulation
*
* @author lewhwa
*/
public class ConsoleSimulator implements Runnable {
private volatile boolean isStop = false ;
private static final int INFO = 0 ;
private static final int ERROR = 1 ;
private InputStream is;
private int type;
/** Creates a new instance of StreamInterceptor */
public ConsoleSimulator(InputStream is, int type) {
this .is = is;
this .type = type;
}
public void run() {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr);
String s;
try {
while (( ! isStop) && (s = reader.readLine()) != null ) {
if (s.length() != 0 ) {
if (type == INFO) {
System.out.println( " INFO> " + s);
} else {
System.err.println( " ERROR> " + s);
}
try {
Thread.sleep( 10 );
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void stop() {
isStop = true ;
}
public static void main(String[] args) throws IOException,
InterruptedException {
// Process child = Runtime.getRuntime().exec("run.bat");
Process child = Runtime.getRuntime().exec( " java -classpath bin helloworld.Test " );
OutputStream os = child.getOutputStream();
InputStream stdin = child.getInputStream(); //
InputStream stderr = child.getErrorStream();
Thread tIn = new Thread( new ConsoleSimulator(stdin, INFO));
Thread tErr = new Thread( new ConsoleSimulator(stderr, ERROR));
tIn.start();
tErr.start();
int result = child.waitFor();
tIn.join();
tErr.join();
if (result == 0 ) {
System.out.println( " SUCCESS! " );
} else {
System.out.println( " FAILED! " );
}
}
}
外部Bat文件:
列表2
cmd.exe / C / Q copy
javac
cmd.exe / C tree
rem c:\Designer_v5. 1 .0_win32_x86.exe c:\Designer_v5. 1 .0_win32_x861.exe
time / t
列表3:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/** Test Class
* @author lewhwa
*
*/
public class Test {
public static void main(String[] args) throws IOException {
FileReader fir = new FileReader( " src/helloworld/Test1.java " );
BufferedReader br = new BufferedReader(fir);
String s;
while ((s = br.readLine()) != null ){
System.out.println(s);
}
fir.close();
}
}
当ConsoleSimulator程序执行外部的run.bat时,输出如图1所示:
图1
当ConsoleSimulator程序执行外部的java test正常时,输出如图2所示:
图2
当ConsoleSimulator程序执行外部的java test发生异常时,输出如图3所示:
图3
综上,虽然没有在自己的GUI里将stdout和stderr进行说明,只是用ERROR>提示符和INFO>提示符进行演示,但是完全IDE Console的原理。对ConsoleSimulator稍加修改,完全放入到自己的应用程序当中去。
在我们进行Java程序开发的过程当中,可能涉及到其它的应用程序,借助这种技术,可以很好利用它们,将它们集成到自己的应用当中,将极大地缩短开发周期,何乐而不为呢!