简介
PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流,是字节流的一种。两者配合使用可以进行同一个进程不同线程之间的通信,管道输入流用于向管道中读取数据,管道输出流用于向管道中写入数据。通常管道输入流和管道输出流在不同的线程中,如果在同一个线程可能存在死锁的情况。
实现方式大致是,管道输出流与管道输出流进行关联,当一个线程向管道输出流中写入数据,实际上是将数据写到与管道输出关联的管道输入流的缓冲区中(大小为1024的字节数组),其他线程通过管道输入流读取到缓冲区中的数据,这样就是实现了线程之间的通信。而当缓冲区中的数据已经为满的情况下,管道输出流将阻塞,当缓冲区中的数据为空的情况下,管道输入流将阻塞。
PipedOutputStream介绍
1.PipedOutputStream的构造方法
public PipedOutputStream(PipedInputStream snk) throws IOException{ connect(snk) }
public PipedOutputStream() {}
- 有参构造传入PipedInputStream,创建连接到指定管道输入流的管道输出流。
- 无参构造,创建了未连接到管道输入流的管道输出流。
2.内部变量
private PipedInputStream sink;
- 与管道输出流连接的管道输出流。
3.内部方法
public synchronized void connect(PipedInputStream snk) throws IOException {}
public void write(int b) throws IOException {}
public void write(byte b[], int off, int len) throws IOException {}
public synchronized void flush() throws IOException {}
public void close() throws IOException {}
-
connect(PipedInputStream snk)---将管道输入流和管道输出流连接。
-
write(int b)---将一个字节b写入到缓冲区.
-
write(byte b[],int off,int len)---将字节数组b的off索引开始,长度为len个字节写入到缓冲区中.
-
flush()---刷新缓冲区.
-
close()---关闭管道输出流,释放相关资源,并且流不能再写入字节.
PipedInputStream介绍
1.PipedInputStream的构造方法
public PipedInputStream(PipedOutputStream src) throws IOException {}
//创建管道输入流,并连接到管道输出流src
public PipedInputStream(PipedOutputStream src, int pipeSize) {}
//创建管道出入流,并连接到管道输出流src,并创建指定大小pipeSize的缓冲区
public PipedInputStream() {}
//创建无连接的管道输入流
public PipedInputStream(int pipeSize) {}
//创建无连接的管道输入流,并创建指定大小PipeSize缓冲区
2.内部变量
protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE;
protected byte buffer[];
protected int in = -1;
protected int out = 0;
- PIPE_SIZE---默认创建的缓冲区的大小为1024字节.
- buffer[] ---缓冲区为一字节数组.
- in---缓冲区的索引,从管道输出流中接受到的字节存储到此位置
- out---缓冲区的索引,管道输入流读取的下一个字节的位置.
3.内部方法
public void connect(PipedOutputStream src) throws IOException {}
protected synchronized void receive(int b) throws IOException {}
synchronized void receive(byte b[], int off, int len) throws IOException {}
public synchronized int read(byte b[], int off, int len) throws IOException {}
public synchronized int read() throws IOException {}
public synchronized int available() throws IOException {}
public void close() throws IOException {}
-
connect(PipedOutputStream src)---将管道输入流连接到管道输出流.
-
receive(int b)---从管道输出流中接受一个字节b存储到缓冲区.
-
receive(byte b[] int off,int len)---从管道输出流中接受字节数组b中,索引off开始,长度为len的字节.
-
read(byte[] b,int off,int len)---将缓冲区的字节读取到字节数组b中索引off开始,长度为len之间的位置.最多为len个字节.
-
read()---从缓冲区读取一个字节.
-
available()---从缓冲区中可读取的字节数.
-
close()---关闭管道输入流,并释放相关资源
案例
public class PipedDemo {
public static void main(String[] args) {
Sender sender = new Sender();
Receiver receiver = new Receiver();
PipedOutputStream pops = sender.getOutputStream();
PipedInputStream pips = receiver.getPipedInputStream();
try {
//管道流连接,与注释的语句相同
pops.connect(pips);
//pips.connect(pops);
//开始两个线程,一个管道输入流用于读取数据,一个是管道输出流写入数据
sender.start();
receiver.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Receiver extends Thread {
private PipedInputStream in = new PipedInputStream();
public PipedInputStream getPipedInputStream() {
return in;
}
public void run() {
try {
byte[] buffer = new byte[1024];
int len = in.read(buffer);
System.out.println(new String(buffer, 0, len));
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Sender extends Thread {
private PipedOutputStream out = new PipedOutputStream();
public PipedOutputStream getOutputStream() {
return out;
}
public void run() {
try {
String message = "The Demo is about PipedOutputStream and PipedInputStream";
out.write(message.getBytes());
out.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
运行结果:
The Demo is about PipedOutputStream and PipedInputStream
PipedOutputStream源码分析
package pipedDemo;
import java.io.IOException;
import java.io.PipedInputStream;
public class PipedOutputStream {
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;
// in是缓冲区buffer存储下一个字节的位置,初始化-1,表示缓冲区是空的
snk.in = -1;
// out是从缓冲区读取下一个字节的位置,初始化为0
// 当in==out表示缓冲区已满
snk.out = 0;
snk.connected = true;
}
// 将字节b写入到缓冲区里面,实际存储到与管道输出流连接的管道输入流的缓冲区中.
public void write(int b) throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
}
sink.receive(b);
}
// 将字节数组b,off开始,长度为len的字节写入到缓冲区里面
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();
}
}
}
总结:
1.将字节数组写入到管道输出流,其实是写入到与管道输出流连接的管道输入流中,读取数据只不过是切换到另外一个线程来读取.
2.管道输入流里面的缓冲区实际上字节数组buffer,in表示的是缓冲区存储下一个字节的位置,in=-1时,缓冲区为空;out表示的从缓冲区里面读取下一个字节的位置.当in==out表示的是缓冲区已满了.
PipedInputStream源码分析
package pipedDemo;
import java.io.IOException;
import java.io.PipedOutputStream;
public class PipedInputputStream {
// 管道输出流是否关闭
boolean closedByWriter = false;
// 管道输入流是否关闭
volatile boolean closedByReader = false;
// 管道输入流和管道输出流是否连接
boolean connected = false;
// 向缓冲区写入数据的线程
Thread readSide;
// 读取缓冲区数据的线程
Thread writeSide;
// 缓冲区默认大小为1024字节
private static final int DEFAULT_PIPE_SIZE = 1024;
protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE;
// 缓冲区实际上为字节数组
protected byte buffer[];
// 接受管道输出流的字节存储到缓冲数组buffer的位置,in=-1时,缓冲区是空的
protected int in = -1;
// 管道输入流读取下一个字节的位置
protected int out = 0;
// 创建和管道输出流连接的管道输入流,缓冲区大小为默认的1024字节
public PipedInputStream(PipedOutputStream src) throws IOException {
this(src, DEFAULT_PIPE_SIZE);
}
// 创建和管道输入流连接的管道输出流,并指定缓冲区的大小pipeSize
public PipedInputStream(PipedOutputStream src, int pipeSize)
throws IOException {
initPipe(pipeSize);
connect(src);
}
// 创建未连接的管道输入流,缓冲区的大小为1024字节
public PipedInputStream() {
initPipe(DEFAULT_PIPE_SIZE);
}
// 创建未连接的管道输入流,并指定缓冲区的大小为pipeSize
public PipedInputStream(int pipeSize) {
initPipe(pipeSize);
}
// 创建指定大小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);
}
// 从管道输出流接受一个字节b存储到缓冲区
protected synchronized void receive(int b) throws IOException {
// 检查能否写入缓冲区的状态
checkStateForReceive();
writeSide = Thread.currentThread();
// 缓冲区数据已满,等待管道输入流读取数据
if (in == out)
awaitSpace();
// 缓冲区数据为空,重置in和out为0
if (in < 0) {
in = 0;
out = 0;
}
// 将字节数组b存储到缓冲区buffer中位置in上,并将in自加
buffer[in++] = (byte) (b & 0xFF);
if (in >= buffer.length) {
in = 0;
}
}
/**
* 接受管道输出流过来的字节数组b,off开始,长度len个字节写入缓冲区,实际写入的数据长度不一定
是len,要看缓冲区里面可存储的位置.
*/
synchronized void receive(byte b[], int off, int len) throws IOException {
checkStateForReceive();
writeSide = Thread.currentThread();
int bytesToTransfer = len;
while (bytesToTransfer > 0) {
// int=out,表示缓冲区已经满了,等待读取,int是表示缓冲区里面下一个字节存储的位置,out表示下一个读取字节的位置
if (in == out)
awaitSpace();
// nextTransferAmount表示实际缓冲区Buffer还能存储字节的长度,bytesToTransfer表示的是数组b实际上可写到缓冲区里面的长度
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 {
// 当in=-1表示剩余还能写入buffer.length-in,当in不等于-1的时候,表示---in<out--两边虚线位置的已经存在数据
//所以剩余能写入的长度是out-in
// 缓冲区里面只要out读取过了,那么就可以进行写入
nextTransferAmount = out - in;
}
}
if (nextTransferAmount > bytesToTransfer)
nextTransferAmount = bytesToTransfer;
// 断言,当nextTransferAmmount大于0.程序中断
assert (nextTransferAmount > 0);
// 写完以后会将字节数组里面起始位置off位置移动上次已经实际写入的长度,in也会相应移动本次实际写入缓冲里面长度
System.arraycopy(b, off, buffer, in, nextTransferAmount);
bytesToTransfer -= nextTransferAmount;
off += nextTransferAmount;
in += nextTransferAmount;
// 当in的位置如果超过缓冲区最大长度,那么就将in置为0
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();
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;
// 当in小于0,缓冲区为空,唤醒写入线程写入数据到缓冲区
while (in < 0) {
if (closedByWriter) {
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();
}
}
// 读取位置out的字节,out自加
int ret = buffer[out++] & 0xFF;
if (out >= buffer.length) {
out = 0;
}
// 缓冲区数据已满,从头开始
if (in == out) {
/* now empty */
in = -1;
}
return ret;
}
// 将缓冲区里面的数组读取到字节数组b的索引off开始,len个长度,所以最多读取长度是len
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;
// 当in大于out时,即out---in之间数组可以被读取
if (in > out) {
available = Math.min((buffer.length - out), (in - out));
} else {
// 当in小于等于out,即in out----buffer.length之间的数据可被读取
available = buffer.length - out;
}
// 缓冲区里面可读取的数据大于要读取的长度len-1,那么实际可以读取len-1
// len-1原因是off+rlen
if (available > (len - 1)) {
available = len - 1;
}
// 将要读取的字节复制到字节数组b中
System.arraycopy(buffer, out, b, off + rlen, available);
out += available;
rlen += available;
len -= available;
if (out >= buffer.length) {
out = 0;
}
// 缓冲区数据已满的情况下,将in置为-1
if (in == out) {
in = -1;
}
}
return rlen;
}
// 实际可以读取缓冲区里面数据的长度
public synchronized int available() throws IOException {
if (in < 0)
return 0;
// in==out表示缓冲区数据已满,可读数据长度为buffer.length;
else if (in == out)
return buffer.length;
// out----in,表示out到in之间数据还未读取
else if (in > out)
return in - out;
else
// ---in <out---,当in小于out的时候,说明in到out之间数据已经读取了,
// 剩下in+(buffer.length-out)两段之间的数据
return in + buffer.length - out;
}
// 关闭管道输入流
public void close() throws IOException {
closedByReader = true;
synchronized (this) {
in = -1;
}
}
}
总结:
- 管道输入流和管道输出流不要重复连接两次,否则会出现报错的情况.
- 不要在同一个线程里面用PipedOutputStream和PipedInputStream,否则会造成死锁的情况.
分析源码可以结合一下几个图例进行分析
1.管道输出流先向缓冲区里面写入800个字节,读取线程读取100个字节.此时out<in,剩余可读取的字节数为in-out.
2.然后又向缓冲区里面写入424个字节.此时out<in,可读取的字节数为buffer.length-out/in-out.
3.再次循环写入100个字节,此时in==out表示,缓冲区数据已经满了,等待读取线程读取数据.
4.然后读取线程读取了700个字节,那么此时out>in,此时可读取的字节数为in+(buffer.length-out).