溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java截獲標準輸出(2)(轉)

發布時間:2020-08-06 18:43:38 來源:ITPUB博客 閱讀:145 作者:ba 欄目:編程語言
java截獲標準輸出(2)(轉)[@more@]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);
} // 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(´ ´).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("創建進程時出錯... " + 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下就不會出現問題。

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

參考:

Java 技巧 14:在 Java 中對標準流進行重定向
Java 技巧 33:再談對流進行重定向
編寫多線程的Java 應用程序 如何避免當前編程中最常見的問題
Java 程序中的多線程


關于作者
俞良松,軟件工程師,獨立顧問和自由撰稿人。最初從事PB和Oracle開發,現主要興趣在于Internet開發。您可以通過 javaman@163.net 和我聯系。
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女