6.2.2 进程使用
在Java程序中可能需要调用底层操作系统上的其他程序,Java标准API提供了创建底层操作系统上运行的进程的能力,只需要传入正确的命令和相关的参数,就可以启动一个进程。在进程启动之后,可以从Java程序向进程提供输入数据,以及读取进程运行过程中产生的输出数据。对于在Java程序中启动其他进程这个任务来说,最重要的是输入和输出的处理。通常的做法是把Java程序的内部运行结果作为输入传递给一个新创建的进程,然后等待进程执行完成。在得到进程输出的运行结果之后,再继续下面的处理。通过这种方式,底层操作系统上的其他进程可以很好地与Java程序集成起来。
在Java 7之前,对进程的输入和输出进行处理的方式比较有限,只支持管道式的方式。进程的输入对Java程序来说是一个输出流,程序向这个输出流中写入的数据会通过管道传递给进程。同样的,进程的输出对于Java程序来说是一个输入流,通过读取此输入流的内容获得进程的输出。标准的创建新进程的过程是使用java.lang.ProcessBuilder类来设置新进程的属性,然后通过start方法来启动进程的执行。ProcessBuilder类的start方法的返回值是一个表示进程的java.lang.Process类的对象。通过Process类的getOutputStream方法可以得到向进程写入数据的输出流,而通过getInputStream和getErrorStream方法可以分别得到包含进程正常执行和出错时输出内容的输入流。代码清单6-7给出了创建进程的示例。示例中启动了Windows上的命令行工具来执行“netstat-a”命令,并把结果保存到一个文件中。
代码清单6-7 创建进程的示例
public void startProcessNormal()throws IOException{
ProcessBuilder pb=
new ProcessBuilder("cmd.exe","/c","netstat","-a");
Process process=pb.start();
InputStream input=process.getInputStream();
Files.copy(input, Paths.get("netstat.txt"),StandardCopyOption.REPLACE_EXISTING);
}
使用管道的方式在某些情况下显得不够灵活,因此Java 7对进程的输入和输出处理进行了更新,增加了另外的两种处理方式。第一种是继承式,即新创建进程的输入和输出与当前的Java进程相同。第二种是基于文件式,即把文件作为进程输入的来源和输出的目的地。代码清单6-8给出了继承式的一个示例,其中启动的进程通过Windows上的命令行工具来执行dir命令,通过ProcessBuilder类的redirectOutput方法把进程的输出设置为继承自父进程,运行的结果就是dir命令的输出内容,会显示在Java程序默认的输出控制台中。
代码清单6-8 进程的输入和输出的继承式处理方式的示例
public void dir()throws IOException{
ProcessBuilder pb=
new ProcessBuilder("cmd.exe","/c","dir");
pb.redirectOutput(Redirect.INHERIT);
pb.start();
}
如果希望把进程的输入或输出改为文件,那么可以使用ProcessBuilder类中的redirectInput和redirectOut方法的其他重载形式。代码清单6-9给出了基于文件式处理方式的示例。示例中通过一个文件来保存进程的输出内容,只需要把一个java.io.File类的对象作为redirectOutput方法的参数即可。这种做法在实现上相当于通过管道方式读取输入流来获取进程的输出,然后将其写入一个文件中。不过以标准API的方式给出的实现,显然比程序自己来实现要好。
代码清单6-9 进程的输入和输出的文件式处理方式的示例
public void listProcesses()throws IOException{
ProcessBuilder pb=
new ProcessBuilder("wmic","process");
File output=Paths.get("tasks.txt").toFile();
pb.redirectOutput(output);
pb.start();
}
从API的角度来说,Java 7通过新增的ProcessBuilder.Redirect类对进程的输入和输出重定向方式进行了统一。ProcessBuilder.Redirect类提供了两种直接使用的重定向类型,一种是Java 7之前就有的管道式,用ProcessBuilder.Redirect.PIPE来表示;另一种是前面介绍的继承式,用ProcessBuilder.Redirect.INHERIT来表示。其余3种方式都是与文件相关的,在使用时都需要一个File类的对象作为参数。ProcessBuilder.Redirect.from表示从一个文件中读取内容作为输入,ProcessBuilder.Redirect.to表示把输出写入一个文件中,ProcessBuilder.Redirect.appendTo表示把输出的内容添加到一个已有的文件中。
在通过ProcessBuilder类的redirectInput和redirectOutput方法将输入和输出重定向之后,如果新的输入源和输出目标不是默认的管道方式,那么就无法访问所创建进程的Process类的对象中的对应流。例如,通过redirectInput方法把进程的输入重定向到某个文件之后,Process类的对象的getOutputStream方法返回的是一个空的输出流,调用该输出流的write方法总是会抛出IOException异常;通过redirectOutput和redirectError方法把进程的正常和错误的输出重定向到某个文件之后,Process类的对象的getInputStream和getErrorStream方法返回的是一个空的输入流,调用该输入流的read方法总是会返回-1。因此,如果在ProcessBuilder类的对象中对进程的输入或输出进行了重定向,那么相应的Process类的对象的使用者一定要了解这一点,以免造成使用错误。