服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java截获标准输出(2)


  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[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(
"不能创建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追加文本是一个线程安全的操作。读取一行

扫描关注微信公众号