[重学Java基础][Java IO流][Part.10] 字节管道输入输出流
总述
PipedOutputStream和PipedInputStream分别是管道字节输出流和管道字节输入流。
多线程可以通过管道进行线程间的通讯,在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。
使用管道通信时,基于生产者-消费者模式,一般流程是:PipedOutputStream对象表示流发送端,PipedInputStream对象则表示流接收端,使用两个对象的connect()方法连接两端。我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的PipedInputStream中,进而存储在PipedInputStream的缓冲中;此时,线程B通过读取PipedInputStream中的数据。就可以实现,线程A和线程B的通信。
PipedInputStream
概述
源码分析
成员属性
管道输出流是否关闭
boolean closedByWriter;
管道输入流是否关闭
volatile boolean closedByReader;
管道输入流与管道输出流是否连接
boolean connected;
读取管道中数据的线程
Thread readSide;
写入管道中数据的线程
Thread writeSide;
管道默认缓冲区大小 为1024字节
private static final int DEFAULT_PIPE_SIZE = 1024;
管道大小1024字节
protected static final int PIPE_SIZE = 1024;
管道缓冲字节数组
protected byte[] buffer;
下一个输入字节位置
protected int in;
下一个输出字节位置
protected int out;
成员方法
构造函数
传入一个PipedOutputStream对象 并调用第二个构造函数重载 初始化缓冲区并连接管道
public PipedInputStream(PipedOutputStream src) throws IOException {
this(src, 1024);
}
设置输入输出位置 初始化缓冲区并连接管道
public PipedInputStream(PipedOutputStream src, int pipeSize) throws IOException {
this.in = -1;
this.out = 0;
this.initPipe(pipeSize);
this.connect(src);
}
无参构造 设置输入输出位置并初始化缓冲区
public PipedInputStream() {
this.in = -1;
this.out = 0;
this.initPipe(1024);
}
按指定大小初始化缓冲区
public PipedInputStream(int pipeSize) {
this.in = -1;
this.out = 0;
this.initPipe(pipeSize);
}
初始化缓冲区 连接方法
按指定大小初始化缓冲区
private void initPipe(int pipeSize) {
if (pipeSize <= 0) {
throw new IllegalArgumentException("Pipe Size <= 0");
} else {
this.buffer = new byte[pipeSize];
}
}
管道连接 实际调用了PipedOutputStream的连接方法
public void connect(PipedOutputStream src) throws IOException {
src.connect(this);
}
接收流内容方法 会在管道字节输出流PipedOutputStream的write()方法中被调用
protected synchronized void receive(int b) throws IOException {
检查管道状态
this.checkStateForReceive();
获取当前输入的线程
this.writeSide = Thread.currentThread();
如果读出位置=写入位置 说明已经读完 线程等待
if (this.in == this.out) {
this.awaitSpace();
}
if (this.in < 0) {
this.in = 0;
this.out = 0;
}
读入数据保存到缓冲区
this.buffer[this.in++] = (byte)(b & 255);
if (this.in >= this.buffer.length) {
this.in = 0;
}
}
读入字节数组 并指定始终位置
synchronized void receive(byte[] b, int off, int len) throws IOException {
检查管道状态
this.checkStateForReceive();
this.writeSide = Thread.currentThread();
要转换的字节长度
int bytesToTransfer = len;
while(bytesToTransfer > 0) {
读出游标位置=写入游标位置 说明已经读完 线程等待
if (this.in == this.out) {
this.awaitSpace();
}
int nextTransferAmount = 0;
如果读出小于写入 则设置下面要被转换的字节大小
nextTransferAmount=buffer.length - in;
if (this.out < this.in) {
nextTransferAmount = this.buffer.length - this.in;
} else if (this.in < this.out) {
如果读出大于写入的情况
if (this.in == -1) {
输入=-1 说明已经读完
则设置读入=写出=0
this.in = this.out = 0;
nextTransferAmount = this.buffer.length - this.in;
} else {
nextTransferAmount = this.out - this.in;
}
}
若下面要被转换的字节nextTransferAmount大于要转换的字节长度bytesToTransfer
if (nextTransferAmount > bytesToTransfer) {
则设置nextTransferAmount=bytesToTransfer的值
nextTransferAmount = bytesToTransfer;
}
使用断言,若nextTransferAmount <= 0,则退出此进程
assert nextTransferAmount > 0;
数据写入到缓冲数组
System.arraycopy(b, off, this.buffer, this.in, nextTransferAmount);
bytesToTransfer -= nextTransferAmount;
off += nextTransferAmount;
this.in += nextTransferAmount;
if (this.in >= this.buffer.length) {
this.in = 0;
}
}
}
检查管道状态
private void checkStateForReceive() throws IOException {
检查管道状态
if (!this.connected) {
未连接抛出未连接异常
throw new IOException("Pipe not connected");
} else if (!this.closedByWriter && !this.closedByReader) {
if (this.readSide != null && !this.readSide.isAlive()) {
如果读入线程为空或者已终止 则抛出 读入终止异常
throw new IOException("Read end dead");
}
} else {
否则抛出管道关闭异常
throw new IOException("Pipe closed");
}
}
等待方法
前面提到 当写入管道的数据被读出完毕后 调用此方法 让线程处于等待状态
private void awaitSpace() throws IOException {
如果管道数据已读完 则每隔1秒唤醒线程 看是否有数据继续写入
while(this.in == this.out) {
this.checkStateForReceive();
this.notifyAll();
try {
this.wait(1000L);
} catch (InterruptedException var2) {
throw new InterruptedIOException();
}
}
}
输入端关闭后 调用读出端接收完毕方法
synchronized void receivedLast() {
this.closedByWriter = true;
this.notifyAll();
}
读入数据并存储到byte数组中方法
不是此类的主流功能 且和其他类类似功能大同小异
public synchronized int read(byte b[], int off, int len) throws IOException
{
……
}
从此字节管道输入流对象中读取的字节数
public synchronized int available() throws IOException {
if(in < 0)
return 0;
else if(in == out)
return buffer.length;
else if (in > out)
return in - out;
else
return in + buffer.length - out;
}
关闭
public void close() throws IOException {
设置由读取端关闭标志
closedByReader = true;
synchronized (this) {
in = -1;
}
}
PipedOutputStream
源码分析
成员属性
字节管道输入流对象
private PipedInputStream sink;
成员方法
构造方法
传入一个PipedInputStream参数并连接两个管道
public PipedOutputStream(PipedInputStream snk) throws IOException {
this.connect(snk);
}
空构造
public PipedOutputStream() {
}
管道连接
public synchronized void connect(PipedInputStream snk) throws IOException {
if (snk == null) {
throw new NullPointerException();
} else if (this.sink == null && !snk.connected) {
设置初始化参数
this.sink = snk;
snk.in = -1;
snk.out = 0;
snk.connected = true;
} else {
throw new IOException("Already connected");
}
}
写入到管道
public void write(int b) throws IOException {
if (this.sink == null) {
throw new IOException("Pipe not connected");
} else {
内部调用了PipedInputStream的接收方法
this.sink.receive(b);
}
}
写入字节数组到管道 并指定始终位置
public void write(byte[] b, int off, int len) throws IOException {
if (this.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) {
if (len != 0) {
内部调用了PipedInputStream的接收方法
this.sink.receive(b, off, len);
}
} else {
throw new IndexOutOfBoundsException();
}
}
刷新管道方法 实质上是调用管道输入流的notifyAll()方法
目的是让“管道输入流”放弃对当前资源的占有,
让其它的等待线程(等待读取管道输出流的线程)读取“管道输出流”的值。
public synchronized void flush() throws IOException {
if (this.sink != null) {
PipedInputStream var1 = this.sink;
synchronized(this.sink) {
this.sink.notifyAll();
}
}
}
关闭方法
public void close() throws IOException {
if (this.sink != null) {
this.sink.receivedLast();
}
}
代码示例
public static void main(String[] args) throws Exception {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
pos.connect(pis);
Runnable sender = () -> sendeMessage(pos);
Runnable receiver = () -> receiveMessage(pis);
new Thread(sender).start();
new Thread(receiver).start();
}
public static void sendeMessage(PipedOutputStream pos) {
try {
for (int i = 1; i <= 50; i++) {
pos.write((byte) i);
pos.flush();
System.out.println("发送: " + i);
Thread.sleep(500);
}
pos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void receiveMessage(PipedInputStream pis) {
try {
int num = -1;
while ((num = pis.read()) != -1) {
System.out.println("接收: " + num);
}
pis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
输出结果
发送: 1
接收: 1
发送: 2
接收: 2
发送: 3
接收: 3
发送: 4
接收: 4
发送: 5
接收: 5
发送: 6
接收: 6
发送: 7
接收: 7
发送: 8
接收: 8
发送: 9
接收: 9
发送: 10
接收: 10
发送: 11
接收: 11
……
发送: 50
接收: 50