java 管道 和 流的区别,Java中的管道流 PipedOutputStream和PipedInputStream

我們在學習IO流的時候可能會學字節流、字符流等,但是關於管道流的相信大部分視頻或者教程都是一語帶過,第一個是因為這個東西在實際開發中用的也不是很多,但是學習無止境,存在既有理。JDK中既然有個類那說明他並不是一無是處,只是我們目前還沒有場景用到它,那說明我們說的還不夠,知識點還不足以去駕馭它。

eccd870f973f91af191fa6a0d2901fb0.png

管道流其實是一個很有魅力的流,用法也很獨特。他用來連接兩個線程之間的通信,比如傳輸文件等。它們的作用是讓多線程可以通過管道進行線程間的通訊。在使用管道通信時,必須將PipedOutputStream和PipedInputStream配套使用。費話不多說,我們來看一個例子:

public class PipdTest {

public static void main(String[] args) throws IOException {

// 創建一個發送者對象

Sender sender = new Sender();

// 創建一個接收者對象

Receiver receiver = new Receiver();

// 獲取輸出管道流

PipedOutputStream outputStream = sender.getOutputStream();

// 獲取輸入管道流

PipedInputStream inputStream = receiver.getInputStream();

// 鏈接兩個管道,這一步很重要,把輸入流和輸出流聯通起來

outputStream.connect(inputStream);

// 啟動發送者線程

sender.start();

// 啟動接收者線程

receiver.start();

}

}

/**

* 發送線程

*

* @author yuxuan

*

*/

class Sender extends Thread {

// 聲明一個 管道輸出流對象 作為發送方

private PipedOutputStream outputStream = new PipedOutputStream();

public PipedOutputStream getOutputStream() {

return outputStream;

}

@Override

public void run() {

String msg = "Hello World";

try {

outputStream.write(msg.getBytes());

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

// 關閉輸出流

outputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

/**

* 接收線程

*

* @author yuxuan

*

*/

class Receiver extends Thread {

// 聲明一個 管道輸入對象 作為接收方

private PipedInputStream inputStream = new PipedInputStream();

public PipedInputStream getInputStream() {

return inputStream;

}

@Override

public void run() {

byte[] buf = new byte[1024];

try {

// 通過read方法 讀取長度

int len = inputStream.read(buf);

System.out.println(new String(buf, 0, len));

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

// 關閉輸入流

inputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

上面的代碼有幾個點需要掌握清楚。

1、第一個就是connect方法,他的源碼是這么寫的

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;

/*代表連接該管道輸入流的輸出流PipedOutputStream下一個字節將存儲在循環緩沖數組buffer的位置。

當in<0說明緩沖數組是空的;當in==out說明緩沖數組已滿。*/

snk.in = -1;

//代表該管道輸入流下一個要讀取的字節在循環緩沖數組中的位置

snk.out = 0;

//表示該管道輸入流是否與管道輸出流建立了連接,true為已連接

snk.connected = true;

}

我們可以看到,他是一個線程同步的方法,通過synchronized 關鍵字修飾。

除了調用connect方法之外,還可以在構造函數中直接傳進去,源碼如下:

8b0db76cb82a12dc83edf344c1767fcd.png

當然管道流也有一些注意事項:

管道流僅用於多個線程之間傳遞信息,若用在同一個線程中可能會造成死鎖;

管道流的輸入輸出是成對的,一個輸出流只能對應一個輸入流,使用構造函數或者connect函數進行連接;

一對管道流包含一個緩沖區,其默認值為1024個字節,若要改變緩沖區大小,可以使用帶有參數的構造函數;

管道的讀寫操作是互相阻塞的,當緩沖區為空時,讀操作阻塞;當緩沖區滿時,寫操作阻塞;

管道依附於線程,因此若線程結束,則雖然管道流對象還在,仍然會報錯“read dead end”;

管道流的讀取方法與普通流不同,只有輸出流正確close時,輸出流才能讀到-1值。

下面我們來看write方法的源碼:

f06810e4dc2e564efcfe95826786e51e.png

看到這里是不是一目了然了。以下還有一些注意事項,我們來看:

PipedInputStream運用的是一個1024字節固定大小的循環緩沖區。寫入PipedOutputStream的數據實際上保存到對應的 PipedInputStream的內部緩沖區。從PipedInputStream執行讀操作時,讀取的數據實際上來自這個內部緩沖區。如果對應的 PipedInputStream輸入緩沖區已滿,任何企圖寫入PipedOutputStream的線程都將被阻塞。而且這個寫操作線程將一直阻塞,直至出現讀取PipedInputStream的操作從緩沖區刪除數據。

這意味着,向PipedOutputStream寫數據的線程不應該是負責從對應PipedInputStream讀取數據的唯一線程。從圖二可以清楚地看出這里的問題所在:假設線程t是負責從PipedInputStream讀取數據的唯一線程;另外,假定t企圖在一次對 PipedOutputStream的write()方法的調用中向對應的PipedOutputStream寫入2000字節的數據。在t線程阻塞之前,它最多能夠寫入1024字節的數據(PipedInputStream內部緩沖區的大小)。然而,一旦t被阻塞,讀取 PipedInputStream的操作就再也不會出現,因為t是唯一讀取PipedInputStream的線程。這樣,t線程已經完全被阻塞,同時,所有其他試圖向PipedOutputStream寫入數據的線程也將遇到同樣的情形。這並不意味着在一次write()調用中不能寫入多於1024字節的數據。但應當保證,在寫入數據的同時,有另一個線程從PipedInputStream讀取數據。

從PipedInputStream讀取數據時,如果符合下面三個條件,就會出現IOException異常:

試圖從PipedInputStream讀取數據,

PipedInputStream的緩沖區為“空”(即不存在可讀取的數據),

最后一個向PipedOutputStream寫數據的線程不再活動(通過Thread.isAlive()檢測)。

這是一個很微妙的時刻,同時也是一個極其重要的時刻。假定有一個線程w向PipedOutputStream寫入數據;另一個線程r從對應的 PipedInputStream讀取數據。下面一系列的事件將導致r線程在試圖讀取PipedInputStream時遇到IOException異常:

w向PipedOutputStream寫入數據。

w結束(w.isAlive()返回false)。

r從PipedInputStream讀取w寫入的數據,清空PipedInputStream的緩沖區。

r試圖再次從PipedInputStream讀取數據。這時PipedInputStream的緩沖區已經為空,而且w已經結束,從而導致在讀操作執行時出現IOException異常。

如果一個寫操作在PipedOutputStream上執行,同時最近從對應PipedInputStream讀取的線程已經不再活動(通過 Thread.isAlive()檢測),則寫操作將拋出一個IOException異常。假定有兩個線程w和r,w向 PipedOutputStream寫入數據,而r則從對應的PipedInputStream讀取。下面一系列的事件將導致w線程在試圖寫入 PipedOutputStream時遇到IOException異常:

寫操作線程w已經創建,但r線程還不存在。

w向PipedOutputStream寫入數據。

讀線程r被創建,並從PipedInputStream讀取數據。

r線程結束。

w企圖向PipedOutputStream寫入數據,發現r已經結束,拋出IOException異常。

此篇文章主要用於理解運用管道流,如果在實際項目開發中用到的話建議一定要研究透在用,他的坑可不止我上面諾列的這些哦

有問題可以在下面評論,技術問題可以私聊我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值