Process简介

我们在实际Java开发工作中可能会遇到调用操作系统命令的场景,比如查看下文件夹,执行下sh/exe文件等等,那么我们就要用到Process了!

首先,打开API来认识下Process :

 java.lang
    类 Process
    java.lang.Object
      继承者 java.lang.Process
    public abstract class Process
          extends
          Object

        ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法
        创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。
        当没有 Process 对象的更多引用时,不是删掉子进程,而是继续异步执行子进程。
        对于带有 Process 对象的 Java 进程,没有必要异步或并发执行由 Process 对象表示的进程。

        需要我们注意的地方就是“因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。”。这是一个可能出现的BUG,我们只需留意一下。

以及“当没有 Process 对象的更多引用时,不是删掉子进程,而是继续异步执行子进程。”的意思,我大概理解为,即使在程序运行时已经没有变量引用Process的实例对象时,按java的gc机制,Process应该是被回收的,但是它不会,它会继续独立的运行下去,也就是说,如果我们Process对象已经使用完毕了,我们最好调用它的destroy方法去结束该子进程

        我们看到,Process是个抽象类,继承自“全民祖先”Object,有两种方式可以创建Process子类的实例,以及一系列进程交互方法:

方法摘要
    abstract  void    destroy()
              杀掉子进程。
    abstract  int    exitValue()
              返回子进程的出口值。
    abstract  InputStream    getErrorStream()
              获取子进程的错误流。
    abstract  InputStream    getInputStream()
              获取子进程的输入流。
    abstract  OutputStream    getOutputStream()
              获取子进程的输出流。
    abstract  int    waitFor()
              导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。

ProcessBuilder.start()Runtime.exec 方法执行之后都会返回一个Process类的实例,它不代表上述方法创建的进程,但是可以用来操纵该进程。 

由于Process类是Object类的直接子类,所以除了Object的基础方法外,以上便是Process所有的全部方法。

看本篇文章的标题大家也都清楚了,重点是如何对正在运行的程序输入数据,并获取输出的数据。在java,数据的传递,主要是以IO的数据流为主,还要JDK1.4中开始提供NIO,以数据块传输,这又是后话了。

那么,如何获取Process的数据流呢,那便是要依靠getInputStream和getOutputStream方法了,但是!需要注意的是:

Input和Output都是针对当前调用Process的程序而言的,而不是针对Process!

也就是说如果你要往Process进程中输入数据,那么你要调用Process的getOutputStream方法!

相反,如果你要获取Process进程的输出数据,那么你要调用Process的getInputStream方法!

在API中也说了,但是并不好理解:

 

换成咋们人说的话,站在我们自己写的程序角度来想,如果我们的程序想要往别的程序输入数据,那么就是我们程序要输出对吧,所以我们需要获取输出流也就是调用getOutputStream方法,那么反过来,如果我们要获取别的程序的输出的数据,对于我们程序来说也就是输入的数据,所以我们要获取输入流,调用getInputStream方法。

这是Process设计者的锅,不怪我们,但是我们也只有这样使用了。

另外的几个方法也简单介绍一下吧:

destroy:杀掉该Process对象代表的进程。

exitValue:返回该Process对象代表的进程的出口值,值0表示正常退出,非0非正常。关于该方法,应该是返回System.exit(int)方法中的参数。

waitFor:返回该Process对象代表的进程的出口值,值0表示正常退出,非0非正常。

该方法很类似exitValue方法,但是他们有个取别很大的地方,那就是exitValue方法会直接返回一个值,但是当前程序有可能还在运行中,所以该值不一定是正确的,而本方法会一直等待Process对象代表的进程退出后才返回值,而且调用本方法的进程将会一直堵塞等待返回值。建议使用本方法!

getErrorStream:与getInputStream的作用差不多,但略有些区别,在程序正常运行后输出的值是在getInputStream流中,但是如果程序出现错误后,输出的错误信息在getInputStream中是看不到的,因为错误信息在getErrorStream中!

我认为本方法和getInputStream的不同与System.out.println()和System.err.println()是对应的。所以我们在获取Process对象代表的进程的输出时不能只调用getInputStream方法,也应该调用getErrorStream方法,把两个流结合在一起进行查看。这样得到的信息才是完整的。

Process实战操练

学了那么多理论,还不如来个demo,我们来一个用“ping 百度网址”的例子吧,请看:

  import java.io.IOException;
     
    public class ProcessDemo {
        public static void main(String[] args) {
            try {
                Process process = Runtime.getRuntime().exec("ping www.baidu.com");
                System.out.println("任务执行完毕!");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

顺利运行,调用成功。但是结果是调用ping被挂到后台运行,程序直接打印了“任务执行完毕!”,而我们想要的效果是先把ping操作执行完成后,再输出“任务执行完毕!”。

好吧,Process提供了WaitFor和getInputStream两个方法,这两个方法都是阻塞java线程,等待脚本返回或结束后,再继续执行java程序,OK,那说改就改!

import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
     
    public class ProcessDemo {
        public static void main(String[] args) {
            try {
                Process process = Runtime.getRuntime().exec("ping www.baidu.com");
                BufferedReader bufferedReader = new BufferedReader(
                        new InputStreamReader(process.getInputStream(),"gbk"));
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    System.out.println(line);
                }
                System.out.println("任务执行完毕!");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
import java.io.IOException;
     
    public class ProcessDemo {
        public static void main(String[] args) {
            try {
                Process process = Runtime.getRuntime().exec("ping www.baidu.com");
                process.waitFor();
                System.out.println("任务执行完毕!");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

这就是我们想要的啦~

 
问题进阶

       仔细的同学可能发现了,API文档里还有这么一句话:“创建进程的方法可能无法针对某些本机平台 上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、 stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程 的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。

       这句话晦涩难懂,但可以简单地归纳一下,针对可执行程序的输入输出每个平台都会提供缓冲区,当没有及时把缓冲数据读出,且可执行程序在短时间内有大量数据输入缓冲区的话,缓冲区撑满,进程就会被阻塞。

       好吧,那意思很清楚了,就是让我们在写Process程序的时候,尽量主动把可执行程序的输入和输出读出来,不要让它们长时间留在缓冲区。

       上代码,我们先创建一个线程类,它主要负责不停地来读出Process所调用脚本的输出数据(主要的是读出error信息):

import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
     
    public class CleanInputCache extends Thread {
        private InputStream is;
        private String type;
     
        public CleanInputCache(InputStream is, String type) {
            this.is = is;
            this.type = type;
        }
     
        public void run() {
            try {
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                String line = null;
                while ((line = br.readLine()) != null)
                    System.out.println(type + ">>>" + line);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }

然后改造一下以上的“ping百度网址”的程序:

 import java.io.IOException;
     
    public class ProcessDemo {
        public static void main(String[] args) {
            try {
                Process process = Runtime.getRuntime().exec("ping www.baidu.com");
                new CleanInputCache(process.getInputStream(),"INFO").start();
                new CleanInputCache(process.getErrorStream(),"ERROR").start();
                process.waitFor();
                System.out.println("任务执行完毕!");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐