我們在學習IO流的時候可能會學字節流、字符流等,但是關於管道流的相信大部分視頻或者教程都是一語帶過,第一個是因為這個東西在實際開發中用的也不是很多,但是學習無止境,存在既有理。JDK中既然有個類那說明他並不是一無是處,只是我們目前還沒有場景用到它,那說明我們說的還不夠,知識點還不足以去駕馭它。
管道流其實是一個很有魅力的流,用法也很獨特。他用來連接兩個線程之間的通信,比如傳輸文件等。它們的作用是讓多線程可以通過管道進行線程間的通訊。在使用管道通信時,必須將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方法之外,還可以在構造函數中直接傳進去,源碼如下:
當然管道流也有一些注意事項:
管道流僅用於多個線程之間傳遞信息,若用在同一個線程中可能會造成死鎖;
管道流的輸入輸出是成對的,一個輸出流只能對應一個輸入流,使用構造函數或者connect函數進行連接;
一對管道流包含一個緩沖區,其默認值為1024個字節,若要改變緩沖區大小,可以使用帶有參數的構造函數;
管道的讀寫操作是互相阻塞的,當緩沖區為空時,讀操作阻塞;當緩沖區滿時,寫操作阻塞;
管道依附於線程,因此若線程結束,則雖然管道流對象還在,仍然會報錯“read dead end”;
管道流的讀取方法與普通流不同,只有輸出流正確close時,輸出流才能讀到-1值。
下面我們來看write方法的源碼:
看到這里是不是一目了然了。以下還有一些注意事項,我們來看:
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異常。
此篇文章主要用於理解運用管道流,如果在實際項目開發中用到的話建議一定要研究透在用,他的坑可不止我上面諾列的這些哦
有問題可以在下面評論,技術問題可以私聊我