`

Use Piped Stream

阅读更多

本文的目标是设计一个基于Swing的JTextArea显示控制台输出。此期间,我们还将讨论一些和Java管道流(PipedInputStream 和PipedOutputStream)有关的注意事项。最后还要创建一个能够捕获和显示其他程序(可以是非Java的程序)控制台输出的简单程序。

一、Java管道流

     要在文本框中显示控制台输出,我们必须用某种方法“截取”控制台流。换句话说,我们要有一种高效地读取写入到System.out和System.err 所有内容的方法。Java的管道流PipedInputStream和PipedOutputStream是一个非常有效的工具。

     写入到PipedOutputStream输出流的数据可以从对应的PipedInputStream输入流读取。Java的管道流极大地方便了我们截取 控制台输出。Listing 1显示了一种非常简单的截取控制台输出方案。 

【Listing 1 :用管道流截取控制台输出】
 

   1. PipedInputStream pipedIS = new PipedInputStream();     
   2. PipedOutputStream pipedOS = new PipedOutputStream();     
   3. try {     
   4.   pipedOS.connect(pipedIS);     
   5. }     
   6. catch(IOException e) {     
   7.   System.err.println("连接失败");     
   8.   System.exit(1);     
   9. }     
  10. PrintStream ps = new PrintStream(pipedOS);     
  11. System.setOut(ps);     
  12. System.setErr(ps);  
 

 

     这里我们只是建立了一个PipedInputStream,把它设置为所有写入控制台流的数据的最终目的地。所有写入到控制台流的数据都被转到 PipedOutputStream,这样,从相应的PipedInputStream读取就可以迅速地截获所有写入控制台流的数据。接下来的事情似乎只 剩下在Swing JTextArea中显示从pipedIS流读取的数据,得到一个能够在文本框中显示控制台输出的程序。遗憾的是,在使用Java管道流时有一些重要的注 意事项。只有认真对待所有这些注意事项才能保证Listing 1的代码稳定地运行。下面我们来看第一个注意事项。

1.1 注意事项一

  PipedInputStream运用的是一个1024字节固定大小的循环缓冲区。写入PipedOutputStream的数据实际上保存到 对应的PipedInputStream的内部缓冲区。从PipedInputStream执行读操作时,读取的数据实际上来自这个内部缓冲区。如果对应 的PipedInputStream输入缓冲区已满,任何企图写入PipedOutputStream的线程都将被阻塞。而且这个写操作线程将一直阻塞, 直至出现读取PipedInputStream的操作从缓冲区删除数据。

  这意味着,向PipedOutputStream写数据的线程不应该是负责从对应PipedInputStream读取数据的唯一线程。从图1 可以清楚地看出这里的问题所在:假设线程t是负责从PipedInputStream读取数据的唯一线程;另外,假定t企图在一次对 PipedOutputStream的write()方法的调用中向对应的PipedOutputStream写入2000字节的数据。在t线程阻塞之 前,它最多能够写入1024字节的数据(PipedInputStream内部缓冲区的大小)。然而,一旦t被阻塞,读取 PipedInputStream的操作就再也不会出现,因为t是唯一读取PipedInputStream的线程。这样,t线程已经完全被阻塞,同时, 所有其他试图向PipedOutputStream写入数据的线程也将遇到同样的情形。

                                       

                                                               图1:管道流工作过程

  这并不意味着在一次write()调用中不能写入多于1024字节的数据。但应当保证,在写入数据的同时,有另一个线程从 PipedInputStream读取数据。

  Listing 2示范了这个问题。这个程序用一个线程交替地读取PipedInputStream和写入PipedOutputStream。每次调用write()向 PipedInputStream的缓冲区写入20字节,每次调用read()只从缓冲区读取并删除10个字节。内部缓冲区最终会被写满,导致写操作阻 塞。由于我们用同一个线程执行读、写操作,一旦写操作被阻塞,就不能再从PipedInputStream读取数据。

【Listing 2 :用同一个线程执行读/写操作导致线程阻塞】
 

   1. import java.io.*;  
   2.   
   3. // 用同一个线程执行读/写操作导致线程阻塞】  
   4. public class PipedStreamWithOneThread {  
   5.     static PipedInputStream  pipedIS = new PipedInputStream();  
   6.     static PipedOutputStream pipedOS = new PipedOutputStream();  
   7.     public static void main(String[] a){  
   8.         try {  
   9.             pipedIS.connect(pipedOS);  
  10.         }catch(IOException e) {  
  11.             System.err.println(" 连接失败");  
  12.             System.exit(1);  
  13.         }  
  14.         byte[] inArray = new byte[10];  
  15.         byte[] outArray = new byte[20];  
  16.         int bytesRead = 0;  
  17.         try {  
  18.             pipedOS.write(outArray, 0, 20);  // 向pipedOS发送20字节数据  
  19.             System.out.println("    已发送20字节...");  
  20. //           在每一次循环迭代中,读入10字节// 发送20字节  
  21.             bytesRead = pipedIS.read(inArray, 0, 10);   
  22.             System.out.println("    已读取10字节...");  
  23.             System.out.println("   10");  
  24.             int i=0;  
  25.             while(bytesRead != -1) {  
  26.                 pipedOS.write(outArray, 0, 20);  
  27.                 System.out.println("   已发送20字节..."+i);  
  28.                 i++;  
  29.                 bytesRead = pipedIS.read(inArray, 0, 10);  
  30.                 System.out.println("    已读取10字节...");  
  31.                 System.out.println(10+10*i);  
  32.             }  
  33.         }catch(IOException e) {  
  34.             System.err.println("读取 pipedIS时出现错误: " + e);  
  35.             System.exit(1);  
  36.             }  
  37.         }  
  38.     }  

 

     只要把读/写操作分开到不同的线程,Listing 2的问题就可以轻松地解决。

     Listing 3是Listing 2经过修改后的版本,它在一个单独的线程中执行写入PipedOutputStream的操作(和读取线程不同的线程)。为证明一次写入的数据可以超过 1024字节,我们让写操作线程每次调用PipedOutputStream的write()方法时写入2000字节。那么,在 startWriterThread()方法中创建的线程是否会阻塞呢?按照Java运行时线程调度机制,它当然会阻塞。写操作在阻塞之前实际上最多只能 写入1024字节的有效载荷(即PipedInputStream缓冲区的大小)。但这并不会成为问题,因为主线程(main)很快就会从 PipedInputStream的循环缓冲区读取数据,空出缓冲区空间。最终,写操作线程会从上一次中止的地方重新开始,写入2000字节有效载荷中的 剩余部分。

【Listing 3 :把读/写操作分开到不同的线程】
 

   1. import java.io.IOException;  
   2. import java.io.PipedInputStream;  
   3. import java.io.PipedOutputStream;  
   4.   
   5. //把读/写操作分开到不同的线程  
   6. public class PipedStreamWithTwoThread {  
   7.     static PipedInputStream  pipedIS = new PipedInputStream();  
   8.     static PipedOutputStream pipedOS = new PipedOutputStream();  
   9.     public static void main(String[] a){  
  10.         try {  
  11.             pipedIS.connect(pipedOS);  
  12.         }catch(IOException e) {  
  13.             System.err.println(" 连接失败");  
  14.             System.exit(1);  
  15.         }  
  16.         byte[] inArray = new byte[10];  
  17.         int bytesRead = 0;  
  18.         startWriterThread();  //启动write线程  
  19.         try {  
  20.             bytesRead = pipedIS.read(inArray, 0, 10); //每次读10字节  
  21.             System.out.println("已读取"+bytesRead+"字节...");  
  22.             int i=0;  
  23.             while(bytesRead != -1) {  
  24.                 bytesRead = pipedIS.read(inArray, 0, 10);  
  25.                 System.out.println("已读取"+bytesRead+" 字节...");  
  26.                 //下面这段防止程序无限执行  
  27.                 /*i++; 
  28.                 if (i==300) { 
  29.                     System.exit(0); 
  30.                 }*/  
  31.             }  
  32.         }catch(IOException e) {  
  33.             System.err.println(" 读取pipedIS时出现错误: " + e);  
  34.             System.exit(1);  
  35.             }  
  36.         }  
  37.       
  38.     //创建一个独立的线程 执行write操作  
  39.     private static void startWriterThread(){  
  40.         new Thread(new Runnable(){  
  41.             public void run(){  
  42.                 byte outArray[]=new byte[2000];  
  43.                 int j=0;  
  44.                 while (j==0) {   //无限循环  
  45.                     try {  
  46.                         pipedOS.write(outArray, 0, 2000);  //一次最多写入1024字节  
  47.                         j++;  
  48.                     } catch (IOException e) {  
  49.                         System.err.println("写操作失败");  
  50.                         System.exit(1);  
  51.                     }  
  52.                     System.out.println("已发送2000字节...");  
  53.                 }  
  54.             }  
  55.         }).start();  
  56.     }  
  57. }  

  

     也许我们不能说这个问题是Java管道流设计上的缺陷,但在应用管道流时,它是一个必须密切注意的问题。下面我们来看看第二 个更重要(更危险的)问题。

<!-- 分页 -->

1.2 注意事项二

  从PipedInputStream读取数据时,如果符合下面三个条件,就会出现IOException异常:

  1. 试图从PipedInputStream读取数据,
  2. PipedInputStream的缓冲区为“空”(即不存在可读取的数据),
  3. 最后一个向PipedOutputStream写数据的线程不再活动(通过Thread.isAlive()检测)。

  这是一个很微妙的时刻,同时也是一个极其重要的时刻。假定有一个线程w向PipedOutputStream写入数据;另一个线程r从对应的 PipedInputStream读取数据。下面一系列的事件将导致r线程在试图读取PipedInputStream时遇到IOException异 常:

  1. w向PipedOutputStream写入数据。
  2. w结束(w.isAlive()返回false)。
  3. r从PipedInputStream读取w写入的数据,清空PipedInputStream的缓冲区。
  4. r试图再次从PipedInputStream读取数据。这时PipedInputStream的缓冲区已经为空,而且w已经结束,从而导致在读 操作执行时出现IOException异常。

  构造一个程序示范这个问题并不困难,只需从Listing 3的startWriterThread()方法中,删除while(true)条件。这个改动阻止了执行写操作的方法循环执行,使得执行写操作的方法在 一次写入操作之后就结束运行。如前所述,此时主线程试图读取PipedInputStraem时,就会遇到一个IOException异常。

  这是一种比较少见的情况,而且不存在直接修正它的方法。请不要通过从管道流派生子类的方法修正该问题——在这里使用继承是完全不合适的。而且, 如果Sun以后改变了管道流的实现方法,现在所作的修改将不再有效。

最后一个问题和第二个问题很相似,不同之处在于,它在读线程(而不是写线程)结束时产生IOException异常。

1.3 注意事项三

  如果一个写操作在PipedOutputStream上执行,同时最近从对应PipedInputStream读取的线程已经不再活动(通过 Thread.isAlive()检测),则写操作将抛出一个IOException异常。假定有两个线程w和r,w向 PipedOutputStream写入数据,而r则从对应的PipedInputStream读取。下面一系列的事件将导致w线程在试图写入 PipedOutputStream时遇到IOException异常:

  1. 写操作线程w已经创建,但r线程还不存在。
  2. w向PipedOutputStream写入数据。
  3. 读线程r被创建,并从PipedInputStream读取数据。
  4. r线程结束。
  5. w企图向PipedOutputStream写入数据,发现r已经结束,抛出IOException异常。

  实际上,这个问题不象第二个问题那样棘手。和多个读线程/单个写线程的情况相比,也许在应用中有一个读线程(作为响应请求的服务器)和多个写线 程(发出请求)的情况更为常见。

1.4 解决问题

  要防止管道流前两个局限所带来的问题,方法之一是用一个ByteArrayOutputStream作为代理或替代 PipedOutputStream。Listing 4显示了一个LoopedStreams类,它用一个ByteArrayOutputStream提供和Java管道流类似的功能,但不会出现死锁和 IOException异常。这个类的内部仍旧使用管道流,但隔离了本文介绍的前两个问题。我们先来看看这个类的公用方法(参见图2)。构造函数很简单, 它连接管道流,然后调用startByteArrayReaderThread()方法(稍后再讨论该方法)。getOutputStream()方法返 回一个OutputStream(具体地说,是一个ByteArrayOutputStream)用以替代PipedOutputStream。写入该 OutputStream的数据最终将在getInputStream()方法返回的流中作为输入出现。和使用PipedOutputStream的情形 不同,向ByteArrayOutputStream写入数据的线程的激活、写数据、结束不会带来负面效果。

                   

                                                         图2:ByteArrayOutputStream原理

【Listing 4:防止管道流应用中出现的常见问题】
 

   1. import java.io.ByteArrayOutputStream;  
   2. import java.io.IOException;  
   3. import java.io.InputStream;  
   4. import java.io.OutputStream;  
   5. import java.io.PipedInputStream;  
   6. import java.io.PipedOutputStream;  
   7.   
   8. public class LoopedStreams {  
   9.   
  10.  private PipedOutputStream pipedOS=new PipedOutputStream();  
  11.  private boolean keepRunning=true;  
  12.  private ByteArrayOutputStream byteArrayOS=new ByteArrayOutputStream(){  
  13.   public void close() {  
  14.    System.out.println("byteArrayOS.close()");  
  15.    keepRunning=false;  
  16.    try {  
  17.     super.close();  
  18.     pipedOS.close();  
  19.    } catch (IOException e) {  
  20.     System.out.println("关闭 byteArrayOS错误:"+e.getMessage());  
  21.     System.exit(1);  
  22.    }  
  23.   }  
  24.  };  
  25.  private PipedInputStream pipedIS=new PipedInputStream(){  
  26.   public void close() {  
  27.    System.out.println("pipedIS.close()");  
  28.    keepRunning=false;  
  29.    try {  
  30.     super.close();  
  31.    } catch (IOException e) {  
  32.     System.out.println("关闭pipedIS错误:"+e.getMessage());  
  33.     System.exit(1);  
  34.    }  
  35.   }  
  36.  };  
  37.    
  38.  public LoopedStreams() throws IOException{  
  39.   // TODO 自动生成构造函数存根  
  40.   pipedOS.connect(pipedIS);  
  41.   startByteArrayReaderThread();  
  42.  }  
  43.    
  44.  public OutputStream getOutputStream() {  
  45.   return byteArrayOS;  
  46.  }  
  47.    
  48.  public InputStream getInputStream() {  
  49.   return pipedIS;  
  50.  }  
  51.    
  52.  public void startByteArrayReaderThread() {  
  53.   new Thread(new Runnable(){  
  54.    public void run() {  
  55.     while (keepRunning) {  
  56.      if (byteArrayOS.size()>0) { //检查流里面的字节  
  57.       byte buffer[]=null;  
  58.       synchronized (byteArrayOS) {  
  59.        buffer=byteArrayOS.toByteArray();  
  60.        byteArrayOS.reset(); //清空缓冲区  
  61.       }  
  62.       try {  
  63.        pipedOS.write(buffer, 0, buffer.length); //把提取的流发送到pipedOS  
  64.       } catch (IOException e) {  
  65.        System.out.println(e.getMessage());  
  66.        System.exit(1);  
  67.       }  
  68.      }else {  //没有数据可用,线程进入休眠状态  
  69.       try {  
  70.        Thread.sleep(1000); //休眠1秒  
  71.       } catch (InterruptedException e) {  
  72.       }  
  73.         
  74.      }  
  75.     }  
  76.    }  
  77.   }).start();  
  78.  }  
  79. }  

   

    startByteArrayReaderThread()方法是整个类真正的关键所在。这个方法的目标很简单,就是创建一个定期地检查 ByteArrayOutputStream缓冲区的线程。缓冲区中找到的所有数据都被提取到一个byte数组,然后写入到 PipedOutputStream。由于PipedOutputStream对应的PipedInputStream由 getInputStream()返回,从该输入流读取数据的线程都将读取到原先发送给ByteArrayOutputStream的数据。前面提 到,LoopedStreams类解决了管道流存在的前二个问题,我们来看看这是如何实现的。 <!-- 分页 --><!-- 分页 -->

  ByteArrayOutputStream具有根据需要扩展其内部缓冲区的能力。由于存在“完全缓冲”,线程向 getOutputStream()返回的流写入数据时不会被阻塞。因而,第一个问题不会再给我们带来麻烦。另外还要顺便说一 句,ByteArrayOutputStream的缓冲区永远不会缩减。例如,假设在能够提取数据之前,有一块500 K的数据被写入到流,缓冲区将永远保持至少500 K的容量。如果这个类有一个方法能够在数据被提取之后修正缓冲区的大小,它就会更完善。

  第二个问题得以解决的原因在于,实际上任何时候只有一个线程向PipedOutputStream写入数据,这个线程就是由 startByteArrayReaderThread()创建的线程。由于这个线程完全由LoopedStreams类控制,我们不必担心它会产生 IOException异常。

  LoopedStreams类还有一些细节值得提及。首先,我们可以看到byteArrayOS和pipedIS实际上分别是 ByteArrayOutputStream和PipedInputStream的派生类的实例,也即在它们的close()方法中加入了特殊的行为。如 果一个LoopedStreams对象的用户关闭了输入或输出流,在startByteArrayReaderThread()中创建的线程必须关闭。覆 盖后的close()方法把keepRunning标记设置成false以关闭线程。另外,请注意 startByteArrayReaderThread()中的同步块。要确保在toByteArray()调用和reset()调用之间 ByteArrayOutputStream缓冲区不被写入流的线程修改,这是必不可少的。由于ByteArrayOutputStream的 write()方法的所有版本都在该流上同步,我们保证了ByteArrayOutputStream的内部缓冲区不被意外地修改。

注意LoopedStreams类并不涉及管道流的第三个问题。该类的getInputStream()方法返回 PipedInputStream。如果一个线程从该流读取,一段时间后终止,下次数据从ByteArrayOutputStream缓冲区传输到 PipedOutputStream时就会出现IOException异常。

 

二、捕获Java控制台输出

  Listing 5的ConsoleToTextArea类扩展Swing JTextArea捕获控制台输出。不要对这个类有这么多代码感到惊讶,必须指出的是,ConsoleToTextArea类有超过50%的代码用来进行 测试。

【Listing 5 :截获Java控制台输出】   

   1. import java.awt.BorderLayout;  
   2. import java.awt.event.WindowAdapter;  
   3. import java.awt.event.WindowEvent;  
   4. import java.io.BufferedReader;  
   5. import java.io.IOException;  
   6. import java.io.InputStream;  
   7. import java.io.InputStreamReader;  
   8. import java.io.PrintStream;  
   9.   
  10. import javax.swing.JFrame;  
  11. import javax.swing.JOptionPane;  
  12. import javax.swing.JScrollPane;  
  13. import javax.swing.JTextArea;  
  14. import javax.swing.text.Document;  
  15.   
  16. public class ConsoleToTextArea extends JTextArea{  
  17.   
  18.     //转发所有从各个数组元素读取的数据到JTextArea  
  19.     public ConsoleToTextArea(InputStream[] inStreams) {  
  20.         for(int i = 0; i < inStreams.length; i++){  
  21.             startConsoleReaderThread(inStreams[i]);  
  22.         }  
  23.     }  
  24.   
  25.     //捕获和显示所有写入到控制台流的数据  
  26.     public ConsoleToTextArea() throws IOException{  
  27.         final LoopedStreams loopedStreams=new LoopedStreams();  
  28.           
  29.         //重定向System.out和System.err  
  30.         PrintStream printStream=new PrintStream(loopedStreams.getOutputStream());  
  31.         System.setOut(printStream);  
  32.         System.setErr(printStream);  
  33.         startConsoleReaderThread(loopedStreams.getInputStream());  
  34.     }  
  35.       
  36.     private void startConsoleReaderThread(InputStream inStream){  
  37.         final BufferedReader bufferedReader=  
  38.             new BufferedReader(new InputStreamReader(inStream));  
  39.           
  40.         new Thread(new Runnable(){  
  41.             public void run() {  
  42.                 StringBuffer stringBuffer=new StringBuffer();  
  43.                 try {  
  44.                     String s;  
  45.                     Document document=getDocument();  
  46.                     while ((s=bufferedReader.readLine())!=null) {  
  47.                         boolean caretAtEnd=false;  
  48.                         caretAtEnd=getCaretPosition()==document.getLength()?true:false;  
  49.                         stringBuffer.setLength(0);  
  50.                         append(stringBuffer.append(s).append("\n").toString());  
  51.                         if (caretAtEnd) {  
  52.                             setCaretPosition(document.getLength());  
  53.                         }  
  54.                     }  
  55.                 } catch (IOException e) {  
  56.                     JOptionPane.showMessageDialog(null, "从 bufferedReader中读取错误:"+e.getMessage());  
  57.                     System.exit(1);  
  58.                 }  
  59.             }  
  60.         }).start();  
  61.     }  
  62.   
  63.     /** 
  64.      * @param args  以下代码用于测试 
  65.      */  
  66.     public static void main(String[] args) {  
  67.         JFrame jFrame=new JFrame("ConsoleToTextArea测试");  
  68.         ConsoleToTextArea consoleToTextArea=null;  
  69.         try {  
  70.             consoleToTextArea=new ConsoleToTextArea();  
  71.         } catch (IOException e) {  
  72.             System.err.println("创建 ConsoleToTextArea出错");  
  73.             System.exit(1);  
  74.         }  
  75.         jFrame.getContentPane().add(new JScrollPane(consoleToTextArea),BorderLayout.CENTER);  
  76.         jFrame.setBounds(50,50,300,300);  
  77.         jFrame.setVisible(true);  
  78.         jFrame.addWindowListener(new WindowAdapter(){  
  79.             public void windowClosing(WindowEvent e) {  
  80.                 System.exit(0);  
  81.             }  
  82.         });  
  83.   
  84.         startWriterTestThread("写操作#1",System.err,950,50);  
  85.         startWriterTestThread("写操作#2",System.out,500,50);  
  86.         startWriterTestThread("写操作#3",System.out,200,50);  
  87.         startWriterTestThread("写操作#4",System.out,1000,50);  
  88.         startWriterTestThread("写操作#5",System.err,850,50);  
  89.     }  
  90.       
  91.     public static void startWriterTestThread(final String name,final PrintStream ps,final int dalay,final int count) {  
  92.         new Thread(new Runnable(){  
  93.             public void run() {  
  94.                 for (int i = 1; i < count; i++) {  
  95.                     ps.println(name+" i="+i);  
  96.                     try {  
  97.                         Thread.sleep(dalay);  
  98.                     } catch (InterruptedException e) {  
  99.                         // TODO: handle exception  
 100.                     }  
 101.                 }  
 102.             }  
 103.         }).start();  
 104.     }  
 105.   
 106. }  
 

 

    main()方法创建了一个JFrame,JFrame包含一个ConsoleToTextArea 的实例。这些代码并没有什么特别之处。Frame显示出来之后,main()方法 启动一系列的写操作线程,写操作线程向控制台流输出大量信息。ConsoleToTextArea 捕 获并显示这些信息,如图一所示。 <!-- 分页 -->

  ConsoleToTextArea提供了两个构造函数。没有参数的构造函数用来捕获和显示所有写入到控制台流的数据,有一个 InputStream[]参数的构造函数转发所有从各个数组元素读取的数据到JTextArea。稍后将有一个例子显示这个构造函数的用处。首先我们来 看看没有参数的ConsoleToTextArea构造函数。这个函数首先创建一个LoopedStreams对象;然后请求Java运行时环境把控制台 输出转发到LoopedStreams提供的OutputStream;最后,构造函数调用startConsoleReaderThread(),创建 一个不断地把文本行追加到JTextArea的线程。注意,把文本追加到JTextArea之后,程序小心地保证了插入点的正确位置。

  一般来说,Swing部件的更新不应该在AWT事件分派线程(AWT Event Dispatch Thread,AEDT)之外进行。对于本例来说,这意味着所有把文本追加到JTextArea的操作应该在AEDT中进行,而不是在 startConsoleReaderThread()方法创建的线程中进行。然而,事实上在Swing中向JTextArea追加文本是一个线程安全的 操作。读取一行文本之后,我们只需调用JText.append()就可以把文本追加到JTextArea的末尾。

三、捕获其他程序的控制台输出

  在JTextArea中捕获Java程序自己的控制台输出是一回事,去捕获其他程序(甚至包括一些非Java程序)的控制台数据又是另一回事。 ConsoleToTextArea提供了捕获其他应用的输出时需要的基础功能,Listing 6的OtherProgrameConsoleCapture利用ConsoleToTextArea,截取其他应用的输出信息然后显示在 ConsoleToTextArea中。

【Listing 6 :截获其他程序的控制台输出】   

 

   1. import java.awt.BorderLayout;  
   2. import java.awt.event.WindowAdapter;  
   3. import java.awt.event.WindowEvent;  
   4. import java.io.IOException;  
   5. import java.io.InputStream;  
   6.   
   7. import javax.swing.JFrame;  
   8. import javax.swing.JScrollPane;  
   9.   
  10. public class OtherProgrameConsoleCapture {  
  11.   
  12.     private static Process process;  
  13.     /** 
  14.      * @param args 
  15.      */  
  16.     public static void main(String[] args) {  
  17.         if (args.length==0) {  
  18.             System.err.println("用法:java OtherProgrameConsoleCapture " +  
  19.                     "<程序名字>{参数1 参数2...}");  
  20.             /*BufferedReader cin = new BufferedReader( new InputStreamReader(System.in )); 
  21.             try { 
  22.                 String arg= cin.readLine(); 
  23.             } catch (Exception e) { 
  24.                 // TODO: handle exception 
  25.             }*/  
  26.             System.exit(0);  
  27.         }  
  28.         try {  
  29.             //启动命令行指定程序的新进程  
  30.             process=Runtime.getRuntime().exec(args);  
  31.         } catch (IOException e) {  
  32.             System.err.println("创建进程出错!");  
  33.             System.exit(1);  
  34.         }  
  35.         //获得新进程写入的流  
  36.         InputStream[] inStreams =new InputStream[] {  
  37.                 process.getInputStream(),process.getErrorStream()};  
  38.   
  39.         ConsoleToTextArea consoleToTextArea=new ConsoleToTextArea(inStreams);  
  40.         JFrame jFrame=new JFrame(args[0]+"控制台输出");  
  41.         jFrame.getContentPane().add(new JScrollPane(consoleToTextArea),BorderLayout.CENTER);  
  42.         jFrame.setBounds(50, 50, 400, 400);  
  43.         jFrame.setVisible(true);  
  44.         jFrame.addWindowListener(new WindowAdapter(){  
  45.             public void windowClosing(WindowEvent e) {  
  46.                 process.destroy();  
  47.                 try {  
  48.                     process.waitFor(); //win98下可能会被挂起  
  49.                 } catch (InterruptedException ee) {  
  50.                     System.exit(0);  
  51.                 }  
  52.             }  
  53.         });  
  54.   
  55.     }  
  56.   
  57. }  
 

  OtherProgrameConsoleCapture 的工作过程如下:首先利用Runtime.exec()方法启动指定程序的一个新进程。启动新进程之后,从结果Process对象得到它的控制台流。之 后,把这些控制台流传入ConsoleToTextArea(InputStream[])构造函数(这就是带参数ConsoleToTextArea构 造函数的用处)。使用OtherProgrameConsoleCapture 时,在命令行上指定待截取其输出的程序名字。例如,如果在Windows XP下进入cmd中进入到classes目录 执行  java OtherProgrameConsoleCapture ping www.yahoo.akadns.net 查看效果(图3) 

                               

                                                      图3:截取其他程序的控制台输出

  使用OtherProgrameConsoleCapture 时应该注意,被截取输出的应用程序最初输出的一些文本可能无法截取。因为在调用Runtime.exec()和ConsoleToTextArea初始化 完成之间存在一小段时间差。在这个时间差内,应用程序输出的文本会丢失。当OtherProgrameConsoleCapture 窗口被关闭,process.destory()调用试图关闭Java程序开始时创建的进程。测试结果显示出,destroy()方法不一定总是有效(至 少在Windows 98上是这样的)。似乎当待关闭的进程启动了额外的进程时,则那些进程不会被关闭。此外,在这种情况下 OtherProgrameConsoleCapture 程序看起来未能正常结束。但在Windows NT下,一切正常。如果用JDK v1.1.x运行OtherProgrameConsoleCapture ,关闭窗口时会出现一个NullPointerException。这是一个JDK的Bug,JDK 1.2.x和JDK 1.3.x下就不会出现问题。

分享到:
评论

相关推荐

    opk-cli--cloudstitch-google-sheets:命令行客户端,用于通过Cloudstitch API将数据推送到Google Sheet

    ./push --account &lt;account&gt; Example: janesmith --project_id &lt;project&gt; Example: magic-form-7 --stream Accept streaming piped data that delimits pushes by line breaks --verbose Use for deb

    Unix shell programming in 24 hours.pdf

    Reading from `stdin` allows commands to process data piped into them. - **File Descriptors** File descriptors are integer values representing open files or streams. `0` represents standard input, `1`...

    技嘉Z97X UD3H F9 增加NVME启动功能

    亲测可用

    《基于YOLOv8的音响设备识别系统》(包含源码、完整数据集、可视化界面、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计.zip

    资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。

    毕业设计物联网实战项目基于mqttd-centos7-v2.3.11.zip 配置的emqtt服务器,配套金大万翔物联网管理平台.zip

    【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    毕设单片机实战项目基于ESP8266的家庭版简易开关.zip

    【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    毕设单片机实战项目基于ESP8266制作的一个局域网关灯神器,还有一个OLED,用于显示实时时间和最近三天的天气信息。.zip

    【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    毕业设计物联网实战项目基于树莓派wifi的物联网项目.zip

    【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    IDE的护眼的主题的套件

    IDE护眼主题套件

    【新能源汽车】基于Matlab/Simulink的增程式电动车动力系统仿真模型设计与优化:功率跟随控制及能量流管理

    内容概要:文章详细介绍了基于Matlab/Simulink构建的增程式电动车仿真模型。该模型由电池、电机、发动机、整车动力学、控制策略和驾驶员模块六大组件构成,重点在于各模块间的能量流动逻辑。文中特别强调了功率跟随控制策略,通过PID闭环控制使发动机功率与电池需求动态匹配,优化了燃油经济性和SOC控制精度。此外,模型采用开放式架构,所有参数通过m脚本集中管理,便于修改和扩展。文章展示了模型在典型工况下的性能表现,并突出了其在科研和工程应用中的灵活性和实用性。; 适合人群:对新能源汽车技术感兴趣的工程师、研究人员以及高校相关专业师生。; 使用场景及目标:①用于研究增程式电动车的能量管理策略;②作为教学案例帮助学生理解复杂系统的建模方法;③为实际工程项目提供可复用的仿真平台。; 阅读建议:读者应重点关注模型的架构设计和关键控制算法实现,同时结合提供的代码片段进行实践操作,以便更好地掌握增程式电动车的工作原理及其优化方法。

    51a30-main.zip

    51a30-main.zip

    【Java数据库技术】索引类型与事务特性详解:提升SQL查询性能与数据一致性管理

    内容概要:本文详细介绍了多种类型的数据库索引及其应用场景,包括普通索引、唯一性索引、单个索引、复合索引、聚簇索引、非聚簇索引、主索引、外键索引、全文索引和空间索引。每种索引都有其独特的定义、要点和适用场景,并附有具体的SQL代码示例。此外,文章还对比了InnoDB和MyISAM两种存储引擎的特点,解释了脏读、不可重复读、可重复读和幻读的概念,并讨论了SQL优化的方法以及数据库事务的ACID特性。 适合人群:具备一定数据库基础知识的开发者、数据库管理员以及参与数据库设计和优化的技术人员。 使用场景及目标:①帮助开发者选择合适的索引类型以提高查询效率;②理解不同存储引擎的特点,选择最适合应用场景的存储引擎;③掌握事务隔离级别的概念,避免数据不一致问题;④学习SQL优化技巧,提升数据库性能;⑤理解ACID特性,确保数据库操作的一致性和可靠性。 阅读建议:本文内容较为全面且深入,建议读者结合实际项目需求,重点理解不同类型索引的应用场景,掌握SQL优化的基本原则,并熟悉事务处理的最佳实践。

    【MATLAB优化算法】基于MATLAB的优化算法实现与应用:涵盖梯度下降、线性规划、非线性规划及智能优化算法

    内容概要:本文详细介绍了MATLAB中优化算法的实现方法,涵盖确定性算法(如梯度下降法)和随机性算法(如遗传算法、粒子群优化)。文章首先讲解了梯度下降法和MATLAB优化工具箱的应用,展示了如何使用fmincon解决约束优化问题。接着,文章深入探讨了线性规划、非线性规划和多目标优化的理论和实践,提供了具体的MATLAB代码示例。此外,文中还介绍了遗传算法、粒子群优化和模拟退火算法的原理及应用,并通过实例展示了这些算法在实际问题中的使用。最后,文章讨论了优化算法在工程、金融和机器学习领域的高级应用,以及调试和优化的常见策略。 适合人群:具备一定编程基础,对优化算法感兴趣的工程师、研究人员和学生。 使用场景及目标:①理解优化算法的基础理论和实现方法;②掌握MATLAB优化工具箱的使用,解决线性、非线性、多目标优化问题;③学习遗传算法、粒子群优化和模拟退火算法的具体应用;④提高优化算法的性能和可靠性,解决实际工程、金融和机器学习问题。 阅读建议:本文内容丰富,涉及多种优化算法及其MATLAB实现,建议读者先掌握基本的优化理论和MATLAB编程基础,再逐步深入学习各类算法的具体应用。在学习过程中,结合提供的代码示例进行实践,并尝试调整参数以优化算法性能。

    python for appium ui automate test, it is something to update.

    this is for myself learn coding, change a pc debug.

    p111基于django的企业员工管理系统.zip

    项目资源包含:可运行源码+sql文件 适用人群:学习不同技术领域的小白或进阶学习者;可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 项目具有较高的学习借鉴价值,也可拿来修改、二次开发。 有任何使用上的问题,欢迎随时与博主沟通,博主看到后会第一时间及时解答。 开发语言:Python 框架:django Python版本:python3.8 数据库:mysql 5.7 数据库工具:Navicat 开发软件:PyCharm 浏览器:谷歌浏览器

    毕设单片机实战项目基于esp8266的太空人时钟小电视.zip

    【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    电力电子MMC型STATCOM/SVG载波移相调制与电压均衡控制技术详解:从理论到实战调试经验分享

    内容概要:本文深入探讨了MMC型STATCOM/SVG的核心技术和调试技巧,重点讲解了载波移相调制(CPS-PWM)和电压均衡控制两大关键技术。载波移相调制通过为每个子模块设置不同的载波相位差,有效降低谐波含量并优化开关频率。电压均衡则分为桥臂内、桥臂间和相间三个层次,分别采用动态排序、比例控制和零序电压注入等方法,确保系统稳定运行。文章还分享了多个实战经验,如低压调试、红外热像仪检测以及避免参数设置不当引发的问题。; 适合人群:从事电力电子领域,特别是参与STATCOM/SVG项目的设计、开发和调试的技术人员。; 使用场景及目标:①理解MMC型STATCOM/SVG的工作原理和技术细节;②掌握载波移相调制的具体实现方法;③学习电压均衡控制的各种策略及其应用场景;④获取实际调试过程中常见问题的解决方案。; 阅读建议:本文涉及大量技术细节和实战经验,建议读者结合实际项目进行阅读,重点关注载波移相调制和电压均衡控制的具体实现,并参考提供的代码片段进行实践。

    liangmmm_finalll.scdoc

    liangmmm_finalll.scdoc

    区块链开发中Solidity语言的关键特性与应用解析

    内容概要:本文详细介绍了Solidity语言的核心概念和语法特性,涵盖结构体、函数修改器、事件、类型系统、数组、映射、操作符、合约可见性、构造函数、抽象合约、接口、继承、控制结构、异常处理和keccak256哈希函数等内容。通过这些知识点的讲解,帮助开发者理解如何构建高效、安全的智能合约。; 适合人群:对区块链开发感兴趣,尤其是希望深入了解以太坊智能合约开发的初学者及有一定编程基础的研发人员。; 使用场景及目标:①掌握Solidity语言的基本语法和高级特性,如结构体、函数修改器、事件等;②理解合约的可见性、继承、接口等面向对象编程特性;③学会使用keccak256等安全机制保障智能合约的安全性;④能够运用控制结构和异常处理编写健壮的合约逻辑。; 阅读建议:建议读者从基础语法开始逐步深入,结合实际案例进行练习。尤其要注意合约的安全性和性能优化,避免常见的漏洞和错误。在学习过程中,应多参考官方文档和其他优质资料,不断巩固和拓展知识体系。

Global site tag (gtag.js) - Google Analytics