PipedInputStream類與PipedOutputStream類用於在應用程序中創建管道通信.一個PipedInputStream實例對象必須和一個PipedOutputStream實例對象進行連接而產生一個通信管道.PipedOutputStream可以向管道中寫入數據,PipedIntputStream可以讀取PipedOutputStream向管道中寫入的數據.這兩個類主要用來完成線程之間的通信.一個線程的PipedInputStream對象能夠從另外一個線程的PipedOutputStream對象中讀取數據.如下圖所示。
PipedInputStream和PipedOutputStream的實現原理類似於"生產者-消費者"原理,PipedOutputStream是生產者,PipedInputStream是消費者,在PipedInputStream中有一個buffer字節數組,默認大小爲1024,作爲緩衝區,存放"生產者"生產出來的東西.還有兩個變量in和out。in是用來記錄"生產者"生產了多少,out是用來記錄"消費者"消費了多少,in爲-1表示消費完了,in==out表示生產滿了.當消費者沒東西可消費的時候,也就是當in爲-1的時候,消費者會一直等待,直到有東西可消費.
1、創建連接與初始化
在兩者的構造函數中,都相互提供了連接的構造方法,分別用於接收對方的管道實例,然後調用各自的connect()方法進行連接,如PipedInputStream:
PipedInputStream
private static final int DEFAULT_PIPE_SIZE = 1024;
protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE;
// 省略部分構造函數
public PipedInputStream(PipedOutputStream src, int pipeSize) throws IOException {
initPipe(pipeSize);
connect(src);
}
private void initPipe(int pipeSize) { // 初始化buffer的大小,pipeSize可以通過構造方法指定,也可以使用默認的PIPE_SIZE的大小
if (pipeSize <= 0) {
throw new IllegalArgumentException("Pipe Size <= 0");
}
buffer = new byte[pipeSize]; // 初始化緩衝區的大小
}
public void connect(PipedOutputStream src) throws IOException {
src.connect(this); // 連接輸入管道
}
看一下PipedOutputStream的構造函數,如下:
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;
snk.in = -1; // buffer數組中無數據
snk.out = 0; // 取出的數據爲0
snk.connected = true;// 表示連接成功
}
可以看到,相互之間會調用connect()方法來連接,其效果是一樣的。
2、數據的寫入與讀取
連接成功就,就可以進行數據的寫入與讀出操作了,在PipedOutputStream中的write()寫法如下:
PipedOutputStream
public void write(int b) throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
}
sink.receive(b); // 調用receive方法進行數據的輸出
}
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);
}
方法在寫入到byte[]數組緩存區數據後,就會調用PipedInputStream中的receive方法。輸出管道中的receive()方法如下:
PipedInputStream
protected synchronized void receive(int b) throws IOException { // 只會在PipedOutputStream類的write()中調用
checkStateForReceive();
writeSide = Thread.currentThread();
if (in == out) // in==out表示buffer數組已滿
awaitSpace(); // 等待空閒空間
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 { // 將下標off開始的len個數組數據寫入到輸出管道中
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); // nextTransferAmount<=0,則終止程序的執行
System.arraycopy(b, off, buffer, in, nextTransferAmount);// 拷貝數組中數據到緩衝區
bytesToTransfer -= nextTransferAmount;
off += nextTransferAmount;
in += nextTransferAmount;
if (in >= buffer.length) {
in = 0;
}
}
}
輸入管理通過如上的對應方法接收到數據並保存到輸入緩衝區後,下面就可以使用read()方法讀出這些數據了。
看一下awaitSpace()方法的實現源代碼:
PipedInputStream
/*
* 若寫入管道的數據正好全部被讀取完(例如,管道緩衝滿),則執行awaitSpace()操作;
* 讓讀取管道的線程管道產生讀取數據請求,從而才能繼續的向“管道”中寫入數據
*/
private void awaitSpace() throws IOException {
/*
* 如果管道中被讀取的數據,等於寫入管道的數據時,
* 則每隔1000ms檢查“管道狀態”,並喚醒管道操作:若有讀取管道數據線程被阻塞,則喚醒該線程
*/
while (in == out) {
checkStateForReceive();
/* full: kick any waiting readers */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
}
PipedInputStream類中的read()方法源代碼如下:
PipedInputStream
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) { // 如果in<0(表示管道中無數據)且closedByWriter爲true(表示輸入管道已經關閉)則直接返回-1
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;
}
/* possibly wait on the first character */
int c = read();
if (c < 0) {
return -1;
}
b[off] = (byte) c;
int rlen = 1;
while ((in >= 0) && (len > 1)) {
int available;
if (in > out) {
available = Math.min((buffer.length - out), (in - out));
} else {
available = buffer.length - out;
}
// A byte is read beforehand outside the loop
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) {
in = -1;
}
}
return rlen;
}
3、刷新與關閉管道
來看一下管道輸出流中的刷新和關閉方法,源代碼如下:
PipedOutputStream
// 刷回管道輸出流
public synchronized void flush() throws IOException {
if (sink != null) {
synchronized (sink) {
/*
* 調用管道輸入流的notifyAll(),通知管道輸入流放棄對當前資源的佔有,
* 讓其它的等待線程(等待讀取管道輸出流的線程)讀取管道輸出流的值。
*/
sink.notifyAll();
}
}
}
// 關閉管道輸出流
public void close() throws IOException {
if (sink != null) {
sink.receivedLast();// 通知管道輸入流,輸出管理已經關閉
}
}
看一下receivedLast()方法,如下:
PipedInputStream
synchronized void receivedLast() {
closedByWriter = true; // 輸出管道標誌爲true,表示關閉
notifyAll(); // 喚醒所有的等待線程
}
通知所有的等待線程,最後的數據已經全部到達。PipedInputStream
public void close() throws IOException { // 關閉管道輸出流
closedByReader = true;
synchronized (this) {
in = -1; // 清空緩衝區數據
}
}
下面來具體舉一個例子,如下:
public class test04 {
public static void main(String [] args) {
Sender sender = new Sender();
Receiver receiver = new Receiver();
PipedOutputStream outStream = sender.getOutStream();
PipedInputStream inStream = receiver.getInStream();
try {
//inStream.connect(outStream); // 與下一句一樣
outStream.connect(inStream);
} catch (Exception e) {
e.printStackTrace();
}
sender.start();
receiver.start();
}
}
class Sender extends Thread {
private PipedOutputStream outStream = new PipedOutputStream();
public PipedOutputStream getOutStream() {
return outStream;
}
public void run() {
String info = "hello, receiver";
try {
outStream.write(info.getBytes());
outStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Receiver extends Thread {
private PipedInputStream inStream = new PipedInputStream();
public PipedInputStream getInStream() {
return inStream;
}
public void run() {
byte[] buf = new byte[1024];
try {
int len = inStream.read(buf);
System.out.println("receive message from sender : " + new String(buf, 0, len));
inStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
最後運行後輸出的結果如下:receive message from sender : hello, receiver
參考文獻:
1、http://www.cnblogs.com/lich/archive/2011/12/11/2283928.html
2、http://www.cnblogs.com/skywang12345/p/io_04.html