`

转载自俞良松--在Java程序中截获控制台输出

    博客分类:
  • java
阅读更多
在Java开发中,控制台输出仍是一个重要的工具,但默认的控制台输出有着各种各样的局限。本文介绍如何用Java管道流截取控制台输出,分析管道流应用中应该注意的问题,提供了截取Java程序和非Java程序控制台输出的实例。
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

即使在图形用户界面占统治地位的今天,控制台输出仍旧在Java程序中占有重要地位。控制台不仅是Java程序默认的堆栈跟踪和错误信息输出窗口,而且还 是一种实用的调试工具(特别是对习惯于使用println()的人来说)。然而,控制台窗口有着许多局限。例如在Windows 9x平台上,DOS控制台只能容纳50行输出。如果Java程序一次性向控制台输出大量内容,要查看这些内容就很困难了。

对于使用javaw这个启动程序的开发者来说,控制台窗口尤其宝贵。因为用javaw启动java程序时,根本不会有控制台窗口出现。如果程序遇到了问题 并抛出异常,根本无法查看Java运行时环境写入到System.out或System.err的调用堆栈跟踪信息。为了捕获堆栈信息,一些人采取了用 try/catch()块封装main()的方式,但这种方式不一定总是有效,在Java运行时的某些时刻,一些描述性错误信息会在抛出异常之前被写入 System.out和System.err;除非能够监测这两个控制台流,否则这些信息就无法看到。

因此,有些时候检查Java运行时环境(或第三方程序)写入到控制台流的数据并采取合适的操作是十分必要的。本文讨论的主题之一就是创建这样一个输入流, 从这个输入流中可以读入以前写入Java控制台流(或任何其他程序的输出流)的数据。我们可以想象写入到输出流的数据立即以输入的形式“回流”到了 Java程序。

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


图一:多线程的控制台输出截取程序
图一:多线程的控制台输出截取程序

一、Java管道流

要在文本框中显示控制台输出,我们必须用某种方法“截取”控制台流。换句话说,我们要有一种高效地读取写入到System.out和System.err 所有内容的方法。如果你熟悉Java的管道流PipedInputStream和PipedOutputStream,就会相信我们已经拥有最有效的工 具。

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

【Listing 1:用管道流截取控制台输出】
PipedInputStream pipedIS = new PipedInputStream();
PipedOutputStream pipedOS = new PipedOutputStream();
try {
   pipedOS.connect(pipedIS);
}
catch(IOException e) {
   System.err.println("连接失败");
   System.exit(1);
}
PrintStream ps = new PrintStream(pipedOS);
System.setOut(ps);
System.setErr(ps);

 

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

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

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


图二:管道流工作过程
图二:管道流工作过程

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

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

【Listing 2:用同一个线程执行读/写操作导致线程阻塞】
import java.io.*;
public class Listing2 {
    static PipedInputStream pipedIS = new PipedInputStream();
    static PipedOutputStream pipedOS = 
        new PipedOutputStream();
    public static void main(String[] a){
        try {
            pipedIS.connect(pipedOS);
        }
        catch(IOException e) {
            System.err.println("连接失败");
                System.exit(1);
            }
        byte[] inArray    = new byte[10];
        byte[] outArray = new byte[20];
        int bytesRead = 0;
        try {
            // 向pipedOS发送20字节数据
            pipedOS.write(outArray, 0, 20);
            System.out.println("     已发送20字节...");
           // 在每一次循环迭代中,读入10字节
           // 发送20字节
            bytesRead = pipedIS.read(inArray, 0, 10);
            int i=0;
            while(bytesRead != -1) {
                pipedOS.write(outArray, 0, 20);
                System.out.println("     已发送20字节..."+i);
                i++;
                bytesRead = pipedIS.read(inArray, 0, 10);
            }
        }
        catch(IOException e) {
                System.err.println("读取pipedIS时出现错误: " + e);
                System.exit(1);
        }
    } // main()
}

 

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

【Listing 3:把读/写操作分开到不同的线程】
import java.io.*;
public class Listing3 {
    static PipedInputStream pipedIS =
        new PipedInputStream();
    static PipedOutputStream pipedOS =
        new PipedOutputStream();
    public static void main(String[] args) {
        try {
            pipedIS.connect(pipedOS);
        }
        catch(IOException e) {
            System.err.println("连接失败");
            System.exit(1);
        }
        byte[] inArray = new byte[10];
        int bytesRead = 0;
        // 启动写操作线程
        startWriterThread();
        try {
            bytesRead = pipedIS.read(inArray, 0, 10);
            while(bytesRead != -1) {
                System.out.println("已经读取" +
                    bytesRead + "字节...");
                bytesRead = pipedIS.read(inArray, 0, 10);
            }
        }
        catch(IOException e) {
            System.err.println("读取输入错误.");
            System.exit(1);
        }
    } // main()
    // 创建一个独立的线程
    // 执行写入PipedOutputStream的操作
    private static void startWriterThread() {
        new Thread(new Runnable() {
            public void run() {
                byte[] outArray = new byte[2000];
                while(true) { // 无终止条件的循环
                    try {
                        // 在该线程阻塞之前,有最多1024字节的数据被写入
                        pipedOS.write(outArray, 0, 2000);
                    }
                    catch(IOException e) {
                        System.err.println("写操作错误");
                        System.exit(1);
                    }
                    System.out.println("     已经发送2000字节...");
                }
            }
        }).start();
    } // startWriterThread()
} // Listing3

 

也许我们不能说这个问题是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异常。这个类的内部仍旧使用管道流,但隔离了本文介绍的前两个问题。我们先来看看这个类的公用方法(参见图3)。构造函数很简单, 它连接管道流,然后调用startByteArrayReaderThread()方法(稍后再讨论该方法)。getOutputStream()方法返 回一个OutputStream(具体地说,是一个ByteArrayOutputStream)用以替代PipedOutputStream。写入该 OutputStream的数据最终将在getInputStream()方法返回的流中作为输入出现。和使用PipedOutputStream的情形 不同,向ByteArrayOutputStream写入数据的线程的激活、写数据、结束不会带来负面效果。


图三:ByteArrayOutputStream原理
图三:ByteArrayOutputStream原理

【Listing 4:防止管道流应用中出现的常见问题】
import java.io.*;
public class LoopedStreams {
    private PipedOutputStream pipedOS = 
        new PipedOutputStream();
    private boolean keepRunning = true;
    private ByteArrayOutputStream byteArrayOS =
        new ByteArrayOutputStream() {
        public void close() {
            keepRunning = false;
            try {
                super.close();
                pipedOS.close();
            }
            catch(IOException e) {
                // 记录错误或其他处理
                // 为简单计,此处我们直接结束
                System.exit(1);
            }
        }
    };
    private PipedInputStream pipedIS = new PipedInputStream() {
        public void close() {
            keepRunning = false;
            try    {
                super.close();
            }
            catch(IOException e) {
                // 记录错误或其他处理
                // 为简单计,此处我们直接结束
                System.exit(1);
            }
        }
    };
    public LoopedStreams() throws IOException {
        pipedOS.connect(pipedIS);
        startByteArrayReaderThread();
    } // LoopedStreams()
    public InputStream getInputStream() {
        return pipedIS;
    } // getInputStream()
    public OutputStream getOutputStream() {
        return byteArrayOS;
    } // getOutputStream()
    private void startByteArrayReaderThread() {
        new Thread(new Runnable() {
            public void run() {
                while(keepRunning) {
                    // 检查流里面的字节数
                    if(byteArrayOS.size() > 0) {
                        byte[] buffer = null;
                        synchronized(byteArrayOS) {
                            buffer = byteArrayOS.toByteArray();
                            byteArrayOS.reset(); // 清除缓冲区
                        }
                        try {
                            // 把提取到的数据发送给PipedOutputStream
                            pipedOS.write(buffer, 0, buffer.length);
                        }
                        catch(IOException e) {
                            // 记录错误或其他处理
                            // 为简单计,此处我们直接结束
                            System.exit(1);
                        }
                    }
                    else // 没有数据可用,线程进入睡眠状态
                        try {
                            // 每隔1秒查看ByteArrayOutputStream检查新数据
                            Thread.sleep(1000);
                        }
                        catch(InterruptedException e) {}
                    }
             }
        }).start();
    } // startByteArrayReaderThread()
} // LoopedStreams

 

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的ConsoleTextArea类扩展Swing JTextArea捕获控制台输出。不要对这个类有这么多代码感到惊讶,必须指出的是,ConsoleTextArea类有超过50%的代码用来进行测试。

【Listing 5:截获Java控制台输出】
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;
public class ConsoleTextArea extends JTextArea {
    public ConsoleTextArea(InputStream[] inStreams) {
        for(int i = 0; i < inStreams.length; ++i)
            startConsoleReaderThread(inStreams[i]);
    } // ConsoleTextArea()
    public ConsoleTextArea() throws IOException {
        final LoopedStreams ls = new LoopedStreams();
        // 重定向System.out和System.err
        PrintStream ps = new PrintStream(ls.getOutputStream());
        System.setOut(ps);
        System.setErr(ps);
        startConsoleReaderThread(ls.getInputStream());
    } // ConsoleTextArea()
    private void startConsoleReaderThread(
        InputStream inStream) {
        final BufferedReader br =
            new BufferedReader(new InputStreamReader(inStream));
        new Thread(new Runnable() {
            public void run() {
                StringBuffer sb = new StringBuffer();
                try {
                    String s;
                    Document doc = getDocument();
                    while((s = br.readLine()) != null) {
                        boolean caretAtEnd = false;
                        caretAtEnd = getCaretPosition() == doc.getLength() ?
                            true : false;
                        sb.setLength(0);
                        append(sb.append(s).append('\n').toString());
                        if(caretAtEnd)
                            setCaretPosition(doc.getLength());
                    }
                }
                catch(IOException e) {
                    JOptionPane.showMessageDialog(null,
                        "从BufferedReader读取错误:" + e);
                    System.exit(1);
                }
            }
        }).start();
    } // startConsoleReaderThread()
    // 该类剩余部分的功能是进行测试
    public static void main(String[] args) {
        JFrame f = new JFrame("ConsoleTextArea测试");
        ConsoleTextArea consoleTextArea = null;
        try {
            consoleTextArea = new ConsoleTextArea();
        }
        catch(IOException e) {
            System.err.println(
                "不能创建LoopedStreams:" + e);
            System.exit(1);
        }
        consoleTextArea.setFont(java.awt.Font.decode("monospaced"));
        f.getContentPane().add(new JScrollPane(consoleTextArea),
            java.awt.BorderLayout.CENTER);
        f.setBounds(50, 50, 300, 300);
        f.setVisible(true);
        f.addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(
                java.awt.event.WindowEvent evt) {
                System.exit(0);
            }
        });
        // 启动几个写操作线程向
        // System.out和System.err输出
        startWriterTestThread(
            "写操作线程 #1", System.err, 920, 50);
        startWriterTestThread(
            "写操作线程 #2", System.out, 500, 50);
        startWriterTestThread(
            "写操作线程 #3", System.out, 200, 50);
        startWriterTestThread(
            "写操作线程 #4", System.out, 1000, 50);
        startWriterTestThread(
            "写操作线程 #5", System.err, 850,    50);
    } // main()
    private static void startWriterTestThread(
        final String name, final PrintStream ps, 
        final int delay, final int count) {
        new Thread(new Runnable() {
            public void run() {
                for(int i = 1; i <= count; ++i) {
                    ps.println("***" + name + ", hello !, i=" + i);
                    try {
                        Thread.sleep(delay);
                    }
                    catch(InterruptedException e) {}
                }
            }
        }).start();
    } // startWriterTestThread()
} // ConsoleTextArea

 

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

ConsoleTextArea提供了两个构造函数。没有参数的构造函数用来捕获和显示所有写入到控制台流的数据,有一个InputStream[]参数 的构造函数转发所有从各个数组元素读取的数据到JTextArea。稍后将有一个例子显示这个构造函数的用处。首先我们来看看没有参数的 ConsoleTextArea构造函数。这个函数首先创建一个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程序)的控制台数据又是另一回事。ConsoleTextArea提供了捕获其他应用的输出时需要的基础功能,Listing 6的AppOutputCapture利用ConsoleTextArea,截取其他应用的输出信息然后显示在ConsoleTextArea中。

【Listing 6:截获其他程序的控制台输出】
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
public class AppOutputCapture {
        private static Process process;
        public static void main(String[] args) {
                if(args.length == 0) {
             System.err.println("用法:java AppOutputCapture " +
                 "<程序名字> {参数1 参数2 ...}");
             System.exit(0);
                }
                try {
                        // 启动命令行指定程序的新进程
                        process = Runtime.getRuntime().exec(args);
                }
                catch(IOException e) {
                        System.err.println("创建进程时出错...\n" + e);
                        System.exit(1);
                }
                // 获得新进程所写入的流
                InputStream[] inStreams =
                        new InputStream[] {
                    process.getInputStream(),process.getErrorStream()};
                ConsoleTextArea cta = new
    ConsoleTextArea(inStreams);
                cta.setFont(java.awt.Font.decode("monospaced"));
                JFrame frame = new JFrame(args[0] +
                        "控制台输出");
                frame.getContentPane().add(new JScrollPane(cta),
                    BorderLayout.CENTER);
                frame.setBounds(50, 50, 400, 400);
                frame.setVisible(true);
                frame.addWindowListener(new WindowAdapter() {
                        public void windowClosing(WindowEvent evt) {
                                process.destroy();
                                try {
                                        process.waitFor(); // 在Win98下可能被挂起
                                }
                                catch(InterruptedException e) {}
                                        System.exit(0);
                                }
                        });
        } // main()
} // AppOutputCapture

 

AppOutputCapture的工作过程如下:首先利用Runtime.exec()方法启动指定程序的一个新进程。启动新进程之后,从结果 Process对象得到它的控制台流。之后,把这些控制台流传入ConsoleTextArea(InputStream[])构造函数(这就是带参数 ConsoleTextArea构造函数的用处)。使用AppOutputCapture时,在命令行上指定待截取其输出的程序名字。例如,如果在 Windows 2000下执行javaw.exe AppOutputCapture ping.exe www.yahoo.com,则结果如图四所示。


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

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

分享到:
评论

相关推荐

    Java程序 中截获控制台 输出

    在Java编程中,有时我们需要对程序的控制台输出进行管理和控制,例如在自动化测试中捕获日志,或者在特定环境中防止不必要的信息显示。本文将详细介绍如何在Java程序中实现控制台输出的截获。 首先,`Listing1.java...

    在Java程序中截获控制台输出

    在Java程序中截获java控制台输出/还有其他语言输出的控制台 在Java开发中,控制台输出仍是一个重要的工具,但默认的控制台输出有着各种各样的局限。本文介绍如何用Java管道流截取控制台输出,分析管道流应用中应该...

    在Java程序中截获控制台输出JAVA语言

    在Java程序中截获控制台输出

    在java中截获控制台输出

    这里我们将深入探讨如何在Java中实现这个功能,特别是在Swing环境中创建一个文本窗口来显示控制台输出。 首先,Java中的`System.out.println()`和`System.err.println()`是我们常见的控制台输出方式,它们主要用于...

    商业编程-源码-消息截获程序代码.zip

    消息截获程序代码可能是为了提供一种机制,允许开发者在不修改原有代码的情况下,对系统中传递的消息进行拦截、分析或拦截后的操作。 【标签】:“商业编程-源码-消息截获程序代码”这个标签进一步明确了代码的用途...

    java文本编辑器(类似记事本,可编译运行java程序)

    自己写的文本编辑器,以windows记事本为模版,增加编译运行java程序的功能,运行java程序时可截获控制台输出,并与之交互。本人也是新手,资源无偿下载,大家帮助测试,希望指出bug,提出宝贵意见帮助我改正

    C++控制台程序退出事件

    在C++编程中,控制台程序的退出事件处理是一个重要的环节,特别是在开发涉及用户交互或者需要在程序结束前执行特定清理任务的系统时。本文将深入探讨如何在Visual Studio 2003环境下,通过C++代码来截获并处理控制台...

    mysql-connector-java-8.0.19.zip

    在Java开发中,特别是SpringBoot框架的应用,`mysql-connector-java-8.0.19.jar`是必不可少的依赖。SpringBoot是一个快速开发框架,它简化了Spring应用的初始搭建以及开发过程。通过引入这个驱动,SpringBoot项目...

    alipay-sdk-JAVA-20180104135049

    本文将深入探讨“alipay-sdk-JAVA-20180104135049”这一版本,帮助开发者更好地理解和使用。 1. **SDK概述**: 支付宝SDK for Java是支付宝官方发布的,用于帮助Java开发者快速实现与支付宝接口的对接。2018年1月4...

    商业编程-源码-后台密码截获程序源代码.zip

    在IT行业中,编程是至关重要的一个领域,尤其在商业应用中。这个压缩包"商业编程-源码-后台密码截获程序源代码.zip"显然包含了用于理解和分析后台密码截获程序的源代码。这种程序通常涉及到网络安全、数据保护以及...

    微信支付-微信小程序+Java后台代码

    在这个项目中,我们结合了微信小程序和Java后台技术,实现了一个完整的微信支付流程。以下将详细阐述涉及的知识点: 1. **微信小程序**:微信小程序是一种轻量级的应用开发框架,允许开发者在微信内部创建和运行...

    计算机软件-商业源码-截获以太网数据,附源代码(NT 控制台程序 ).zip

    标题中的“计算机软件-商业源码-截获以太网数据,附源代码(NT 控制台程序 ).zip”指的是一个包含商业源码的软件项目,这个项目专注于捕获以太网网络数据,并且它是一个在Windows NT操作系统环境下运行的控制台应用...

    博客-”深入windows的关机消息截获-从XP到Win7的变化“测试文件

    这是博客“深入windows的关机消息截获-从XP到Win7的变化”的测试文件和测试日志,利用这个测试文件测试Windows的关机方案从XP到Win7到底发生了什么变化,分析得到如何进行有效的截获Windows关机消息。

    java-card-.rar_Java smartcard_java card_java 电子钱包_电子钱包_电子钱包java

    Java智能卡技术是一种在小型、安全的硬件设备上运行应用程序的技术,主要应用于金融交易、身份认证等场景。Java Card技术是Sun Microsystems(现为Oracle公司)于1990年代末推出的,它允许开发人员将Java应用程序...

    管道流说明和例子,例子很详细的

    但在某些情况下,如控制台窗口容量有限或使用`javaw`命令启动程序时,可能无法直接查看控制台输出。 - **应用场景**: - **调试辅助**:在开发阶段,通过截获控制台输出可以帮助开发者更方便地进行调试。 - **错误...

    用于聊天的libsignal-protocol-java的简单演示_Java_Shell_下载.zip

    这个“用于聊天的libsignal-protocol-java的简单演示_Java_Shell_下载.zip”文件看起来是一个包含示例代码的压缩包,帮助开发者了解如何在Java环境中使用libsignal-protocol-java来实现安全的聊天功能。 Signal协议...

    VC中截获按键消息

    在VC++编程环境中,截获按键消息是一项常见的需求,特别是在创建自定义用户界面或实现特殊键盘处理逻辑时。本文将深入讲解如何在VC++中截获按键消息,并以`WM_KEYUP`消息为例进行详细说明。 首先,我们需要了解...

    springboot-weapp_java_微信小程序_

    在本文中,我们将深入探讨如何使用Java开发支持文件上传的微信小程序服务端接口。微信小程序是一种轻量级的应用,可以在微信环境中运行,为用户提供便捷的服务。为了实现与后端服务器的交互,尤其是文件上传功能,...

    VB中截获shell程序的输出_转发

    start.hStdOutput = hWritePipe 和 start.hStdError = hWritePipe 将shell程序的标准输出和标准错误输出重定向到我们创建的管道`hWritePipe`。这意味着shell程序的所有输出都会被发送到这个管道,而不是默认的显示器...

Global site tag (gtag.js) - Google Analytics