Java程序中可以启动其他的应用程序,这种在Java中启动的进程称为子进程,启动子进程的Java程序称为父进程,其实这个父进程就是一个Java虚拟机
1、在Java程序中可以用Process类的实例对象来表示子进程,子进程的标准输入和输出不再连接到键盘和显示器(也就是不再接收键盘输入,和显示器输
出),而是以管道流的形式连接到父进程的一个输出流和输入流对象上
2、调用Process类的getOutputStream和getInputStream方法可以获得连接到子进程的输出流和输入流对象。子进程从标准输入读取到的内容就是父进程
通过输出流对象写入到它们俩之间的进程管道中的数据,子进程写入的标准输出的数据通过它们之间的进程管道传递到了父进程的输入流对象中,父进程
从这个输入流对象中读取到的内容就是子进程写入到标准输出的数据
编程实例:在TestInOut类中启动java.exe命令执行另外一个MyTest类,TestInOut和MyTest通过进程间的管道相互传递数据
TestInOut启动两个线程,一个不停的向MyTest中发送数据,另一个它不停的读取MyTest写回的数据
import java.io.*;
class test implements Runnable{
Process p = null;
public test() throws Exception
{
p = Runtime.getRuntime().exec("java MyTest"); //启动子进程,这个程序不存在会出现问题!
new Thread(this).start(); //启动线程,用new test()时,因为在构造函数中,所以就创建了两个实例对象,Thread会调用第二个对象中的run方法,而run方法调用的p就是第二个对象中的成员变量,而send方法所调用的p是第一个test对象中的成员变量,这样两个p不匹配了。还有可以产生无限递归,因为在构造函数中,创建一次就又调用一次构造方法,所以应该用this代替
}
public static void main(String[] args) throws Exception{
test ts = new test(); //创建对象时,子进程就启动了,接收线程也启动了
ts.send();
}
public void send() throws Exception{
try{
OutputStream ops = p.getOutputStream(); //发送,首先要连接一个输出流对象
while(true)
{
ops.write("help/r/n".getBytes()); //写入字符串
}
}catch(Exception e){ e.printStackTrace();}
}
public void run(){
try{
InputStream in = p.getInputStream(); //接收,首先要获取输入流对象
BufferedReader bfr = new BufferedReader(new InputStreamReader(in)); //为了一次读一行,就可以使用BufferedReader这个包装类来包装InputStream类,包装这个类需要先将字节流转换成字符流以后才能包装,查帮助!
while(true){
String strLine = bfr.readLine();
System.out.println(strLine);
}
}catch(Exception e){ e.printStackTrace();}
}
}
import java.io.*;
public class MyTest{
public static void main(String[] args){
while(true)
{
try{
System.out.println("hi:"+
new BufferedReader(new InputStreamReader(System.in)).readLine()); //读取父进程得数据
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}
【注意】每次执行都会启动一个子进程,在编译器中用Ctrl+c强行终止父进程后,子进程还是在运行。BufferedReader的实现是有点问题的,父进程已经终止了,子进程读取时,BufferedReader应该要抛出异常,让程序结束!根据BufferedReader的JDK帮助,那么我们对子进程代码进行一些修改,这样父进程终止后,子进程也结束了。那么test这个类也可以进行类似的修改,就可以不再不停的打印出null而不终止了。
import java.io.*;
public class MyTest{
public static void main(String[] args){
while(true)
{
try{
String strLine = new BufferedReader(new InputStreamReader(System.in)).readLine();
if(strLine != null)
{
System.out.println("hi:"+ strLine); //读取父进程得数据
}
else{
return;
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}
【思考】另外在上面的例子中我们可以看到数据丢失的情况,比如有字符串没打印完全的行,这是因为管道是有一定容量的,这个管道的容量就是PipedInputStream管道输入流缓冲区的大小,如果缓冲区满后,输出程序还在不停的写数据,就可能将前面的几个数据顶出去,那么如果接收端比较慢,那么就有可能少读一些内容回来了!Java对缓冲区满了后,它到底是以什么样的方式来处理的,是将前面的几个数据顶出去,还是将写进程阻塞,还是抛出异常?
验证管道缓冲区满后,将发生下面的哪种情况:
1、新的数据写入时,将最前面写入的数据挤出去,从而发生数据丢失
2、与PipedInputStream相连的PipedOutputStream无法再写入新的数据,PipedOutputStream.write方法处于阻塞状态
3、与PipedInputStream相连的PipedOutputStream无法再写入新的数据,PipedOutputStream.write方法抛出异常
【程序验证】
1、先让子进程不读取数据(MyTest类),空循环,那么父进程在不停的往管道里面写,就肯定会发生阻塞。这时运行程序,程序停滞不前了,不清楚是什么状况,但可以肯定的是缓冲区满了后没有抛出异常。注意,这时系统进程中有子进程,虽然父进程我们用Ctrl+c结束了
2、在父进程的send方法中先打印一个行号,即每次循环时,这样就能知道它是阻塞了还是继续在写,因为做第一步操作时,我们只能看到程序停滞不前,什么信息都没有。这时会发现行号打印到一定的时候程序就停止了(缓冲区跟机器有关?因为每个机器运行后的行号停止位置不一样。我是到了1366),
这样就说明了往管道缓冲区写满了以后,写的进程就发生了阻塞,而不是把前面的数据顶出去。注意,这时系统进程中有子进程,虽然父进程我们用Ctrl+c结束了
验证了管道缓冲区满后将出现的情况,我们接下来解决数据丢失的问题
1、还原代码,让子进程(MyTest类)再继续不停的读数据,注意写进程根据上面的验证应该是没什么问题的,它不会丢失数据
2、观察结果,这个情况应该不是读丢了数据,要是读丢了数据它怎么会将"hi:"这个我们在子进程加入的字符打印出来了呢,既然它都读到了"hi:",那
么它应该没有丢失信息,丢失信息应该是子进程读到的strLine中的内容不是完整的信息
3、现在就可以基本上确定问题发生了MyTest这个类中,而它的有效代码不长,打印内容部分应该没什么问题,那么问题就集中在String strLine = new
BufferedReader(new InputStreamReader(System.in)).readLine();这一句上。要解决这一句的问题,一般来说不太好掌握,但是对这种情况下我们自己
分析不出来原因的时候,可以把代码改用其他方式来写。
4、将这一句放到循环外面,因为我们找不出原因,可以将代码分拆试试,换个位置试试,因为这里代码也比较简单。如定义一个变量bfr,然后在循环中
调用readLine方法,即
BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
while(true)
{
String strLine = bfr.readLine();
}
5、这时发现程序正常了,没有丢失信息了,这是为什么呢?我们将代码移个位置就好了。
6、再回头看看原始的代码,new BufferedReader(new InputStreamReader(System.in))在循环中,每循环一次就创建一次新BufferedReader对象。而输
入输出流应该调用它们close方法关闭系统创建的流资源(不是Java创建的对象,而是系统创建的资源)。关闭Java创建的对象后,这个流资源对象不一定
会释放。这样运行的次数多了,积累的这种资源越来越多,系统就出问题了。
7、思考一下这时我们如果就在循环中创建对象,然后调用close方法会怎么样呢即
while(true)
{
BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
String strLine = bfr.readLine();
...
bfr.close()
}
8、再次编译运行,发现编译器只打印了一行信息,然后就报IO异常,管道已结束,这是为什么呢?
9、再分析这个问题,如果关闭流栈中最上层的流对象,它就会关闭这个流栈中所有底层的流,我们调用bfr.close(),那么它会连InputStreamReader和System.in所关联的流资源都给关闭掉。第二次循环时,System.in所关联的那个资源已经被关闭了,这时就不存在了,并且,这个异常时父进程报出来的,而子进程中将System.in一关闭掉,父进程就会返回-1,认为自己结束了,所以System.in就不能轻易的关闭掉
10、所以解决数据丢失的方法就是将BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in))放在循环外面。所以要注意程序代码的放置位置。另外我们用Java虚拟机启动一个子进程的时候,我们一定要注意在父进程结束时一定要让子进程结束。记住调用Process类的destroy方法结束子进程的运行(因为这个例子中io正好返回一个null,所以我们用null来判断,一般应该使用destroy方法来结束子进程)。并且在程序中创建子进程的情况很常见,如JCreator它会调用javac.exe和java.ext
【提示】要有一种思想,在编程时,脑中不要想的是一行行的代码,而是一个一个对象,程序每运行一句每个对象在干什么,是不是又多了对象,又少了对象,对象状态又怎么样去变化了,应该这样去思考。
提高程序的运行效率
1、for(int i=0;i<str.length();i++)
{..}
2、int len = str.length();
for(int i=0;i<len;i++)
{..}
第2种方法比第1种效率高,因为第1种每次循环都要调用length方法来检测字符串长度
3、byte[] buf = new byte[1024]
while(true){..}
4、while(true)
{byte[] buf = new byte[1024];}
第3种方法比第4种效率高,因为第4种每次循环都要创建一次数组对象,并且多占内存