Java 7之傳統I/O - PipedInputStream和PipedOutputStream

       PipedInputStream類與PipedOutputStream類用於在應用程序中創建管道通信.一個PipedInputStream實例對象必須和一個PipedOutputStream實例對象進行連接而產生一個通信管道.PipedOutputStream可以向管道中寫入數據,PipedIntputStream可以讀取PipedOutputStream向管道中寫入的數據.這兩個類主要用來完成線程之間的通信.一個線程的PipedInputStream對象能夠從另外一個線程的PipedOutputStream對象中讀取數據.如下圖所示。

        

        PipedInputStreamPipedOutputStream的實現原理類似於"生產者-消費者"原理,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





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章