Thinker

            - long way to go...

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            24 隨筆 :: 0 文章 :: 143 評論 :: 0 Trackbacks

          即使在圖形用戶界面占統(tǒng)治地位的今天,控制臺輸出仍舊在Java程序中占有重要地位。控制臺不僅是Java程序默認的堆棧跟蹤和錯誤信息輸出窗口,而且還 是一種實用的調試工具(特別是對習慣于使用println()的人來說)。然而,控制臺窗口有著許多局限。例如在Windows 9x平臺上,DOS控制臺只能容納50行輸出。如果Java程序一次性向控制臺輸出大量內容,要查看這些內容就很困難了。

          對于使用javaw這個啟動程序的開發(fā)者來說,控制臺窗口尤其寶貴。因為用javaw啟動java程序時,根本不會有控制臺窗口出現。如果程序遇到了問題 并拋出異常,根本無法查看Java運行時環(huán)境寫入到System.out或System.err的調用堆棧跟蹤信息。為了捕獲堆棧信息,一些人采取了用 try/catch()塊封裝main()的方式,但這種方式不一定總是有效,在Java運行時的某些時刻,一些描述性錯誤信息會在拋出異常之前被寫入 System.out和System.err;除非能夠監(jiān)測這兩個控制臺流,否則這些信息就無法看到。

          因此,有些時候檢查Java運行時環(huán)境(或第三方程序)寫入到控制臺流的數據并采取合適的操作是十分必要的。本文討論的主題之一就是創(chuàng)建這樣一個輸入流, 從這個輸入流中可以讀入以前寫入Java控制臺流(或任何其他程序的輸出流)的數據。我們可以想象寫入到輸出流的數據立即以輸入的形式“回流”到了 Java程序。

          本文的目標是設計一個基于Swing的文本窗口顯示控制臺輸出。在此期間,我們還將討論一些和Java管道流(PipedInputStream和 PipedOutputStream)有關的重要注意事項。圖一顯示了用來截取和顯示控制臺文本輸出的Java程序,用戶界面的核心是一個 JTextArea。最后,我們還要創(chuàng)建一個能夠捕獲和顯示其他程序(可以是非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的代碼穩(wěn)定地運行。下面我們來看第一個注意事項。

          1.1 注意事項一
          PipedInputStream運用的是一個1024字節(jié)固定大小的循環(huán)緩沖區(qū)。寫入PipedOutputStream的數據實際上保存到對應的 PipedInputStream的內部緩沖區(qū)。從PipedInputStream執(zhí)行讀操作時,讀取的數據實際上來自這個內部緩沖區(qū)。如果對應的 PipedInputStream輸入緩沖區(qū)已滿,任何企圖寫入PipedOutputStream的線程都將被阻塞。而且這個寫操作線程將一直阻塞,直 至出現讀取PipedInputStream的操作從緩沖區(qū)刪除數據。

          這意味著,向PipedOutputStream寫數據的線程不應該是負責從對應PipedInputStream讀取數據的唯一線程。從圖二可以清楚地 看出這里的問題所在:假設線程t是負責從PipedInputStream讀取數據的唯一線程;另外,假定t企圖在一次對 PipedOutputStream的write()方法的調用中向對應的PipedOutputStream寫入2000字節(jié)的數據。在t線程阻塞之 前,它最多能夠寫入1024字節(jié)的數據(PipedInputStream內部緩沖區(qū)的大小)。然而,一旦t被阻塞,讀取 PipedInputStream的操作就再也不會出現,因為t是唯一讀取PipedInputStream的線程。這樣,t線程已經完全被阻塞,同時, 所有其他試圖向PipedOutputStream寫入數據的線程也將遇到同樣的情形。



          圖二:管道流工作過程

          這并不意味著在一次write()調用中不能寫入多于1024字節(jié)的數據。但應當保證,在寫入數據的同時,有另一個線程從PipedInputStream讀取數據。

          Listing 2示范了這個問題。這個程序用一個線程交替地讀取PipedInputStream和寫入PipedOutputStream。每次調用write()向 PipedInputStream的緩沖區(qū)寫入20字節(jié),每次調用read()只從緩沖區(qū)讀取并刪除10個字節(jié)。內部緩沖區(qū)最終會被寫滿,導致寫操作阻 塞。由于我們用同一個線程執(zhí)行讀、寫操作,一旦寫操作被阻塞,就不能再從PipedInputStream讀取數據。

          【Listing 2:用同一個線程執(zhí)行讀/寫操作導致線程阻塞】
          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發(fā)送20字節(jié)數據
          pipedOS.write(outArray, 0, 20);
          System.out.println(" 已發(fā)送20字節(jié)...");
          // 在每一次循環(huán)迭代中,讀入10字節(jié)
          // 發(fā)送20字節(jié)
          bytesRead = pipedIS.read(inArray, 0, 10);
          int i=0;
          while(bytesRead != -1) {
          pipedOS.write(outArray, 0, 20);
          System.out.println(" 已發(fā)送20字節(jié)..."+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經過修改后的版本,它在一個單獨的線程中執(zhí)行寫入PipedOutputStream的操作(和讀取線程不同的線程)。為證明一次寫入的數據可以超過 1024字節(jié),我們讓寫操作線程每次調用PipedOutputStream的write()方法時寫入2000字節(jié)。那么,在 startWriterThread()方法中創(chuàng)建的線程是否會阻塞呢?按照Java運行時線程調度機制,它當然會阻塞。寫操作在阻塞之前實際上最多只能 寫入1024字節(jié)的有效載荷(即PipedInputStream緩沖區(qū)的大小)。但這并不會成為問題,因為主線程(main)很快就會從 PipedInputStream的循環(huán)緩沖區(qū)讀取數據,空出緩沖區(qū)空間。最終,寫操作線程會從上一次中止的地方重新開始,寫入2000字節(jié)有效載荷中的 剩余部分。

          【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 + "字節(jié)...");
          bytesRead = pipedIS.read(inArray, 0, 10);
          }
          }
          catch(IOException e) {
          System.err.println("讀取輸入錯誤.");
          System.exit(1);
          }
          } // main()
          // 創(chuàng)建一個獨立的線程
          // 執(zhí)行寫入PipedOutputStream的操作
          private static void startWriterThread() {
          new Thread(new Runnable() {
          public void run() {
          byte[] outArray = new byte[2000];
          while(true) { // 無終止條件的循環(huán)
          try {
          // 在該線程阻塞之前,有最多1024字節(jié)的數據被寫入
          pipedOS.write(outArray, 0, 2000);
          }
          catch(IOException e) {
          System.err.println("寫操作錯誤");
          System.exit(1);
          }
          System.out.println(" 已經發(fā)送2000字節(jié)...");
          }
          }
          }).start();
          } // startWriterThread()
          } // Listing3

          也許我們不能說這個問題是Java管道流設計上的缺陷,但在應用管道流時,它是一個必須密切注意的問題。下面我們來看看第二個更重要(更危險的)問題。

          1.2 注意事項二
          從PipedInputStream讀取數據時,如果符合下面三個條件,就會出現IOException異常:

          1. 試圖從PipedInputStream讀取數據,
          2. PipedInputStream的緩沖區(qū)為“空”(即不存在可讀取的數據),
          3. 最后一個向PipedOutputStream寫數據的線程不再活動(通過Thread.isAlive()檢測)。

          這是一個很微妙的時刻,同時也是一個極其重要的時刻。假定有一個線程w向PipedOutputStream寫入數據;另一個線程r從對應的 PipedInputStream讀取數據。下面一系列的事件將導致r線程在試圖讀取PipedInputStream時遇到IOException異 常:

          1. w向PipedOutputStream寫入數據。
          2. w結束(w.isAlive()返回false)。
          3. r從PipedInputStream讀取w寫入的數據,清空PipedInputStream的緩沖區(qū)。
          4. r試圖再次從PipedInputStream讀取數據。這時PipedInputStream的緩沖區(qū)已經為空,而且w已經結束,從而導致在讀操作執(zhí)行時出現IOException異常。

          構 造一個程序示范這個問題并不困難,只需從Listing 3的startWriterThread()方法中,刪除while(true)條件。這個改動阻止了執(zhí)行寫操作的方法循環(huán)執(zhí)行,使得執(zhí)行寫操作的方法在 一次寫入操作之后就結束運行。如前所述,此時主線程試圖讀取PipedInputStraem時,就會遇到一個IOException異常。

          這是一種比較少見的情況,而且不存在直接修正它的方法。請不要通過從管道流派生子類的方法修正該問題――在這里使用繼承是完全不合適的。而且,如果Sun以后改變了管道流的實現方法,現在所作的修改將不再有效。

          最后一個問題和第二個問題很相似,不同之處在于,它在讀線程(而不是寫線程)結束時產生IOException異常。

          1.3 注意事項三
          如果一個寫操作在PipedOutputStream上執(zhí)行,同時最近從對應PipedInputStream讀取的線程已經不再活動(通過 Thread.isAlive()檢測),則寫操作將拋出一個IOException異常。假定有兩個線程w和r,w向 PipedOutputStream寫入數據,而r則從對應的PipedInputStream讀取。下面一系列的事件將導致w線程在試圖寫入 PipedOutputStream時遇到IOException異常:

          1. 寫操作線程w已經創(chuàng)建,但r線程還不存在。
          2. w向PipedOutputStream寫入數據。
          3. 讀線程r被創(chuàng)建,并從PipedInputStream讀取數據。
          4. r線程結束。
          5. w企圖向PipedOutputStream寫入數據,發(fā)現r已經結束,拋出IOException異常。

          實際上,這個問題不象第二個問題那樣棘手。和多個讀線程/單個寫線程的情況相比,也許在應用中有一個讀線程(作為響應請求的服務器)和多個寫線程(發(fā)出請求)的情況更為常見。

          1.4 解決問題
          要防止管道流前兩個局限所帶來的問題,方法之一是用一個ByteArrayOutputStream作為代理或替代PipedOutputStream。 Listing 4顯示了一個LoopedStreams類,它用一個ByteArrayOutputStream提供和Java管道流類似的功能,但不會出現死鎖和 IOException異常。這個類的內部仍舊使用管道流,但隔離了本文介紹的前兩個問題。我們先來看看這個類的公用方法(參見圖3)。構造函數很簡單, 它連接管道流,然后調用startByteArrayReaderThread()方法(稍后再討論該方法)。getOutputStream()方法返 回一個OutputStream(具體地說,是一個ByteArrayOutputStream)用以替代PipedOutputStream。寫入該 OutputStream的數據最終將在getInputStream()方法返回的流中作為輸入出現。和使用PipedOutputStream的情形 不同,向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) {
          // 檢查流里面的字節(jié)數
          if(byteArrayOS.size() > 0) {
          byte[] buffer = null;
          synchronized(byteArrayOS) {
          buffer = byteArrayOS.toByteArray();
          byteArrayOS.reset(); // 清除緩沖區(qū)
          }
          try {
          // 把提取到的數據發(fā)送給PipedOutputStream
          pipedOS.write(buffer, 0, buffer.length);
          }
          catch(IOException e) {
          // 記錄錯誤或其他處理
          // 為簡單計,此處我們直接結束
          System.exit(1);
          }
          }
          else // 沒有數據可用,線程進入睡眠狀態(tài)
          try {
          // 每隔1秒查看ByteArrayOutputStream檢查新數據
          Thread.sleep(1000);
          }
          catch(InterruptedException e) {}
          }
          }
          }).start();
          } // startByteArrayReaderThread()
          } // LoopedStreams

          startByteArrayReaderThread()方法是整個類真正的關鍵所在。這個方法的目標很簡單,就是創(chuàng)建一個定期地檢查 ByteArrayOutputStream緩沖區(qū)的線程。緩沖區(qū)中找到的所有數據都被提取到一個byte數組,然后寫入到 PipedOutputStream。由于PipedOutputStream對應的PipedInputStream由getInputStream ()返回,從該輸入流讀取數據的線程都將讀取到原先發(fā)送給ByteArrayOutputStream的數據。前面提到,LoopedStreams類解 決了管道流存在的前二個問題,我們來看看這是如何實現的。

          ByteArrayOutputStream具有根據需要擴展其內部緩沖區(qū)的能力。由于存在“完全緩沖”,線程向getOutputStream()返回 的流寫入數據時不會被阻塞。因而,第一個問題不會再給我們帶來麻煩。另外還要順便說一句,ByteArrayOutputStream的緩沖區(qū)永遠不會縮 減。例如,假設在能夠提取數據之前,有一塊500 K的數據被寫入到流,緩沖區(qū)將永遠保持至少500 K的容量。如果這個類有一個方法能夠在數據被提取之后修正緩沖區(qū)的大小,它就會更完善。

          第二個問題得以解決的原因在于,實際上任何時候只有一個線程向PipedOutputStream寫入數據,這個線程就是由 startByteArrayReaderThread()創(chuàng)建的線程。由于這個線程完全由LoopedStreams類控制,我們不必擔心它會產生 IOException異常。

          LoopedStreams類還有一些細節(jié)值得提及。首先,我們可以看到byteArrayOS和pipedIS實際上分別是 ByteArrayOutputStream和PipedInputStream的派生類的實例,也即在它們的close()方法中加入了特殊的行為。如 果一個LoopedStreams對象的用戶關閉了輸入或輸出流,在startByteArrayReaderThread()中創(chuàng)建的線程必須關閉。覆 蓋后的close()方法把keepRunning標記設置成false以關閉線程。另外,請注意startByteArrayReaderThread ()中的同步塊。要確保在toByteArray()調用和reset()調用之間ByteArrayOutputStream緩沖區(qū)不被寫入流的線程修 改,這是必不可少的。由于ByteArrayOutputStream的write()方法的所有版本都在該流上同步,我們保證了 ByteArrayOutputStream的內部緩沖區(qū)不被意外地修改。

          注意LoopedStreams類并不涉及管道流的第三個問題。該類的getInputStream()方法返回PipedInputStream。如果 一個線程從該流讀取,一段時間后終止,下次數據從ByteArrayOutputStream緩沖區(qū)傳輸到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(
          "不能創(chuàng)建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()方法創(chuàng)建了一個JFrame,JFrame包含一個ConsoleTextArea的實例。這些代碼并沒有什么特別之處。Frame顯示出來 之后,main()方法啟動一系列的寫操作線程,寫操作線程向控制臺流輸出大量信息。ConsoleTextArea捕獲并顯示這些信息,如圖一所示。

          ConsoleTextArea提供了兩個構造函數。沒有參數的構造函數用來捕獲和顯示所有寫入到控制臺流的數據,有一個InputStream[]參數 的構造函數轉發(fā)所有從各個數組元素讀取的數據到JTextArea。稍后將有一個例子顯示這個構造函數的用處。首先我們來看看沒有參數的 ConsoleTextArea構造函數。這個函數首先創(chuàng)建一個LoopedStreams對象;然后請求Java運行時環(huán)境把控制臺輸出轉發(fā)到 LoopedStreams提供的OutputStream;最后,構造函數調用startConsoleReaderThread(),創(chuàng)建一個不斷地 把文本行追加到JTextArea的線程。注意,把文本追加到JTextArea之后,程序小心地保證了插入點的正確位置。

          一 般來說,Swing部件的更新不應該在AWT事件分派線程(AWT Event Dispatch Thread,AEDT)之外進行。對于本例來說,這意味著所有把文本追加到JTextArea的操作應該在AEDT中進行,而不是在 startConsoleReaderThread()方法創(chuàng)建的線程中進行。然而,事實上在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("創(chuàng)建進程時出錯...\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下執(zhí)行javaw.exe AppOutputCapture ping.exe www.yahoo.com,則結果如圖四所示。



          圖四:截取其他程序的控制臺輸出

          使用AppOutputCapture時應該注意,被截取輸出的應用程序最初輸出的一些文本可能無法截取。因為在調用Runtime.exec()和 ConsoleTextArea初始化完成之間存在一小段時間差。在這個時間差內,應用程序輸出的文本會丟失。當AppOutputCapture窗口被 關閉,process.destory()調用試圖關閉Java程序開始時創(chuàng)建的進程。測試結果顯示出,destroy()方法不一定總是有效(至少在 Windows 98上是這樣的)。似乎當待關閉的進程啟動了額外的進程時,則那些進程不會被關閉。此外,在這種情況下AppOutputCapture程序看起來未能正 常結束。但在Windows NT下,一切正常。如果用JDK v1.1.x運行AppOutputCapture,關閉窗口時會出現一個NullPointerException。這是一個JDK的Bug,JDK 1.2.x和JDK 1.3.x下就不會出現問題。



          原文地址: http://www.ibm.com/developerworks/cn/java/l-console/

          posted on 2007-06-16 22:14 Long 閱讀(5204) 評論(4)  編輯  收藏 所屬分類: Java

          評論

          # re: 在Java程序中截獲控制臺輸出(ZT)[未登錄] 2007-06-17 00:14 Long
          本帖是轉帖,原文請參見:http://www.ibm.com/developerworks/cn/java/l-console/  回復  更多評論
            

          # re: 在Java程序中截獲控制臺輸出(ZT)[未登錄] 2007-06-17 00:18 Long
          最近需要用到截獲Java的控制臺輸出,在網上搜到了這篇文章,轉帖在這給需要的朋友提供個方便  回復  更多評論
            

          # re: 在Java程序中截獲控制臺輸出(ZT) 2007-06-17 01:19 sitinspring
          真是活到老,學到老.  回復  更多評論
            

          # re: 在Java程序中截獲控制臺輸出(ZT) 2007-06-21 22:23 pass86
          好文,是個實用的 技術,在VC里也用過這樣的技術。  回復  更多評論
            

          主站蜘蛛池模板: 长白| 九龙坡区| 岫岩| 肇庆市| 镇康县| 梁河县| 靖州| 壶关县| 阳山县| 保亭| 讷河市| 新蔡县| 益阳市| 阳谷县| 章丘市| 汉川市| 浦江县| 万年县| 湘潭县| 东莞市| 招远市| 竹北市| 西乌珠穆沁旗| 迁安市| 南岸区| 金湖县| 阜城县| 白玉县| 景谷| 鱼台县| 沙雅县| 麻城市| 马尔康县| 且末县| 沽源县| 津市市| 阳西县| 夏河县| 元朗区| 巴楚县| 毕节市|