- 自定义GUI控制台的源码学习
- 思路:
- 创建这样一个输入流,从这个输入流中可以读入以前写入Java控制台流(或任何其他程序的输出流)的数 据。我们可以想象写入到输出流的数据立即以输入的形式“回流”到了 Java程序。
- 目标:
- 目标是设计一个基于Swing的文本窗口显示控制台输出
- 管道流有关的重要注意事项:
- PipedInputStream类与PipedOutputStream类用于在应用程序中创建管道通信.一个PipedInputStream实例对象必须和一个PipedOutputStream实例对象进行连接而产生一个通信管道.PipedOutputStream可以向管道中写入数据,PipedIntputStream可以读取PipedOutputStream向管道中写入的数据.这两个类主要用来完成线程之间的通信.一个线程的PipedInputStream对象能够从另外一个线程的PipedOutputStream对象中读取数据.
- System.out:“标准”输出流。此流已打开并准备接受输出数据。通常,此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。
- System.in:“标准”输入流。此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。
- System.erro:“标准”错误输出流。此流已打开并准备接受输出数据。
- 通常,此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。按照惯例,此输出流用于显示错误消息,或者显示那些即使用户输出流(变量 out 的值)已经重定向到通常不被连续监视的某一文件或其他目标,也应该立刻引起用户注意的其他信息。
- 注意1.
- 1.PipedInputStream运用的是一个1024字节固定大小的循环缓冲区。写入PipedOutputStream的数据实际上保存到对应的 PipedInputStream的内部缓冲区,从PipedInputStream执行读操作时,读取的数据实际上来自这个内部缓冲区如果对应的 PipedInputStream输入缓冲区已满,任何企图写入PipedOutputStream的线程都将被阻塞。而且这个写操作线程将一直阻塞,直至出现读取PipedInputStream的操作从缓冲区删除数据。
- 【用管道流截取控制台输出】
- 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流读取的数据,得到一个能够在文本框中显示控制台输出的程序
- 注意2.
- 内部缓冲区最终会被写满,导致写操作阻塞。由于我们用同一个线程执行读、写操作,一旦写操作被阻塞,就不能再从PipedInputStream读取数据。
- 只要把读/写操作分开到不同的线程,就可以解决这个问题
- 【把读/写操作分开到不同的线程】
- 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 + "字节...");
- bytesRead = pipedIS.read(inArray, 0, 10);
- }
- }
- catch(IOException e) {
- System.err.println("读取输入错误.");
- System.exit(1);
- }
- }
- // 创建一个独立的线程
- // 执行写入PipedOutputStream的操作
- private static void startWriterThread() {
- new Thread(new Runnable() {
- public void run() {
- byte[] outArray = new byte[2000];
- while(true) { // 无终止条件的循环
- try {
- // 在该线程阻塞之前,有最多1024字节的数据被写入
- pipedOS.write(outArray, 0, 2000);
- }
- catch(IOException e) {
- System.err.println("写操作错误");
- System.exit(1);
- }
- System.out.println(" 已经发送2000字节...");
- }
- }
- }).start();
- }
- }
- 注意3.
- 从PipedInputStream读取数据时,如果符合下面三个条件,就会出现IOException异常:
- 1. 试图从PipedInputStream读取数据,
- 2. PipedInputStream的缓冲区为“空”(即不存在可读取的数据),
- 3. 最后一个向PipedOutputStream写数据的线程不再活动(通过Thread.isAlive()检测)。
- 例如:
- 1. w向PipedOutputStream写入数据。
- 2. w结束(w.isAlive()返回false)。
- 3. r从PipedInputStream读取w写入的数据,清空PipedInputStream的缓冲区。
- 4. r试图再次从PipedInputStream读取数据。这时PipedInputStream的缓冲区已经为空,而且w已经结束,从而导致在读操作执行时出现IOException异常。
- 要防止管道流前两个局限所带来的问题,方法之一是用一个ByteArrayOutputStream作为代理或替代PipedOutputStream。
- 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) {
- // 检查流里面的字节数
- if(byteArrayOS.size() > 0) {
- byte[] buffer = null;
- synchronized(byteArrayOS) {
- buffer = byteArrayOS.toByteArray();
- byteArrayOS.reset(); // 清除缓冲区
- }
- try {
- // 把提取到的数据发送给PipedOutputStream
- pipedOS.write(buffer, 0, buffer.length);
- }
- catch(IOException e) {
- // 记录错误或其他处理
- // 为简单计,此处我们直接结束
- System.exit(1);
- }
- }
- else // 没有数据可用,线程进入睡眠状态
- try {
- // 每隔1秒查看ByteArrayOutputStream检查新数据
- Thread.sleep(1000);
- }
- catch(InterruptedException e) {}
- }
- }
- }).start();
- } // startByteArrayReaderThread()
- } // LoopedStreams