转至繁体中文版     | 网站首页 | 图文教程 | 资源下载 | 站长博客 | 图片素材 | 武汉seo | 武汉网站优化 | 
最新公告:     敏韬网|教学资源学习资料永久免费分享站!  [mintao  2008年9月2日]        
您现在的位置: 学习笔记 >> 图文教程 >> 软件开发 >> JAVA开发 >> 正文
J2SE综合:在Java程序中截获控制台输出         ★★★★

J2SE综合:在Java程序中截获控制台输出

作者:闵涛 文章来源:闵涛的学习笔记 点击数:1250 更新时间:2009/4/22 23:33:35

  作者:baocl

  在Java开发中,控制台输出仍是一个重要的工具,但默认的控制台输出有着各种各样的局限。本文介绍如何用Java管道流截取控制台输出,分析管道流应用中应该注意的问题,提供了截取Java程序和非Java程序控制台输出的实例。

  即使在图形用户界面占统治地位的今天,控制台输出仍旧在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异常。


没有相关教程
教程录入:mintao    责任编辑:mintao 
  • 上一篇教程:

  • 下一篇教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      注:本站部分文章源于互联网,版权归原作者所有!如有侵权,请原作者与本站联系,本站将立即删除! 本站文章除特别注明外均可转载,但需注明出处! [MinTao学以致用网]
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    同类栏目
    · C语言系列  · VB.NET程序
    · JAVA开发  · Delphi程序
    · 脚本语言
    更多内容
    热门推荐 更多内容
  • 没有教程
  • 赞助链接
    更多内容
    闵涛博文 更多关于武汉SEO的内容
    500 - 内部服务器错误。

    500 - 内部服务器错误。

    您查找的资源存在问题,因而无法显示。

    | 设为首页 |加入收藏 | 联系站长 | 友情链接 | 版权申明 | 广告服务
    MinTao学以致用网

    Copyright @ 2007-2012 敏韬网(敏而好学,文韬武略--MinTao.Net)(学习笔记) Inc All Rights Reserved.
    闵涛 投放广告、内容合作请Q我! E_mail:admin@mintao.net(欢迎提供学习资源)

    站长:MinTao ICP备案号:鄂ICP备11006601号-18

    闵涛站盟:医药大全-武穴网A打造BCD……
    咸宁网络警察报警平台