Java之IO(八)PipedIutputStream和PipedOutputStream

來源:互聯網
上載者:User

標籤:final   判斷   res   ==   result   關係   情況   exce   ima   

  轉載請註明源出處:http://www.cnblogs.com/lighten/p/7056278.html

1.前言

  本章介紹Java的IO體系中最後一對位元組流--管道流。之前在位元組數組流的時候就說過,其可以充當輸入輸出資料流的轉換作用,Java中還有一個管道流可以完成相似的功能,但是其主要作用是用於不同線程間的通訊,下面就具體講一下管道流是如何?的,以及相關例子。

  值得注意的是,在JDK源碼注釋中提到了,通常使用一個管道輸出資料流關聯一個管道輸入資料流形成管道會話。通常輸入資料流和輸出資料流是不在一個線程中的,如果在同一個線程中使用,可能會造成死結。當無法從輸入資料流中讀取資料的時候,輸出資料流會被中斷。

2.PipedOutputStream

  管道輸出資料流十分簡單,其繼承OutputStream,具體類結構如下:

   其接受一個管道輸入資料流,調用connect方法,判斷通過後會重設相關資料並賦值。當然也可以直接new一個管道輸出資料流,再通過connect方法關聯。

  兩個write方法都和輸出資料流沒有關係,是通過輸入資料流來進行操作的。

  flush方法和close方法也是通過管道輸入資料流進行操作的。

  整個輸入資料流的操作,都是通過輸入資料流完成的,所以接下來我們主要關注輸入資料流做了什麼。

3.PipedInputStream

  這個管道輸入資料流的內容也不多:

  上面就是主要的內容了,輸入資料流有一個buffer位元組數組,建構函式有輸出資料流的時候做了兩件事情,一個是initPipe初始化這個buffer,預設大小1024個位元組,另一個就是調用connect方法,connect方法實際上就是調用輸出資料流的connect,反過來修改了輸入資料流的相關欄位。如果輸入資料流構造時沒有輸出資料流,就需要使用connect方法進行關聯。但是切記,不管是輸入資料流初始化和輸出資料流初始化,connect方法無論是通過什麼途徑,都只能調用一次,也就是兩個對象相互關聯只能發生一次。

  下面我們關注一下輸出資料流write所調用輸入資料流的receive都做了些什麼:

  管道流輸出的時候就會調用輸入管道流的receive方法,最終寫入buffer中。注意buffer寫完了就會重設寫入下標in。receive一個數組也是判斷buffer剩餘空間是否足夠而已。read方法就是讀取這個數組中的內容了:

3.1 死結探究

   之前說了,如果寫入和讀取這兩個操作是在同一線程可能會發生死結,這裡具體看下是如何死結的,測試代碼如下:

    @Testpublic void test() throws IOException {PipedInputStream pis = new PipedInputStream();PipedOutputStream pos = new PipedOutputStream(pis);byte[] read = new byte[10];byte[] write = new byte[100];int count = 0;while(true) {pos.write(write);pis.read(read);System.out.println("完成寫入讀取次數:"+(++count));}}

  結果如下:

  計數到11的時候就不進行下去了,很顯然死結了。這個是怎麼產生的呢?回顧上面源碼:

  寫入資料時,調用了receive方法,獲得pis的對象鎖。寫完了資料就會釋放鎖了。read方法也需要擷取鎖,然後讀完了就釋放鎖。乍一看好像沒什麼問題啊,不應該產生死結的啊。但是問題出在write方法調用receive的一個方法awaitSpace上。因為寫入比讀取速度快,按照原始碼的做法就會造成in的值追上out,然後就一直等待輸入資料流使用,由於輸入資料流和輸出資料流在同一個線程,這裡就變成了一個死迴圈了,wait之後依舊是在等寫入消耗。寫入比讀取快也會造成wait。注釋上說的死結不知是否是這個樣子,如果是那麼說死結就感覺有點不恰當了。

 3.2 寫入讀取操作說明

  讀寫操作在不同線程的時候,資料從管道輸出資料流中寫入,調用管道輸入資料流的receive方法。裡面有一個緩衝數組buffer,用於接收write方法寫入的位元組。輸入資料流有兩個下標,一個in用於標記當前緩衝到的位元組數,一個out用於標記read方法讀取buffer的位置。寫入的時候,如果in==out,則暫停寫入,因為此時判斷寫入快過讀取,防止資料被覆蓋,這個時候寫入線程就會掛起,等待讀取線程讀取資料。讀取線程如果讀到in==out則認為寫入完成,讀取也就完成了。最初in是等於-1的,所以第一次讀取的時候,其也是一個掛起讀取線程等待寫入的過程。之後in只有在讀取線程讀完了所有寫入的時候才會為-1。下面這段在read()方法中,只有第一次讀取可能進入,因為如果有寫入的時候in就不會小於0。

   問題來了,如果寫入線程掛起了,讀取線程讀到目前寫入位置,即in==out時,是判斷讀取完成的,這個時候會出現什麼現象呢?

    @Testpublic void test2() throws IOException, InterruptedException {PipedInputStream pis = new PipedInputStream();PipedOutputStream pos = new PipedOutputStream(pis);CountDownLatch latch = new CountDownLatch(2);new Thread(new Runnable() {@Overridepublic void run() {byte[] b = new byte[1025];b[1024] = 1;try {pos.write(b , 0, 1022);Thread.sleep(2000);pos.write(b, 1022, b.length);pos.close();} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {ByteArrayOutputStream baos = new ByteArrayOutputStream();                byte[] buf = new byte[1024];                int len = 0;                try {                byte b;                while((b = (byte) pis.read()) != -1) {                baos.write(b);                }} catch (IOException e) {e.printStackTrace();}                byte[] result = baos.toByteArray();                System.out.println(result.length + ":" + Arrays.toString(result));                latch.countDown();}}).start();latch.await();}

  結果是:

  會出現異常,且只能讀取到第一次寫入的1022個位元組。這就會產生一個問題,在實際使用的時候就會發生先寫入一批資料,再寫入一批資料,但是可能讀取判斷第一批結束了的尷尬情況。有沒有什麼好的方法解決這個問題呢?至少我沒有找到什麼好辦法。使用管道流的時候最好一次寫入,並且初始化管道流的buffer大小最好是寫入的大小。其它的方法並不能完全防止這種現象發生,只是機率小一點而已,比如控制讀寫速度。如果真的要實現多次寫入並且要可靠,我能想到的辦法就只有,讀寫線程共用一個鎖,再加一個流結束標誌符了。但是這種方法也要萬分小心,在開始很容易產生死結,如果讀線程先擷取公用鎖,內部又擷取了管道流的鎖,即便釋放了管道流的鎖,寫線程也拿到外層的公用鎖。下面給一個demo,僅供參考,可能會有問題:

        static boolean end = false;@Testpublic void test3() throws IOException, InterruptedException {PipedInputStream pis = new PipedInputStream();PipedOutputStream pos = new PipedOutputStream(pis);Object monitor = new Object();CountDownLatch latch = new CountDownLatch(2);new Thread(new Runnable() {@Overridepublic void run() {int count = 3;while(count-->0) {synchronized (monitor) {System.out.println(count);byte[] b = new byte[1022];try {pos.write(b , 0, b.length);Thread.sleep(2000);pos.write(1);} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}monitor.notifyAll();try {monitor.wait(1000);} catch (InterruptedException e) {e.printStackTrace();}}}end = true;try {pos.close();} catch (IOException e) {e.printStackTrace();}latch.countDown();}}).start();new Thread(new Runnable() {@Overridepublic void run() {ByteArrayOutputStream baos = new ByteArrayOutputStream();                byte[] buf = new byte[1024];                int len = 0;                try {                System.out.println("reading");                synchronized (monitor) {                while (!end) {                len = pis.read(buf);    System.out.println("reading...");        baos.write(buf, 0, len);        monitor.notifyAll();try {monitor.wait(1000);} catch (InterruptedException e) {e.printStackTrace();}    }}} catch (IOException e) {e.printStackTrace();}                byte[] result = baos.toByteArray();                System.out.println(result.length + ":" + Arrays.toString(result));                latch.countDown();}}).start();latch.await();}

  再次聲明,此代碼沒有經過仔細思考,只是提供一個思路,出現問題概不負責,使用管道流最好還是一次性寫入所有資料比較好。

  

Java之IO(八)PipedIutputStream和PipedOutputStream

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.