青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

逛奔的蝸牛

我不聰明,但我會(huì)很努力

   ::  :: 新隨筆 ::  ::  :: 管理 ::
From: http://www.ibm.com/developerworks/cn/java/l-console/
在Java開(kāi)發(fā)中,控制臺(tái)輸出仍是一個(gè)重要的工具,但默認(rèn)的控制臺(tái)輸出有著各種各樣的局限。本文介紹如何用Java管道流截取控制臺(tái)輸出,分析管道流應(yīng)用中應(yīng)該注意的問(wèn)題,提供了截取Java程序和非Java程序控制臺(tái)輸出的實(shí)例。

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

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

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

本文的目標(biāo)是設(shè)計(jì)一個(gè)基于Swing的文本窗口顯示控制臺(tái)輸出。在此期間,我們還將討論一些和Java管道流(PipedInputStream和PipedOutputStream)有關(guān)的重要注意事項(xiàng)。圖一顯示了用來(lái)截取和顯示控制臺(tái)文本輸出的Java程序,用戶界面的核心是一個(gè)JTextArea。最后,我們還要?jiǎng)?chuàng)建一個(gè)能夠捕獲和顯示其他程序(可以是非Java的程序)控制臺(tái)輸出的簡(jiǎn)單程序。


圖一:多線程的控制臺(tái)輸出截取程序
圖一:多線程的控制臺(tái)輸出截取程序 

一、Java管道流

要在文本框中顯示控制臺(tái)輸出,我們必須用某種方法“截取”控制臺(tái)流。換句話說(shuō),我們要有一種高效地讀取寫(xiě)入到System.out和System.err所有內(nèi)容的方法。如果你熟悉Java的管道流PipedInputStream和PipedOutputStream,就會(huì)相信我們已經(jīng)擁有最有效的工具。

寫(xiě)入到PipedOutputStream輸出流的數(shù)據(jù)可以從對(duì)應(yīng)的PipedInputStream輸入流讀取。Java的管道流極大地方便了我們截取控制臺(tái)輸出。Listing 1顯示了一種非常簡(jiǎn)單的截取控制臺(tái)輸出方案。

【Listing 1:用管道流截取控制臺(tái)輸出】
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);

可以看到,這里的代碼極其簡(jiǎn)單。我們只是建立了一個(gè)PipedInputStream,把它設(shè)置為所有寫(xiě)入控制臺(tái)流的數(shù)據(jù)的最終目的地。所有寫(xiě)入到控制臺(tái)流的數(shù)據(jù)都被轉(zhuǎn)到PipedOutputStream,這樣,從相應(yīng)的PipedInputStream讀取就可以迅速地截獲所有寫(xiě)入控制臺(tái)流的數(shù)據(jù)。接下來(lái)的事情似乎只剩下在Swing JTextArea中顯示從pipedIS流讀取的數(shù)據(jù),得到一個(gè)能夠在文本框中顯示控制臺(tái)輸出的程序。遺憾的是,在使用Java管道流時(shí)有一些重要的注意事項(xiàng)。只有認(rèn)真對(duì)待所有這些注意事項(xiàng)才能保證Listing 1的代碼穩(wěn)定地運(yùn)行。下面我們來(lái)看第一個(gè)注意事項(xiàng)。

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

這意味著,向PipedOutputStream寫(xiě)數(shù)據(jù)的線程不應(yīng)該是負(fù)責(zé)從對(duì)應(yīng)PipedInputStream讀取數(shù)據(jù)的唯一線程。從圖二可以清楚地看出這里的問(wèn)題所在:假設(shè)線程t是負(fù)責(zé)從PipedInputStream讀取數(shù)據(jù)的唯一線程;另外,假定t企圖在一次對(duì)PipedOutputStream的write()方法的調(diào)用中向?qū)?yīng)的PipedOutputStream寫(xiě)入2000字節(jié)的數(shù)據(jù)。在t線程阻塞之前,它最多能夠?qū)懭?024字節(jié)的數(shù)據(jù)(PipedInputStream內(nèi)部緩沖區(qū)的大小)。然而,一旦t被阻塞,讀取PipedInputStream的操作就再也不會(huì)出現(xiàn),因?yàn)閠是唯一讀取PipedInputStream的線程。這樣,t線程已經(jīng)完全被阻塞,同時(shí),所有其他試圖向PipedOutputStream寫(xiě)入數(shù)據(jù)的線程也將遇到同樣的情形。


圖二:管道流工作過(guò)程
圖二:管道流工作過(guò)程  

這并不意味著在一次write()調(diào)用中不能寫(xiě)入多于1024字節(jié)的數(shù)據(jù)。但應(yīng)當(dāng)保證,在寫(xiě)入數(shù)據(jù)的同時(shí),有另一個(gè)線程從PipedInputStream讀取數(shù)據(jù)。

Listing 2示范了這個(gè)問(wèn)題。這個(gè)程序用一個(gè)線程交替地讀取PipedInputStream和寫(xiě)入PipedOutputStream。每次調(diào)用write()向PipedInputStream的緩沖區(qū)寫(xiě)入20字節(jié),每次調(diào)用read()只從緩沖區(qū)讀取并刪除10個(gè)字節(jié)。內(nèi)部緩沖區(qū)最終會(huì)被寫(xiě)滿,導(dǎo)致寫(xiě)操作阻塞。由于我們用同一個(gè)線程執(zhí)行讀、寫(xiě)操作,一旦寫(xiě)操作被阻塞,就不能再?gòu)腜ipedInputStream讀取數(shù)據(jù)。

【Listing 2:用同一個(gè)線程執(zhí)行讀/寫(xiě)操作導(dǎo)致線程阻塞】
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é)數(shù)據(jù)
            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時(shí)出現(xiàn)錯(cuò)誤: " + e);
                System.exit(1);
        }
    } // main()
}

只要把讀/寫(xiě)操作分開(kāi)到不同的線程,Listing 2的問(wèn)題就可以輕松地解決。Listing 3是Listing 2經(jīng)過(guò)修改后的版本,它在一個(gè)單獨(dú)的線程中執(zhí)行寫(xiě)入PipedOutputStream的操作(和讀取線程不同的線程)。為證明一次寫(xiě)入的數(shù)據(jù)可以超過(guò)1024字節(jié),我們讓寫(xiě)操作線程每次調(diào)用PipedOutputStream的write()方法時(shí)寫(xiě)入2000字節(jié)。那么,在startWriterThread()方法中創(chuàng)建的線程是否會(huì)阻塞呢?按照J(rèn)ava運(yùn)行時(shí)線程調(diào)度機(jī)制,它當(dāng)然會(huì)阻塞。寫(xiě)操作在阻塞之前實(shí)際上最多只能寫(xiě)入1024字節(jié)的有效載荷(即PipedInputStream緩沖區(qū)的大小)。但這并不會(huì)成為問(wèn)題,因?yàn)橹骶€程(main)很快就會(huì)從PipedInputStream的循環(huán)緩沖區(qū)讀取數(shù)據(jù),空出緩沖區(qū)空間。最終,寫(xiě)操作線程會(huì)從上一次中止的地方重新開(kāi)始,寫(xiě)入2000字節(jié)有效載荷中的剩余部分。

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

也許我們不能說(shuō)這個(gè)問(wèn)題是Java管道流設(shè)計(jì)上的缺陷,但在應(yīng)用管道流時(shí),它是一個(gè)必須密切注意的問(wèn)題。下面我們來(lái)看看第二個(gè)更重要(更危險(xiǎn)的)問(wèn)題。

1.2 注意事項(xiàng)二 
從PipedInputStream讀取數(shù)據(jù)時(shí),如果符合下面三個(gè)條件,就會(huì)出現(xiàn)IOException異常:

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

這是一個(gè)很微妙的時(shí)刻,同時(shí)也是一個(gè)極其重要的時(shí)刻。假定有一個(gè)線程w向PipedOutputStream寫(xiě)入數(shù)據(jù);另一個(gè)線程r從對(duì)應(yīng)的PipedInputStream讀取數(shù)據(jù)。下面一系列的事件將導(dǎo)致r線程在試圖讀取PipedInputStream時(shí)遇到IOException異常:

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

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

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

最后一個(gè)問(wèn)題和第二個(gè)問(wèn)題很相似,不同之處在于,它在讀線程(而不是寫(xiě)線程)結(jié)束時(shí)產(chǎn)生IOException異常。

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

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

實(shí)際上,這個(gè)問(wèn)題不象第二個(gè)問(wèn)題那樣棘手。和多個(gè)讀線程/單個(gè)寫(xiě)線程的情況相比,也許在應(yīng)用中有一個(gè)讀線程(作為響應(yīng)請(qǐng)求的服務(wù)器)和多個(gè)寫(xiě)線程(發(fā)出請(qǐng)求)的情況更為常見(jiàn)。

1.4 解決問(wèn)題 
要防止管道流前兩個(gè)局限所帶來(lái)的問(wèn)題,方法之一是用一個(gè)ByteArrayOutputStream作為代理或替代PipedOutputStream。Listing 4顯示了一個(gè)LoopedStreams類,它用一個(gè)ByteArrayOutputStream提供和Java管道流類似的功能,但不會(huì)出現(xiàn)死鎖和IOException異常。這個(gè)類的內(nèi)部仍舊使用管道流,但隔離了本文介紹的前兩個(gè)問(wèn)題。我們先來(lái)看看這個(gè)類的公用方法(參見(jiàn)圖3)。構(gòu)造函數(shù)很簡(jiǎn)單,它連接管道流,然后調(diào)用startByteArrayReaderThread()方法(稍后再討論該方法)。getOutputStream()方法返回一個(gè)OutputStream(具體地說(shuō),是一個(gè)ByteArrayOutputStream)用以替代PipedOutputStream。寫(xiě)入該OutputStream的數(shù)據(jù)最終將在getInputStream()方法返回的流中作為輸入出現(xiàn)。和使用PipedOutputStream的情形不同,向ByteArrayOutputStream寫(xiě)入數(shù)據(jù)的線程的激活、寫(xiě)數(shù)據(jù)、結(jié)束不會(huì)帶來(lái)負(fù)面效果。


圖三:ByteArrayOutputStream原理
圖三:ByteArrayOutputStream原理  
【Listing 4:防止管道流應(yīng)用中出現(xiàn)的常見(jiàn)問(wèn)題】
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) {
                // 記錄錯(cuò)誤或其他處理
                // 為簡(jiǎn)單計(jì),此處我們直接結(jié)束
                System.exit(1);
            }
        }
    };
    private PipedInputStream pipedIS = new PipedInputStream() {
        public void close() {
            keepRunning = false;
            try    {
                super.close();
            }
            catch(IOException e) {
                // 記錄錯(cuò)誤或其他處理
                // 為簡(jiǎn)單計(jì),此處我們直接結(jié)束
                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é)數(shù)
                    if(byteArrayOS.size() > 0) {
                        byte[] buffer = null;
                        synchronized(byteArrayOS) {
                            buffer = byteArrayOS.toByteArray();
                            byteArrayOS.reset(); // 清除緩沖區(qū)
                        }
                        try {
                            // 把提取到的數(shù)據(jù)發(fā)送給PipedOutputStream
                            pipedOS.write(buffer, 0, buffer.length);
                        }
                        catch(IOException e) {
                            // 記錄錯(cuò)誤或其他處理
                            // 為簡(jiǎn)單計(jì),此處我們直接結(jié)束
                            System.exit(1);
                        }
                    }
                    else // 沒(méi)有數(shù)據(jù)可用,線程進(jìn)入睡眠狀態(tài)
                        try {
                            // 每隔1秒查看ByteArrayOutputStream檢查新數(shù)據(jù)
                            Thread.sleep(1000);
                        }
                        catch(InterruptedException e) {}
                    }
             }
        }).start();
    } // startByteArrayReaderThread()
} // LoopedStreams

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

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

第二個(gè)問(wèn)題得以解決的原因在于,實(shí)際上任何時(shí)候只有一個(gè)線程向PipedOutputStream寫(xiě)入數(shù)據(jù),這個(gè)線程就是由startByteArrayReaderThread()創(chuàng)建的線程。由于這個(gè)線程完全由LoopedStreams類控制,我們不必?fù)?dān)心它會(huì)產(chǎn)生IOException異常。

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

注意LoopedStreams類并不涉及管道流的第三個(gè)問(wèn)題。該類的getInputStream()方法返回PipedInputStream。如果一個(gè)線程從該流讀取,一段時(shí)間后終止,下次數(shù)據(jù)從ByteArrayOutputStream緩沖區(qū)傳輸?shù)絇ipedOutputStream時(shí)就會(huì)出現(xiàn)IOException異常。





回頁(yè)首


二、捕獲Java控制臺(tái)輸出

Listing 5的ConsoleTextArea類擴(kuò)展Swing JTextArea捕獲控制臺(tái)輸出。不要對(duì)這個(gè)類有這么多代碼感到驚訝,必須指出的是,ConsoleTextArea類有超過(guò)50%的代碼用來(lái)進(jìn)行測(cè)試。

【Listing 5:截獲Java控制臺(tái)輸出】
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讀取錯(cuò)誤:" + e);
                    System.exit(1);
                }
            }
        }).start();
    } // startConsoleReaderThread()
    // 該類剩余部分的功能是進(jìn)行測(cè)試
    public static void main(String[] args) {
        JFrame f = new JFrame("ConsoleTextArea測(cè)試");
        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);
            }
        });
        // 啟動(dòng)幾個(gè)寫(xiě)操作線程向
        // System.out和System.err輸出
        startWriterTestThread(
            "寫(xiě)操作線程 #1", System.err, 920, 50);
        startWriterTestThread(
            "寫(xiě)操作線程 #2", System.out, 500, 50);
        startWriterTestThread(
            "寫(xiě)操作線程 #3", System.out, 200, 50);
        startWriterTestThread(
            "寫(xiě)操作線程 #4", System.out, 1000, 50);
        startWriterTestThread(
            "寫(xiě)操作線程 #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)建了一個(gè)JFrame,JFrame包含一個(gè)ConsoleTextArea的實(shí)例。這些代碼并沒(méi)有什么特別之處。Frame顯示出來(lái)之后,main()方法啟動(dòng)一系列的寫(xiě)操作線程,寫(xiě)操作線程向控制臺(tái)流輸出大量信息。ConsoleTextArea捕獲并顯示這些信息,如圖一所示。

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

一般來(lái)說(shuō),Swing部件的更新不應(yīng)該在AWT事件分派線程(AWT Event Dispatch Thread,AEDT)之外進(jìn)行。對(duì)于本例來(lái)說(shuō),這意味著所有把文本追加到JTextArea的操作應(yīng)該在AEDT中進(jìn)行,而不是在startConsoleReaderThread()方法創(chuàng)建的線程中進(jìn)行。然而,事實(shí)上在Swing中向JTextArea追加文本是一個(gè)線程安全的操作。讀取一行文本之后,我們只需調(diào)用JText.append()就可以把文本追加到JTextArea的末尾。





回頁(yè)首


三、捕獲其他程序的控制臺(tái)輸出

在JTextArea中捕獲Java程序自己的控制臺(tái)輸出是一回事,去捕獲其他程序(甚至包括一些非Java程序)的控制臺(tái)數(shù)據(jù)又是另一回事。ConsoleTextArea提供了捕獲其他應(yīng)用的輸出時(shí)需要的基礎(chǔ)功能,Listing 6的AppOutputCapture利用ConsoleTextArea,截取其他應(yīng)用的輸出信息然后顯示在ConsoleTextArea中。

【Listing 6:截獲其他程序的控制臺(tái)輸出】
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 " +
                 "<程序名字> {參數(shù)1 參數(shù)2 ...}");
             System.exit(0);
                }
                try {
                        // 啟動(dòng)命令行指定程序的新進(jìn)程
                        process = Runtime.getRuntime().exec(args);
                }
                catch(IOException e) {
                        System.err.println("創(chuàng)建進(jìn)程時(shí)出錯(cuò)...\n" + e);
                        System.exit(1);
                }
                // 獲得新進(jìn)程所寫(xiě)入的流
                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] +
                        "控制臺(tái)輸出");
                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的工作過(guò)程如下:首先利用Runtime.exec()方法啟動(dòng)指定程序的一個(gè)新進(jìn)程。啟動(dòng)新進(jìn)程之后,從結(jié)果Process對(duì)象得到它的控制臺(tái)流。之后,把這些控制臺(tái)流傳入ConsoleTextArea(InputStream[])構(gòu)造函數(shù)(這就是帶參數(shù)ConsoleTextArea構(gòu)造函數(shù)的用處)。使用AppOutputCapture時(shí),在命令行上指定待截取其輸出的程序名字。例如,如果在Windows 2000下執(zhí)行javaw.exe AppOutputCapture ping.exe www.yahoo.com,則結(jié)果如圖四所示。


圖四:截取其他程序的控制臺(tái)輸出
圖四:截取其他程序的控制臺(tái)輸出 

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

請(qǐng)從這里下載本文完整代碼: JavaConsoleOutput_code.zip

posted on 2009-07-14 14:17 逛奔的蝸牛 閱讀(680) 評(píng)論(1)  編輯 收藏 引用 所屬分類: Java

評(píng)論

# re: Java: 在Java程序中截獲控制臺(tái)輸出 2009-08-19 16:30
剛好用到,謝謝共享  回復(fù)  更多評(píng)論
  

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产美女诱惑一区二区| 一本一本a久久| 亚洲精品日韩综合观看成人91| 久久综合福利| 久久香蕉国产线看观看网| 久久精品欧美日韩精品| 欧美99在线视频观看| 欧美黄在线观看| 一本大道久久a久久综合婷婷| 亚洲小视频在线观看| 欧美一区亚洲一区| 蜜臀久久久99精品久久久久久| 欧美福利视频| 国产女人精品视频| 最新成人av网站| 香蕉成人伊视频在线观看| 看片网站欧美日韩| 日韩一级裸体免费视频| 欧美一区二区私人影院日本 | 销魂美女一区二区三区视频在线| 亚洲欧美怡红院| 欧美刺激午夜性久久久久久久| 国产精品av久久久久久麻豆网| 国产一区二区三区在线观看免费| 91久久精品国产91久久| 香蕉成人伊视频在线观看| 欧美国产综合| 欧美亚洲综合久久| 欧美日韩99| 亚洲电影欧美电影有声小说| 一区二区三区欧美成人| 久久亚洲春色中文字幕| 夜夜嗨av一区二区三区四季av| 久久久久九九视频| 国产乱人伦精品一区二区| 亚洲伦理网站| 欧美电影免费观看| 久久成年人视频| 国产精品日韩在线播放| 一区二区三区视频在线看| 亚洲国产高清高潮精品美女| 一区二区三区国产| 欧美精品在线极品| 亚洲人成精品久久久久| 免费久久精品视频| 久久国产一区二区| 国产一区激情| 久久av最新网址| 亚洲欧美国产日韩中文字幕| 国产精品久久福利| 亚洲免费人成在线视频观看| 亚洲精品乱码久久久久久黑人| 免费视频一区二区三区在线观看| 一色屋精品视频在线观看网站 | 国产伦精品一区二区三区高清| 欧美不卡激情三级在线观看| 国产日本欧美一区二区三区| 亚洲资源在线观看| 亚洲美女黄色| 欧美亚男人的天堂| 亚洲专区在线视频| 亚洲午夜精品一区二区| 欧美性视频网站| 性欧美暴力猛交69hd| 亚洲自拍偷拍福利| 国产欧美一区二区精品秋霞影院 | 夜夜嗨av一区二区三区四区| 欧美日韩在线视频一区| 一区二区久久久久| 亚洲免费av观看| 国产精品国产一区二区 | 亚洲美女电影在线| 亚洲国产导航| 欧美性色aⅴ视频一区日韩精品| 亚洲一区尤物| 亚洲欧美中文另类| 国产在线日韩| 欧美成人高清视频| 欧美精品国产一区| 亚洲欧美日韩在线播放| 午夜视频在线观看一区二区| 极品尤物av久久免费看| 亚洲第一网站| 国产精品久久久久久久浪潮网站 | 日韩午夜在线| 亚洲午夜极品| 亚洲第一久久影院| 亚洲精选在线观看| 国产日韩欧美日韩大片| 欧美二区在线| 国产精品日日摸夜夜添夜夜av | 亚洲国产精品嫩草影院| 欧美另类极品videosbest最新版本| 一区二区三欧美| 久久黄色网页| 正在播放亚洲| 久久久天天操| 亚洲国产你懂的| 欧美亚男人的天堂| 蜜月aⅴ免费一区二区三区 | 欧美一区二区三区免费视| 久久精品欧美| 亚洲私人影院在线观看| 久久久久久一区二区| 亚洲字幕一区二区| 午夜精品久久久| 久久视频免费观看| 99成人在线| 久久九九精品| 欧美伊人久久| 欧美人与禽猛交乱配| 看片网站欧美日韩| 国产精品女人网站| 最新国产乱人伦偷精品免费网站| 国产欧美日韩麻豆91| 亚洲三级性片| 亚洲国产精品va在线观看黑人 | av成人免费| 亚洲国产另类久久久精品极度| 亚洲一区二区三区久久| 亚洲无毛电影| 欧美日韩精品中文字幕| 亚洲成人直播| 亚洲丰满少妇videoshd| 久久精品国产99国产精品澳门| 亚洲综合色自拍一区| 欧美视频一二三区| 99国产精品| 99国产成+人+综合+亚洲欧美| 久久夜精品va视频免费观看| 久久理论片午夜琪琪电影网| 国产日产亚洲精品| 午夜一级久久| 久久久另类综合| 国产亚洲精品激情久久| 午夜精品久久久久久久99黑人| 亚洲综合激情| 国产精品日韩欧美一区| 亚洲一区中文| 久久国产精品99久久久久久老狼| 欧美亚洲不卡| 亚洲免费影视| 久久精品视频在线播放| 国产一区二区| 久久青草久久| 亚洲国产精品久久久久婷婷884 | 亚洲一本大道在线| 亚洲欧美成人综合| 国产精品免费区二区三区观看| 亚洲五月婷婷| 久久久久91| 亚洲国产日韩美| 欧美另类视频| 在线一区亚洲| 久久成人在线| 亚洲国产精品悠悠久久琪琪| 蜜臀av一级做a爰片久久| 亚洲精品日产精品乱码不卡| 亚洲综合精品| 一区福利视频| 欧美激情欧美激情在线五月| 一区二区国产在线观看| 久久天堂av综合合色| 日韩一区二区福利| 国产欧美一区二区三区国产幕精品| 久久成人免费网| 亚洲国产激情| 先锋亚洲精品| 亚洲婷婷免费| 亚洲曰本av电影| 影音先锋中文字幕一区| 欧美日本亚洲| 久久久久九九九| 亚洲一区二区黄| 亚洲高清不卡| 久久精品国产69国产精品亚洲 | 久久精品亚洲| aa成人免费视频| 国模精品一区二区三区色天香| 欧美激情亚洲综合一区| 午夜精品久久久久久久久久久久| 欧美激情a∨在线视频播放| 亚洲免费一级电影| 亚洲精品一区二| 国产综合视频| 国产精品啊v在线| 欧美大片专区| 久久亚洲国产精品一区二区| 亚洲欧美在线视频观看| 中日韩高清电影网| 91久久夜色精品国产九色| 久久欧美肥婆一二区| 性欧美精品高清| 宅男精品导航| 99视频国产精品免费观看| 亚洲国产成人在线播放| 国产一区二区三区四区| 国产精品人成在线观看免费 | 一片黄亚洲嫩模| 亚洲欧洲精品一区二区三区不卡|