黑马程序员——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中的connect方法,该方法中调用了相关联的PipedOutputStream对象的connect方法。再看一下PinpedOutputStream中的connect方法
从上面的代码可以看出,在管道流对象间建立连接就是为PipedOutputStream的sink变量赋值、将sink变量的in out connected属性设为初始值。
再看一下PipedOutputStream的write方法
从上面的代码可以看出,当我们调用PipedOutputStream的write方法往管道中写入数据时,底层实际上在调用相关联的PipedInputStream对象的receive方法,而receive方法的作用就是将数据存入PipedInputStream对象的buffer数组中,如下代码所示:
(在看到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的close方法的源码:将closeByReader置为true,将in置为-1。
在调用read方法读取数据时,会先判断closeByReader的值,如下:
如果调用了PipedInputStream的close方法后再调用read方法获取数据将会导致IOException("Pipe closed")异常。
以上是PipedOutputStream的close方法的源码,它仅仅是调用关联的PipedInputStream的receivedLast方法,receivedLast方法如下:
它将closedByWriter置为true,并唤醒当前线程。
关于closedByWriter的作用将在后面说明。
如果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后再次写,如果还不能写,则再阻塞,如此循环。
小例子2:
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
- 黑马程序员——Java IO—字节流—PipedInputStream和PipedOutputStream
- 黑马程序员——Java基础——IO流笔记(PipedInputStream和PipedOutputStream使用示例)
- 黑马程序员——管道流:PipedInputStream和PipedOutputStream与线程的结合运用
- 【Java-IO】PipedInputStream和PipedOutputStream
- Java IO操作——线程间通讯流(管道流 PipedOutputStream、PipedInputStream)
- Java IO系列6 字节流之PipedInputStream与PipedOutputStream
- 管道流PipedOutputStream和PipedInputStream的使用-黑马程序员
- Java IO - PipedOutputStream & PipedInputStream
- Java IO流 PipedInputStream类和PipedOutputStream类
- java(5)--IO流之PipedInputStream和PipedOutputStream
- 【java基础:IO】管道流PipedInputStream和PipedOutputStream的Demostration
- Java IO--管道流PipedOutputStream/PipedInputStream
- java 管道流之PipedOutputStream和PipedInputStream
- 黑马程序员——【Java】IO——字节流
- 黑马程序员——Java IO—字节流
- 黑马程序员——Java基础---IO流(字节流)
- 黑马程序员——java基础 io 字节流
- 黑马程序员——Java基础---IO---字节流
- Sql Server 触发器
- 基本的增量备份策略设置
- 博论时世
- LA 3211 Now or later (2-SAT + 二分)
- Vijos1051. 送给圣诞夜的极光
- 黑马程序员——Java IO—字节流—PipedInputStream和PipedOutputStream
- Leetcode: Binary Tree Level Order Traversal II
- 学习内核驱动是出现的错误及其解决方式
- 提高增量备份的速度
- java多线程之利用“CyclicBarrier”汇总结果
- wxPython中的布局
- Java集合关系图
- Python-OpenCV教程-2
- tastypie 多表关系