簡介
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;當寫指標等於讀指標,說明寫指標已經超圈了,那麼快取區的可用長度就是整個緩衝區的大小,不能再超過讀指標,不然會被理解為可用長度是大於的那一部分。
緩衝區有效資料長度的情況如下圖所示: