黑马程序员——Java IO—字节流—PipedInputStream和PipedOutputStream

来源:互联网 发布:linux pid kill 编辑:程序博客网 时间:2024/06/13 22:36
PipedInputStream类与PipedOutputStream类用于在应用程序中进行管道通信。一个PipedInputStream实例对象必须和一个PipedOutputStream实例对象进行连接而产生一个通信管道。PipedOutputStream可以向管道中写入数据,PipedIntputStream可以读取相关联的PipedOutputStream向管道中写入的数据。这两个类主要用来完成线程之间的通信,一个线程的PipedInputStream对象能够从另外一个线程的PipedOutputStream对象中读取数据。


PipedInputStream和PipedOutputStream的实现原理类似于“生产者-消费者”,PipedOutputStream是生产者,PipedInputStream是消费者。在PipedInputStream中有一个buffer字节数组,默认大小为1024,作为缓冲区,存放“生产者”生产出来的东西;还有两个变量:in、out,in代表buffer数组中下一个可以用来存放数据的位置的索引,是用来记录“生产者”生产了多少,in的初值为-1;out代表buffer数组中下一个可以被都取得数据所在位置的索引,用来记录"消费者"消费了多少,out的初值为0;in为-1,代表buffer为空,表示数据全部被消费完了或还没有数据;in==out表示buffer满了。当消费者没东西可消费的时候,也就是当in为-1的时候,消费者会一直等待,直到有东西可消费。


PipedInputStream和PipedOutputStream是如何通信的呢??

我们知道在创建PipedInputStream、PipedOutputStream对象时,需要分别向它们的构造器传入PipedOutputStream对象或PipedInputStream对象,所以我们可能会认为:在PipedInputStream中会有一个PipedOutputStream类型的变量来保存传入的PipedOutputStream对象;在PipedOutputStream中有一个PipedInputStream类型的变量来保存传入的PipedInputStream对象。但是,实际却不是这样的,在PipedOutputStream中确实有一个PipedInputStream类型的变量来保存相关联的PipedInputStream对象,但在PipedInputStream中却没有相应的变量。

public void connect(PipedOutputStream src) throws IOException {src.connect(this);}

上面的代码是PipedInputStream中的connect方法,该方法中调用了相关联的PipedOutputStream对象的connect方法。再看一下PinpedOutputStream中的connect方法

public synchronized void connect(PipedInputStream snk) throws IOException {if (snk == null) {throw new NullPointerException();} else if (sink != null || snk.connected) {throw new IOException("Already connected");}sink = snk;snk.in = -1;snk.out = 0;snk.connected = true;}

从上面的代码可以看出,在管道流对象间建立连接就是为PipedOutputStream的sink变量赋值、将sink变量的in out connected属性设为初始值。
再看一下PipedOutputStream的write方法

public void write(int b)  throws IOException {if (sink == null) {throw new IOException("Pipe not connected");}sink.receive(b);}

从上面的代码可以看出,当我们调用PipedOutputStream的write方法往管道中写入数据时,底层实际上在调用相关联的PipedInputStream对象的receive方法,而receive方法的作用就是将数据存入PipedInputStream对象的buffer数组中,如下代码所示:

protected synchronized void receive(int b) throws IOException {checkStateForReceive();writeSide = Thread.currentThread();if (in == out)awaitSpace();if (in < 0) {in = 0;out = 0;}buffer[in++] = (byte)(b & 0xFF);if (in >= buffer.length) {in = 0;}}

(在看到in的初始值为-1时,感到有点儿奇怪,第一个可以存放数据的位置的索引不应是0吗?那将初始值定为-1不是会导致异常吗?看到receive方法时就了解了,原来在向buffer中添加元素时,先将in修改为了0。)


一个PipedInputStream只能和一个PipedOutputStream建立关联。
从上面展示的PipedOutputStream的connect方法中可以看到,在建立管道流之间的连接时有一个如下的判断条件:
else if (sink != null || snk.connected)
如果一个PipedInputStream对象已经建立了连接,则它的connected属性将为true,当试图再次建立连接时,将会抛出IOException("Already connected")异常。
如果一个PipedOutputStream对象已经建立了连接,则它的sink属性不为null,当试图再次建立连接时,将会抛出IOException("Already connected")异常。


PipedInputStream 和 PipedOutputStream 的 close

public void close()  throws IOException {closedByReader = true;synchronized (this) {in = -1;}}

以上是PipedInputStream的close方法的源码:将closeByReader置为true,将in置为-1。
在调用read方法读取数据时,会先判断closeByReader的值,如下:

if (!connected) {throw new IOException("Pipe not connected");} else if (closedByReader) {throw new IOException("Pipe closed");}

如果调用了PipedInputStream的close方法后再调用read方法获取数据将会导致IOException("Pipe closed")异常。

public void close()  throws IOException {if (sink != null) {sink.receivedLast();}}

以上是PipedOutputStream的close方法的源码,它仅仅是调用关联的PipedInputStream的receivedLast方法,receivedLast方法如下:

synchronized void receivedLast() {closedByWriter = true;notifyAll();}

它将closedByWriter置为true,并唤醒当前线程。
关于closedByWriter的作用将在后面说明。


PipedInputStream 的 read 方法

在学习其他InputStream时,我们都是使用循环来不断的读取数据,当其read方法返回-1时,表示没有数据可读了,就退出循环。PipedInputStream同样使用read方法的-1返回值来表示读取结束。下面是PipedInputStream的read方法的源代码:

public synchronized int read()  throws IOException {if (!connected) {throw new IOException("Pipe not connected");} else if (closedByReader) {throw new IOException("Pipe closed");} else if (writeSide != null && !writeSide.isAlive()   && !closedByWriter && (in < 0)) {throw new IOException("Write end dead");}readSide = Thread.currentThread();int trials = 2;while (in < 0) {if (closedByWriter) {/* closed by writer, return EOF */return -1;}if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {throw new IOException("Pipe broken");}/* might be a writer waiting */notifyAll();try {wait(1000);} catch (InterruptedException ex) {throw new java.io.InterruptedIOException();}}int ret = buffer[out++] & 0xFF;if (out >= buffer.length) {out = 0;}if (in == out) {/* now empty */in = -1;}return ret;}

如果buffer中有可读的数据(即in >= 0),则无论关联的PipedOutputStream是否关闭,总可以读取buffer中可读的那些数据,直到buffer中没有可读的数据。
如果buffer中没有了可读的数据(即in < 0),则分为关联的PipedOutputStream已关闭和未关闭两种情况。如果关联的PipedOutputStream已关闭,则返回-1,表示读取结束;如果关联的PipedOutputStream未关闭,则调用wait(1000);来阻塞当前线程1秒,之后再判断是否有可读的数据。


PipedOutputStream的 write 方法
从前面的分析中我们了解到,在调用PiptedInputStream的read方法时,如果没有可读的数据,则阻塞当前线程1s,1s后再次读取,如果还没有,则再阻塞1s,如此循环。
PipedOutputStream的write方法也是如此,在调用其write方法是,如果没有足够的空间写数据,则阻塞当前线程1s,1s后再次写,如果还不能写,则再阻塞,如此循环。


小例子1:

package org.lgy.study.io;import java.io.PipedOutputStream;import java.io.PipedInputStream;import java.util.Scanner;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.util.concurrent.Executors;import java.util.concurrent.ExecutorService;/* javac -d classes "src/org/lgy/study/io/PipedStreamTest2.java"java org.lgy.study.io.PipedStreamTest2 */public class PipedStreamTest2{public static void main(String[] args){try{PipedInputStream pipedIn = new PipedInputStream();PipedOutputStream pipedOut = new PipedOutputStream(pipedIn);ExecutorService service = Executors.newCachedThreadPool();service.execute(new Producer(pipedOut));service.execute(new Consumer(pipedIn));service.shutdown();}catch(IOException e){e.printStackTrace();}}// 生产者线程private static class Producer implements Runnable{private PipedOutputStream pipedOut;private Producer(PipedOutputStream pipedOut){this.pipedOut = pipedOut;}public void run(){Scanner scanner = new Scanner(System.in);  // 从键盘获取输入String line = null;try{// 当什么都不输,直接按下回车键时,Scanner的nextLine方法返回的是空字符串“”// 如果程序检测到空字符串,则退出while(!(line = scanner.nextLine()).equals("")){  System.out.println("生产者线程写出的数据:" + line);this.pipedOut.write(line.getBytes("UTF-8"));}}catch(UnsupportedEncodingException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}finally{if(this.pipedOut != null){try{this.pipedOut.close();System.out.println("pipedOut 已关闭");}catch(IOException e){e.printStackTrace();}}}}}// 消费者线程private static class Consumer implements Runnable{private PipedInputStream pipedIn;private Consumer(PipedInputStream pipedIn){this.pipedIn = pipedIn;}public void run(){byte[] buff = new byte[512];int size = 0;try{// 当关联的PipedOutputStream关闭后,PipedInputStream的read方法返回-1// 如果关联的PipedOutputStream没有关闭,但buffer中没有新数据,则使当前线程阻塞while((size = this.pipedIn.read(buff)) != -1){System.out.println("消费者线程读到的数据:" + new String(buff, 0, size, "UTF-8"));}}catch(UnsupportedEncodingException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}finally{if(this.pipedIn != null){try{this.pipedIn.close();System.out.println("pipedIn 已关闭");}catch(IOException e){e.printStackTrace();}}}}}}/* adfff生产者线程写出的数据:adfff消费者线程读到的数据:adfffafafafdasdfa   asfasdfasd生产者线程写出的数据:afafafdasdfa   asfasdfasd消费者线程读到的数据:afafafdasdfa   asfasdfasd你好生产者线程写出的数据:你好消费者线程读到的数据:你好你好,李刚   呵呵生产者线程写出的数据:你好,李刚   呵呵消费者线程读到的数据:你好,李刚   呵呵pipedOut 已关闭pipedIn 已关闭 */

小例子2:

package org.lgy.study.io;import java.io.PipedInputStream;import java.io.PipedOutputStream;import java.io.UnsupportedEncodingException;import java.io.IOException;import java.util.concurrent.Executors;import java.util.concurrent.ExecutorService;/* javac -d classes "src/org/lgy/study/io/PipedStreamTest.java"java org.lgy.study.io.PipedStreamTest */public class PipedStreamTest{public static void main(String[] args){try{PipedInputStream pipedIn = new PipedInputStream();PipedOutputStream pipedOut = new PipedOutputStream(pipedIn);ExecutorService service = Executors.newCachedThreadPool();service.execute(new Producer(pipedOut));service.execute(new Consumer(pipedIn));service.shutdown();System.out.println("main结束");}catch(IOException e){e.printStackTrace();}}// 生产者线程private static class Producer implements Runnable{private PipedOutputStream pipedOut;private Producer(PipedOutputStream pipedOut){this.pipedOut = pipedOut;}public void run(){}// 生产者向通道中写入字符串“你好”对应的UTF-8字节数组public void method1(){try{byte[] utfBytes = "你好".getBytes("UTF-8");  // 获取字符串“你好”对应的UTF-8字节数组this.pipedOut.write(utfBytes);}catch(UnsupportedEncodingException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}finally{try{if(pipedOut != null){this.pipedOut.close();System.out.println("pipedOut 已关闭");}}catch(IOException e){e.printStackTrace();}}}}// 消费者线程public static class Consumer implements Runnable{private PipedInputStream pipedIn;public Consumer(PipedInputStream pipedIn){this.pipedIn = pipedIn;}public void run(){}// 消费者线程先睡眠1s,等生产者线程完成生产并关闭后再读取数据public void method1(){try{Thread.sleep(1000);  // 睡1sbyte[] utfBytes = new byte[512];int size = pipedIn.read(utfBytes);  // 把通道buffer中的字节数据读到指定的字节数组中System.out.println(new String(utfBytes, 0, size, "UTF-8"));  // “你好”System.out.println(new String(utfBytes, 0, size, "GBK"));  // “浣犲ソ”}catch(UnsupportedEncodingException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}catch(InterruptedException e){e.printStackTrace();}finally{try{if(this.pipedIn != null)this.pipedIn.close();}catch(IOException e){e.printStackTrace();}}}}}


0 0