• <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>

            逛奔的蝸牛

            我不聰明,但我會很努力

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

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

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

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

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


            圖一:多線程的控制臺輸出截取程序
            圖一:多線程的控制臺輸出截取程序 

            一、Java管道流

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

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

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

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


            圖二:管道流工作過程
            圖二:管道流工作過程  

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

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

            【Listing 2:用同一個線程執(zhí)行讀/寫操作導(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時出現(xiàn)錯誤: " + e);
                            System.exit(1);
                    }
                } // main()
            }

            只要把讀/寫操作分開到不同的線程,Listing 2的問題就可以輕松地解決。Listing 3是Listing 2經(jīng)過修改后的版本,它在一個單獨的線程中執(zhí)行寫入PipedOutputStream的操作(和讀取線程不同的線程)。為證明一次寫入的數(shù)據(jù)可以超過1024字節(jié),我們讓寫操作線程每次調(diào)用PipedOutputStream的write()方法時寫入2000字節(jié)。那么,在startWriterThread()方法中創(chuàng)建的線程是否會阻塞呢?按照J(rèn)ava運(yùn)行時線程調(diào)度機(jī)制,它當(dāng)然會阻塞。寫操作在阻塞之前實際上最多只能寫入1024字節(jié)的有效載荷(即PipedInputStream緩沖區(qū)的大小)。但這并不會成為問題,因為主線程(main)很快就會從PipedInputStream的循環(huán)緩沖區(qū)讀取數(shù)據(jù),空出緩沖區(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("已經(jīng)讀取" +
                                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é)的數(shù)據(jù)被寫入
                                    pipedOS.write(outArray, 0, 2000);
                                }
                                catch(IOException e) {
                                    System.err.println("寫操作錯誤");
                                    System.exit(1);
                                }
                                System.out.println("     已經(jīng)發(fā)送2000字節(jié)...");
                            }
                        }
                    }).start();
                } // startWriterThread()
            } // Listing3

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

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

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

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

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

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

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

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

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

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

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

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


            圖三:ByteArrayOutputStream原理
            圖三:ByteArrayOutputStream原理  
            【Listing 4:防止管道流應(yīng)用中出現(xià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) {
                            // 記錄錯誤或其他處理
                            // 為簡單計,此處我們直接結(jié)束
                            System.exit(1);
                        }
                    }
                };
                private PipedInputStream pipedIS = new PipedInputStream() {
                    public void close() {
                        keepRunning = false;
                        try    {
                            super.close();
                        }
                        catch(IOException e) {
                            // 記錄錯誤或其他處理
                            // 為簡單計,此處我們直接結(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) {
                                        // 記錄錯誤或其他處理
                                        // 為簡單計,此處我們直接結(jié)束
                                        System.exit(1);
                                    }
                                }
                                else // 沒有數(shù)據(jù)可用,線程進(jìn)入睡眠狀態(tài)
                                    try {
                                        // 每隔1秒查看ByteArrayOutputStream檢查新數(shù)據(jù)
                                        Thread.sleep(1000);
                                    }
                                    catch(InterruptedException e) {}
                                }
                         }
                    }).start();
                } // startByteArrayReaderThread()
            } // LoopedStreams

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

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

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

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

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





            回頁首


            二、捕獲Java控制臺輸出

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

            【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()
                // 該類剩余部分的功能是進(jìn)行測試
                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提供了兩個構(gòu)造函數(shù)。沒有參數(shù)的構(gòu)造函數(shù)用來捕獲和顯示所有寫入到控制臺流的數(shù)據(jù),有一個InputStream[]參數(shù)的構(gòu)造函數(shù)轉(zhuǎn)發(fā)所有從各個數(shù)組元素讀取的數(shù)據(jù)到JTextArea。稍后將有一個例子顯示這個構(gòu)造函數(shù)的用處。首先我們來看看沒有參數(shù)的ConsoleTextArea構(gòu)造函數(shù)。這個函數(shù)首先創(chuàng)建一個LoopedStreams對象;然后請求Java運(yùn)行時環(huán)境把控制臺輸出轉(zhuǎn)發(fā)到LoopedStreams提供的OutputStream;最后,構(gòu)造函數(shù)調(diào)用startConsoleReaderThread(),創(chuàng)建一個不斷地把文本行追加到JTextArea的線程。注意,把文本追加到JTextArea之后,程序小心地保證了插入點的正確位置。

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





            回頁首


            三、捕獲其他程序的控制臺輸出

            在JTextArea中捕獲Java程序自己的控制臺輸出是一回事,去捕獲其他程序(甚至包括一些非Java程序)的控制臺數(shù)據(jù)又是另一回事。ConsoleTextArea提供了捕獲其他應(yīng)用的輸出時需要的基礎(chǔ)功能,Listing 6的AppOutputCapture利用ConsoleTextArea,截取其他應(yīng)用的輸出信息然后顯示在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 " +
                             "<程序名字> {參數(shù)1 參數(shù)2 ...}");
                         System.exit(0);
                            }
                            try {
                                    // 啟動命令行指定程序的新進(jìn)程
                                    process = Runtime.getRuntime().exec(args);
                            }
                            catch(IOException e) {
                                    System.err.println("創(chuàng)建進(jìn)程時出錯...\n" + e);
                                    System.exit(1);
                            }
                            // 獲得新進(jìn)程所寫入的流
                            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()方法啟動指定程序的一個新進(jìn)程。啟動新進(jìn)程之后,從結(jié)果Process對象得到它的控制臺流。之后,把這些控制臺流傳入ConsoleTextArea(InputStream[])構(gòu)造函數(shù)(這就是帶參數(shù)ConsoleTextArea構(gòu)造函數(shù)的用處)。使用AppOutputCapture時,在命令行上指定待截取其輸出的程序名字。例如,如果在Windows 2000下執(zhí)行javaw.exe AppOutputCapture ping.exe www.yahoo.com,則結(jié)果如圖四所示。


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

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

            請從這里下載本文完整代碼: JavaConsoleOutput_code.zip

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

            評論

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

            伊人久久国产免费观看视频| 国产欧美久久久精品影院| 久久婷婷色综合一区二区| 国产高潮国产高潮久久久| 久久频这里精品99香蕉久| 亚洲国产成人乱码精品女人久久久不卡| 精品综合久久久久久97超人| 久久久无码精品亚洲日韩蜜臀浪潮 | 久久久久人妻精品一区| 99蜜桃臀久久久欧美精品网站 | 久久精品国产黑森林| 国产精品欧美久久久久无广告 | 婷婷久久精品国产| 久久久久九国产精品| 久久青青草视频| 777午夜精品久久av蜜臀| 无码人妻久久一区二区三区免费| A级毛片无码久久精品免费| 国产亚洲精久久久久久无码| 91久久香蕉国产熟女线看| 久久精品国产99久久久香蕉| 亚洲?V乱码久久精品蜜桃| 久久精品中文无码资源站| 国产亚洲综合久久系列| 久久免费视频6| 一本一本久久a久久综合精品蜜桃| 日韩人妻无码精品久久免费一| 俺来也俺去啦久久综合网| 久久99久久成人免费播放| 99久久国产综合精品女同图片 | 久久久国产一区二区三区| 亚洲va久久久噜噜噜久久狠狠| 97久久香蕉国产线看观看| 欧美性猛交xxxx免费看久久久| 久久精品中文无码资源站| 久久久久久久亚洲精品| 久久久久人妻一区精品色| 亚洲中文字幕伊人久久无码| 99久久精品费精品国产一区二区| 久久久国产99久久国产一| 色综合久久综合网观看|