Java中的管道流 PipedOutputStream和PipedInputStream
我們?cè)趯W(xué)習(xí)IO流的時(shí)候可能會(huì)學(xué)字節(jié)流、字符流等,但是關(guān)于管道流的相信大部分視頻或者教程都是一語(yǔ)帶過(guò),第一個(gè)是因?yàn)檫@個(gè)東西在實(shí)際開(kāi)發(fā)中用的也不是很多,但是學(xué)習(xí)無(wú)止境,存在既有理。JDK中既然有個(gè)類(lèi)那說(shuō)明他并不是一無(wú)是處,只是我們目前還沒(méi)有場(chǎng)景用到它,那說(shuō)明我們說(shuō)的還不夠,知識(shí)點(diǎn)還不足以去駕馭它。
管道流其實(shí)是一個(gè)很有魅力的流,用法也很獨(dú)特。他用來(lái)連接兩個(gè)線程之間的通信,比如傳輸文件等。它們的作用是讓多線程可以通過(guò)管道進(jìn)行線程間的通訊。在使用管道通信時(shí),必須將PipedOutputStream和PipedInputStream配套使用。費(fèi)話不多說(shuō),我們來(lái)看一個(gè)例子:
public class PipdTest {
public static void main(String[] args) throws IOException {
// 創(chuàng)建一個(gè)發(fā)送者對(duì)象
Sender sender = new Sender();
// 創(chuàng)建一個(gè)接收者對(duì)象
Receiver receiver = new Receiver();
// 獲取輸出管道流
PipedOutputStream outputStream = sender.getOutputStream();
// 獲取輸入管道流
PipedInputStream inputStream = receiver.getInputStream();
// 鏈接兩個(gè)管道,這一步很重要,把輸入流和輸出流聯(lián)通起來(lái)
outputStream.connect(inputStream);
// 啟動(dòng)發(fā)送者線程
sender.start();
// 啟動(dòng)接收者線程
receiver.start();
}
}
/**
* 發(fā)送線程
*
* @author yuxuan
*
*/
class Sender extends Thread {
// 聲明一個(gè) 管道輸出流對(duì)象 作為發(fā)送方
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 {
// 關(guān)閉輸出流
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 接收線程
*
* @author yuxuan
*
*/
class Receiver extends Thread {
// 聲明一個(gè) 管道輸入對(duì)象 作為接收方
private PipedInputStream inputStream = new PipedInputStream();
public PipedInputStream getInputStream() {
return inputStream;
}
@Override
public void run() {
byte[] buf = new byte[1024];
try {
// 通過(guò)read方法 讀取長(zhǎng)度
int len = inputStream.read(buf);
System.out.println(new String(buf, 0, len));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 關(guān)閉輸入流
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
上面的代碼有幾個(gè)點(diǎn)需要掌握清楚。
1、第一個(gè)就是connect方法,他的源碼是這么寫(xiě)的
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下一個(gè)字節(jié)將存儲(chǔ)在循環(huán)緩沖數(shù)組buffer的位置。
當(dāng)in<0說(shuō)明緩沖數(shù)組是空的;當(dāng)in==out說(shuō)明緩沖數(shù)組已滿。*/
snk.in = -1;
//代表該管道輸入流下一個(gè)要讀取的字節(jié)在循環(huán)緩沖數(shù)組中的位置
snk.out = 0;
//表示該管道輸入流是否與管道輸出流建立了連接,true為已連接
snk.connected = true;
}
我們可以看到,他是一個(gè)線程同步的方法,通過(guò)synchronized 關(guān)鍵字修飾。
除了調(diào)用connect方法之外,還可以在構(gòu)造函數(shù)中直接傳進(jìn)去,源碼如下:
當(dāng)然管道流也有一些注意事項(xiàng):
管道流僅用于多個(gè)線程之間傳遞信息,若用在同一個(gè)線程中可能會(huì)造成死鎖;
管道流的輸入輸出是成對(duì)的,一個(gè)輸出流只能對(duì)應(yīng)一個(gè)輸入流,使用構(gòu)造函數(shù)或者connect函數(shù)進(jìn)行連接;
一對(duì)管道流包含一個(gè)緩沖區(qū),其默認(rèn)值為1024個(gè)字節(jié),若要改變緩沖區(qū)大小,可以使用帶有參數(shù)的構(gòu)造函數(shù);
管道的讀寫(xiě)操作是互相阻塞的,當(dāng)緩沖區(qū)為空時(shí),讀操作阻塞;當(dāng)緩沖區(qū)滿時(shí),寫(xiě)操作阻塞;
管道依附于線程,因此若線程結(jié)束,則雖然管道流對(duì)象還在,仍然會(huì)報(bào)錯(cuò)“read dead end”;
管道流的讀取方法與普通流不同,只有輸出流正確close時(shí),輸出流才能讀到-1值。
下面我們來(lái)看write方法的源碼:
看到這里是不是一目了然了。以下還有一些注意事項(xiàng),我們來(lái)看:
PipedInputStream運(yùn)用的是一個(gè)1024字節(jié)固定大小的循環(huán)緩沖區(qū)。寫(xiě)入PipedOutputStream的數(shù)據(jù)實(shí)際上保存到對(duì)應(yīng)的 PipedInputStream的內(nèi)部緩沖區(qū)。從PipedInputStream執(zhí)行讀操作時(shí),讀取的數(shù)據(jù)實(shí)際上來(lái)自這個(gè)內(nèi)部緩沖區(qū)。如果對(duì)應(yīng)的 PipedInputStream輸入緩沖區(qū)已滿,任何企圖寫(xiě)入PipedOutputStream的線程都將被阻塞。而且這個(gè)寫(xiě)操作線程將一直阻塞,直至出現(xiàn)讀取PipedInputStream的操作從緩沖區(qū)刪除數(shù)據(jù)。
這意味著,向PipedOutputStream寫(xiě)數(shù)據(jù)的線程不應(yīng)該是負(fù)責(zé)從對(duì)應(yīng)PipedInputStream讀取數(shù)據(jù)的唯一線程。從圖二可以清楚地看出這里的問(wèn)題所在:假設(shè)線程t是負(fù)責(zé)從PipedInputStream讀取數(shù)據(jù)的唯一線程;另外,假定t企圖在一次對(duì) PipedOutputStream的write()方法的調(diào)用中向?qū)?yīng)的PipedOutputStream寫(xiě)入2000字節(jié)的數(shù)據(jù)。在t線程阻塞之前,它最多能夠?qū)懭?024字節(jié)的數(shù)據(jù)(PipedInputStream內(nèi)部緩沖區(qū)的大小)。然而,一旦t被阻塞,讀取 PipedInputStream的操作就再也不會(huì)出現(xiàn),因?yàn)閠是唯一讀取PipedInputStream的線程。這樣,t線程已經(jīng)完全被阻塞,同時(shí),所有其他試圖向PipedOutputStream寫(xiě)入數(shù)據(jù)的線程也將遇到同樣的情形。這并不意味著在一次write()調(diào)用中不能寫(xiě)入多于1024字節(jié)的數(shù)據(jù)。但應(yīng)當(dāng)保證,在寫(xiě)入數(shù)據(jù)的同時(shí),有另一個(gè)線程從PipedInputStream讀取數(shù)據(jù)。
從PipedInputStream讀取數(shù)據(jù)時(shí),如果符合下面三個(gè)條件,就會(huì)出現(xiàn)IOException異常:
試圖從PipedInputStream讀取數(shù)據(jù),
PipedInputStream的緩沖區(qū)為“空”(即不存在可讀取的數(shù)據(jù)),
最后一個(gè)向PipedOutputStream寫(xiě)數(shù)據(jù)的線程不再活動(dòng)(通過(guò)Thread.isAlive()檢測(cè))。
這是一個(gè)很微妙的時(shí)刻,同時(shí)也是一個(gè)極其重要的時(shí)刻。假定有一個(gè)線程w向PipedOutputStream寫(xiě)入數(shù)據(jù);另一個(gè)線程r從對(duì)應(yīng)的 PipedInputStream讀取數(shù)據(jù)。下面一系列的事件將導(dǎo)致r線程在試圖讀取PipedInputStream時(shí)遇到IOException異常:
w向PipedOutputStream寫(xiě)入數(shù)據(jù)。
w結(jié)束(w.isAlive()返回false)。
r從PipedInputStream讀取w寫(xiě)入的數(shù)據(jù),清空PipedInputStream的緩沖區(qū)。
r試圖再次從PipedInputStream讀取數(shù)據(jù)。這時(shí)PipedInputStream的緩沖區(qū)已經(jīng)為空,而且w已經(jīng)結(jié)束,從而導(dǎo)致在讀操作執(zhí)行時(shí)出現(xiàn)IOException異常。
如果一個(gè)寫(xiě)操作在PipedOutputStream上執(zhí)行,同時(shí)最近從對(duì)應(yīng)PipedInputStream讀取的線程已經(jīng)不再活動(dòng)(通過(guò) Thread.isAlive()檢測(cè)),則寫(xiě)操作將拋出一個(gè)IOException異常。假定有兩個(gè)線程w和r,w向 PipedOutputStream寫(xiě)入數(shù)據(jù),而r則從對(duì)應(yīng)的PipedInputStream讀取。下面一系列的事件將導(dǎo)致w線程在試圖寫(xiě)入 PipedOutputStream時(shí)遇到IOException異常:
寫(xiě)操作線程w已經(jīng)創(chuàng)建,但r線程還不存在。
w向PipedOutputStream寫(xiě)入數(shù)據(jù)。
讀線程r被創(chuàng)建,并從PipedInputStream讀取數(shù)據(jù)。
r線程結(jié)束。
w企圖向PipedOutputStream寫(xiě)入數(shù)據(jù),發(fā)現(xiàn)r已經(jīng)結(jié)束,拋出IOException異常。
此篇文章主要用于理解運(yùn)用管道流,如果在實(shí)際項(xiàng)目開(kāi)發(fā)中用到的話建議一定要研究透在用,他的坑可不止我上面諾列的這些哦
有問(wèn)題可以在下面評(píng)論,技術(shù)問(wèn)題可以私聊我
總結(jié)
以上是生活随笔為你收集整理的Java中的管道流 PipedOutputStream和PipedInputStream的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [转贴]ATOM和RSS的区别
- 下一篇: 取消开机时按F1