简介

PipedOutputStream和PipedInputStream主要用于线程之间的通信 。二者必须配合使用,也就是一段写入,另一端接收。本质上也是一个中间缓存区,讲数据缓存在PipedInputStream的数组当中,等待PipedOutputStream的读取。

PipedInputStream的缓冲区中循环缓冲的思想很有意思。

PS:虽然这个也叫管道,但是这和进程之间的管道通信没有任何关系。这里的管道流是基于Java用户层的代码实现的,而经常通信是基于内核态的程序的通信。

源码分析

PipedOutputStream

public
class PipedOutputStream extends OutputStream {
// 需要传入的输入流
private PipedInputStream sink;
// 输入输出流连接的构造
public PipedOutputStream(PipedInputStream snk) throws IOException {
connect(snk);
}
// 默认构造函数
public PipedOutputStream() {
}
// 连接输入输出流
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;
} // 将一个int类型数据写入到输出流,这里就会将它传给输入流
public void write(int b) throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
}
sink.receive(b);
} // 写入字节数组的指定位置
public void write(byte b[], int off, int len) throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
} else if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
// 调用了输入流的接收函数
sink.receive(b, off, len);
} // 清空管道输出流
public synchronized void flush() throws IOException {
if (sink != null) {
// 让输入流放弃对资源的占有
synchronized (sink) {
// 通知所有其他的等待资源线程可以读取资源了
sink.notifyAll();
}
}
} // 关闭管道输出流
public void close() throws IOException {
if (sink != null) {
// 通知输入流它已经关闭了
sink.receivedLast();
}
}
}

PipedInputStream

public class PipedInputStream extends InputStream {
// 输出流是否被关闭
boolean closedByWriter = false;
// 输入流是否被关闭,这里修饰了volatile
volatile boolean closedByReader = false;
// 输入输出的连接标记
boolean connected = false;
// 需要传入的读写线程
Thread readSide;
Thread writeSide; // 管道默认可以缓存的大小
private static final int DEFAULT_PIPE_SIZE = 1024; protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE; // 缓冲区
protected byte buffer[]; // 当前缓冲区中应该写入的位置
protected int in = -1; // 当前缓冲区可以读取的位置
protected int out = 0; // 传入输出流的构造
public PipedInputStream(PipedOutputStream src) throws IOException {
this(src, DEFAULT_PIPE_SIZE);
} // 传入输出流和管道缓存大小的构造
public PipedInputStream(PipedOutputStream src, int pipeSize)
throws IOException {
initPipe(pipeSize);
connect(src);
} // 默认构造
public PipedInputStream() {
initPipe(DEFAULT_PIPE_SIZE);
} // 传入管道大小的构造
public PipedInputStream(int pipeSize) {
initPipe(pipeSize);
} // 初始化缓存区数组
private void initPipe(int pipeSize) {
if (pipeSize <= 0) {
throw new IllegalArgumentException("Pipe Size <= 0");
}
buffer = new byte[pipeSize];
} // 将输入输出流连接
public void connect(PipedOutputStream src) throws IOException {
src.connect(this);
} // 接收一个字节,同步的
protected synchronized void receive(int b) throws IOException {
// 检测管道的状态
checkStateForReceive();
// 读取当前写入线程
writeSide = Thread.currentThread();
// 写入指针等于读取指针,说明缓冲区满了,通知其他读线程尽快来读
// 当前线程会进入等待状态
if (in == out)
awaitSpace();
// 输出流的写入位置小于0
if (in < 0) {
in = 0;
out = 0;
}
// 写入字节,只取低八位
buffer[in++] = (byte)(b & 0xFF);
// 循环缓冲指针复位
if (in >= buffer.length) {
in = 0;
}
} // 写入一堆
synchronized void receive(byte b[], int off, int len) throws IOException {
checkStateForReceive();
writeSide = Thread.currentThread();
int bytesToTransfer = len;
// 循环写入
while (bytesToTransfer > 0) {
if (in == out)
awaitSpace();
int nextTransferAmount = 0;
if (out < in) {
nextTransferAmount = buffer.length - in;
} else if (in < out) {
if (in == -1) {
in = out = 0;
nextTransferAmount = buffer.length - in;
} else {
nextTransferAmount = out - in;
}
}
if (nextTransferAmount > bytesToTransfer)
nextTransferAmount = bytesToTransfer;
assert(nextTransferAmount > 0);
System.arraycopy(b, off, buffer, in, nextTransferAmount);
bytesToTransfer -= nextTransferAmount;
off += nextTransferAmount;
in += nextTransferAmount;
if (in >= buffer.length) {
in = 0;
}
}
} // 判断连接状态
private void checkStateForReceive() throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByWriter || closedByReader) {
throw new IOException("Pipe closed");
} else if (readSide != null && !readSide.isAlive()) {
throw new IOException("Read end dead");
}
} // 读完了数据,等待写线程继续写数据
private void awaitSpace() throws IOException {
while (in == out) {
checkStateForReceive(); /* full: kick any waiting readers */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
} // 当输出流被关闭的时候使用
synchronized void receivedLast() {
closedByWriter = true;
notifyAll();
} // 读入一个字节
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");
}
// 等待写入线程写入
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;
} // 写入到字节数组当中
public synchronized int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
} // 读到了第一个字节
int c = read();
if (c < 0) {
return -1;
}
// 放入第一个字节
b[off] = (byte) c;
int rlen = 1;
// 循环读取剩下的字节
while ((in >= 0) && (len > 1)) { int available; // 其实这里就是一个循环缓冲的剩余缓冲长度计算了,当写指针超出了缓冲区的长度,就会回到-1,计算长度的方式就不同了
if (in > out) {
// 写指针没超,那么长度就应该就是写指针位置减去读指针,这里取小是反之数组越界
available = Math.min((buffer.length - out), (in - out));
} else {
// 写指针超了,回到了-1,那么剩余长度就是数组长度减去读指针
available = buffer.length - out;
} // 防止数组与越界
if (available > (len - 1)) {
available = len - 1;
}
// 直接将缓冲区的数组有效部分复制过去
System.arraycopy(buffer, out, b, off + rlen, available);
out += available;
rlen += available;
len -= available; // 读指针复位
if (out >= buffer.length) {
out = 0;
}
// 写指针复位
if (in == out) {
/* now empty */
in = -1;
}
}
return rlen;
} // 从字节流中可读的字节数
public synchronized int available() throws IOException {
if(in < 0)
// 当in == -1说明刚被读完或者刚初始化,缓冲区没有数据
return 0;
else if(in == out)
// 只有缓冲区被写满的时候,二者才会相等,说明缓冲区的数据满了,如果是被读完,in会被置-1
return buffer.length;
else if (in > out)
// 还有数据
return in - out;
else
// 循环缓冲,in在out后面,说明in已经跑完一圈了
return in + buffer.length - out;
} // 关闭管道
public void close() throws IOException {
closedByReader = true;
synchronized (this) {
in = -1;
}
}
}

总结

PipedOutputStream特点

  • 本质就是调用PipedInputStream的接口,将数据写进PipedInputStream的缓冲区当中。
  • 一个输出只能一个输入连接。
  • 和之前的ByteArrayInputStream 一样,操作的数据都是字节类型。

PipedInputStream特点

  • 内部主要由缓冲数组、读指针和写指针构成。
  • 由于这两个流是用于线程之间通信,所以他们是需要保证线程安全的,他们对外的函数都是有同步锁修饰的,同时只能有一个线程进行读取获取写入,其实效率不高。
  • 当生产者写入的时候发现缓冲区满了,就会进入等待状态,等待消费者消费数据,再将他们唤醒。
  • 当消费者读取数据的时候发现缓冲区是空的,那么就会进入等待,等待生产者写入数据,再将他们唤醒。

缓冲区特点

  • 缓冲区其实采用的是一个循环缓冲的形式,在读取数据的时候,读取的是读指针当前的位置,读一个增加一个,但是当读指针和写指针相同的时候,in就会被置为-1,这是为了后面缓冲区数据满的时候,读指和写指针的相同的情况进行区分,也就是说,读指针和写指针相等的时候,就是数据满的时候;当读指针超出了缓冲区数组边界,那么就会被置为0,这样往复就是循环缓冲的思想。
  • 当数据写入的时候,数据就会写入写指针的位置,当写指针超出了数组边界,就会被置为0;当写指针等于读指针,说明写指针已经超圈了,那么缓存区的可用长度就是整个缓冲区的大小,不能再超过读指针,不然会被理解为可用长度是大于的那一部分。

缓冲区有效数据长度的情况如下图所示:

Java IO源码分析(三)——PipedOutputStream和PipedInputStream的更多相关文章

  1. Java IO源码分析(二)——ByteArrayInputStream 和 ByteArrayOutputStream

    简介 ByteArrayInputStream 是字节数组输入流,它继承于InputStream. 它的内部数据存储结构就是字节数组. ByteArrayOutputStream是字节数组输出流,它继 ...

  2. java集合源码分析(三):ArrayList

    概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...

  3. tomcat源码分析(三)一次http请求的旅行-从Socket说起

    p { margin-bottom: 0.25cm; line-height: 120% } tomcat源码分析(三)一次http请求的旅行 在http请求旅行之前,我们先来准备下我们所需要的工具. ...

  4. Java Reference 源码分析

    @(Java)[Reference] Java Reference 源码分析 Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互.即可以使 ...

  5. java集合源码分析(六):HashMap

    概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...

  6. java io 源码研究记录(一)

    Java IO 源码研究: 一.输入流 1  基类 InputStream 简介: 这是Java中所有输入流的基类,它是一个抽象类,下面我们简单来了解一下它的基本方法和抽象方法. 基本方法: publ ...

  7. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  8. Java 集合源码分析(一)HashMap

    目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...

  9. java io系列04之 管道(PipedOutputStream和PipedInputStream)的简介,源码分析和示例

    本章,我们对java 管道进行学习. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_04.html java 管道介绍 在java中,PipedOu ...

随机推荐

  1. AQS详解,并发编程的半壁江山

    千呼万唤始出来,终于写到AQS这个一章了,其实为了写这一章,前面也是做了很多的铺垫,比如之前的 深度理解volatile关键字 线程之间的协作(等待通知模式) JUC 常用4大并发工具类 CAS 原子 ...

  2. 新手避坑 -- 用 Jenkins +miniprogram-ci 自动构建微信小程序

    先看看效果: 要实现这样的效果,需要下面3步: 1.下载 node 依赖包 miniprogram-ci,编写预览和上传功能 2. 登录微信公众平台, 下载项目的privateKey+添加代码上传IP ...

  3. arm-linux校时和时钟同步

    # 将时间写到系统 date 2020.08.25-14:02:00 # 将时间同步到硬件时钟芯片 hwclock -f /dev/rtc1 -w # 将时间从硬件时钟芯片同步到系统 hwclock ...

  4. Spring Cloud实战 | 最八篇:Spring Cloud +Spring Security OAuth2+ Axios前后端分离模式下无感刷新实现JWT续期

    一. 前言 记得上一篇Spring Cloud的文章关于如何使JWT失效进行了理论结合代码实践的说明,想当然的以为那篇会是基于Spring Cloud统一认证架构系列的最终篇.但关于JWT另外还有一个 ...

  5. ABBYY FineReader 14新增了什么

    FineReader 是一款一体化的 OCR 和PDF编辑转换器,随着版本的更新,功能的增加,FineReader 14的推出继续为用户在处理文档时提高业务生产力,该版本包含若干新特性和功能增强,包括 ...

  6. MGR(MySQL Group Replication)部署测试

    1. 环境说明 192.168.11.131 mgr1 主节点 192.168.11.132 mgr2 从节点 192.168.11.133 mgr3 从节点 2. 在mgr1.mgr2.mgr3上安 ...

  7. python批量生成SQL语句

    1,首先写一条能运行成功插入SQL的语句 INSERT INTO sign_guest(realname,phone,email,sign,event_id)VALUES("jack&quo ...

  8. img标签到底是行内元素还是块级元素

    面试官问你<img>是什么元素时你怎么回答 写这篇文章源自我之前的一次面试,题目便是问img标签属于块级元素还是行内元素,当时想都没想就说了是行内(inline)元素,面试官追问为什么能够 ...

  9. linux系统下oracle表空间占用情况

    1.我们先查询表空间的占用情况,使用sql如下: select upper(f.tablespace_name) "表空间名", d.tot_grootte_mb "表空 ...

  10. 【常见踩坑】】USB调试安装失败(Installation failed with message INSTALL_CANCELED_BY_USER)

    [参考]http://www.cnblogs.com/liushilin/p/6553918.html 问题:在USB安装调试(小米手机),出现如下错误 解决:1.小米手机解决办法见参考.登录小米账号 ...