PushbackInputStream和PushbackReader是Java I/O系統(tǒng)里兩個(gè)比較讓人迷惑的類,我以前對(duì)它(們)就不太了解,直到某一天看了以前水母Java版的牛人zms的評(píng)論和一些資料以后才有所獲益。這是幾個(gè)月以前的事情了,這幾天寫有關(guān)序列化的總結(jié)時(shí)才想到這也不失為一個(gè)好的話題。
一個(gè)允許你反悔的hook
Java I/O系統(tǒng)是一個(gè)典型的Decorator模式的實(shí)現(xiàn),它以InputStream/OutputStream為基本核心,通過繼承關(guān)系,不斷為該核心添加新的功能,如文件流、緩沖、加解密等。對(duì)I/O系統(tǒng)設(shè)計(jì)模式感興趣的話,可以參考developerWorks上的一篇文章:從Java類庫(kù)看設(shè)計(jì)模式。Java I/O默認(rèn)是不緩沖流的,所謂“緩沖”就是先把從流中得到的一塊字節(jié)序列暫存在一個(gè)被稱為buffer的內(nèi)部字節(jié)數(shù)組里,然后你可以一下子取到這一整塊的字節(jié)數(shù)據(jù),沒有緩沖的流只能一個(gè)字節(jié)一個(gè)字節(jié)讀,效率孰高孰低一目了然。有兩個(gè)特殊的輸入流實(shí)現(xiàn)了緩沖功能,一個(gè)是我們常用的BufferedInputStream,像讀文件我們常用
BufferedInputStream in = new BufferedInputStream(new FileInputStream("datafile")); while ((b = in.read()) != -1) { ... } in.close(); |
這是我們幾乎不用查什么JDK文檔就能信手拈來的代碼段,寫的時(shí)候也應(yīng)該思考一下套一個(gè)BufferedInputStream的意義何在。另一個(gè)就是我們不怎么看到的PushbackInputStream(其對(duì)應(yīng)的字符流模式為PushbackReader)。
在通常狀態(tài)下,“流”意味著“一次性”,就是說你進(jìn)行了一次操作后它的狀態(tài)就變了,譬如讀,無論是文件還是socket,你讀的過程中一個(gè)潛在的“讀指針”一樣的東東就在移動(dòng),你無法在讀以后再重新定位(當(dāng)然RandomAccessFile是另一種情況),如果你以前奇怪為什么數(shù)據(jù)庫(kù)操作中ResultSet里get某個(gè)字段以后就不能再第二次get它了,這里或許是個(gè)解釋。但好在PushbackInputStream給了我們第二次讀的機(jī)會(huì)。我們先來區(qū)別一下“監(jiān)聽”和“截獲”的概念,“監(jiān)聽”就是把得到的消息copy一份,原始消息并不作任何改變地傳遞到目的地;而“截獲”則是先把消息“扣押”下來,不讓其自動(dòng)轉(zhuǎn)給目標(biāo),而是先進(jìn)行一些處理以后在轉(zhuǎn)發(fā)給目標(biāo)(如果是網(wǎng)絡(luò)安全專業(yè)的背景知識(shí),大概知道“監(jiān)聽”是對(duì)“機(jī)密性”的攻擊,而“截獲”不僅是對(duì)“機(jī)密性”還是對(duì)“完整性”的攻擊)。有的朋友大概對(duì)hook這個(gè)名詞有些了解,它是一種Windows的一種消息處理機(jī)制,似乎就是一種消息截獲手段,但我對(duì)Windows編程幾乎一竅不通;此外,如果你熟悉Servlet的話,也能找到像Filter這樣的處理機(jī)制,在對(duì)每個(gè)HTTP請(qǐng)求/應(yīng)答進(jìn)行轉(zhuǎn)發(fā)之前,先在里頭耍一點(diǎn)花招,確定哪些予以轉(zhuǎn)發(fā),哪些屏蔽掉,這也算是“截獲”吧。通過上面的介紹,我們不妨把PushbackInputStream看成是對(duì)輸入流的一種“截獲”手段,其中最重要的方法是unread:
public void unread(int b) throws IOException public void unread(byte[] b) throws IOException public void unread(byte[] b, int off, int len) throws IOException |
我們可以想象一下,PushbackInputStream內(nèi)置一個(gè)緩沖區(qū)(事實(shí)上,你可以從它的源代碼里找到這個(gè)protected的字節(jié)數(shù)組),當(dāng)?shù)蛯恿鬟M(jìn)來時(shí)先流進(jìn)這個(gè)buffer,在你把流“物歸原主”之前還有機(jī)會(huì)對(duì)它耍花招,然后再用unread方法“反悔”一下,把緩沖區(qū)里已經(jīng)讀過的內(nèi)容(一般是沒有被改動(dòng)的,當(dāng)然你也可以改動(dòng)它,那就失去“歸趙”的意義了,因?yàn)橐呀?jīng)不是“完璧”了)再插入到流的頭部,下次讀的時(shí)候是流剩余的部分再加上從緩沖區(qū)“歸還”的部分。上面三個(gè)unread方法分別代表從緩沖區(qū)“歸還”一個(gè)字節(jié)、一個(gè)字節(jié)數(shù)組以及一個(gè)字節(jié)數(shù)組中指定的部分。
PushbackInputStream是對(duì)二進(jìn)制流的處理,字符流下相對(duì)應(yīng)的就是PushbackReader。
有什么用?
學(xué)過編譯的話就容易理解了,比如從左向右掃描字符流“for(int i=0;i<10;i++)”,掃描到“for”是不是就可以說是個(gè)關(guān)鍵字了呢?不行,說不定后面是“for1”,那就是個(gè)變量而不是關(guān)鍵字了,知道看到“(”才恍然大悟,哦,我可以安全地說“看到for關(guān)鍵字”了,但“(”還得歸還給輸入流,因?yàn)樾枰竺胬^續(xù)掃描。在上下文相關(guān)語言里,就更需要這種補(bǔ)償機(jī)制。又如,在解析HTML文檔的時(shí)候,我需要根據(jù)它的“meta”標(biāo)簽的“charset”屬性來決定使用哪種字符集進(jìn)行解析,但HTML可不是“charset”而是“<html>”開頭的哦!所以需要通過PushbackInputStream緩沖前面一段內(nèi)容,等取到字符集名稱后在把讀到的流全部歸還,再用指定的字符集進(jìn)行解析。
參考資料
- Java Network Programming. by Elliotte R. Harold
- zms兄在水母的帖子. by zms(無奈的是,水木清華已經(jīng)不能對(duì)校外開放了)
- JDK 1.4.2 Documentation. by java.sun.com