Java IO 裝飾模式簡(jiǎn)析

Java 的 IO 系統(tǒng)采用了裝飾器設(shè)計(jì)模式。其 IO 分為面向字節(jié)和面向字符兩種镜豹,面向字節(jié)以字節(jié)為輸入輸出單位帽蝶,面向字符以字符為輸入輸出單位。此外第队,在每部分中,又分為輸入和輸出兩部分刨秆,相互對(duì)應(yīng)凳谦,如InputStream類(lèi)型和OutputStream類(lèi)型。再往下分衡未,又分為數(shù)據(jù)源類(lèi)型和裝飾器類(lèi)型晾蜘。數(shù)據(jù)源類(lèi)型表示的是數(shù)據(jù)的來(lái)源和去處,而裝飾器類(lèi)型可以給輸入輸出賦予額外的功能眠屎。

?

Java IO的結(jié)構(gòu)

在使用中,為了得到我們需要的輸入輸出功能肆饶,我們常常需要將一個(gè)數(shù)據(jù)源對(duì)象和多個(gè)裝飾器對(duì)象組合起來(lái)改衩。例如,我們需要從本地文件中以緩沖的方式按字節(jié)讀入數(shù)據(jù)的話(huà)驯镊,就需要將一個(gè)FileInputStream對(duì)象和一個(gè)BufferedInputStream對(duì)象組合起來(lái)葫督,其中FileInputStream對(duì)象負(fù)責(zé)從文件中按字節(jié)為單位讀取數(shù)據(jù),而B(niǎo)ufferedInputStream對(duì)象負(fù)責(zé)對(duì)讀取數(shù)據(jù)進(jìn)行緩沖板惑。

如果不明白裝飾模式的話(huà)橄镜,Java IO 會(huì)變的難以理解。而如果不清楚 Java IO 的結(jié)構(gòu)的話(huà)冯乘,又會(huì)覺(jué)得它難以使用洽胶。這篇博客結(jié)合裝飾模式介紹了 Java IO 的結(jié)構(gòu),以及部分 IO 類(lèi)的實(shí)現(xiàn)裆馒。這其實(shí)是我的學(xué)習(xí)筆記姊氓,如有不足,歡迎指出喷好。

一翔横、輸入源

我們以輸入為例,講解 Java IO 的結(jié)構(gòu)梗搅。輸入的基本功能是將數(shù)據(jù)從某個(gè)輸入源中讀取出來(lái)禾唁。這個(gè)輸入源可能是文件效览,也有可能是一個(gè)ByteArray對(duì)象,也有可能是一個(gè)String對(duì)象荡短。數(shù)據(jù)源不同丐枉,讀入的方式也不同。因此肢预,Java 的開(kāi)發(fā)者為每種輸入源編寫(xiě)了相應(yīng)的輸入類(lèi)矛洞,有從文件中讀入數(shù)據(jù)的FileInputStream,有從ByteArray對(duì)象中讀入數(shù)據(jù)的ByteArrayInputStream烫映,……沼本。為了統(tǒng)一接口,減少重復(fù)代碼的編寫(xiě)锭沟,Java 的設(shè)計(jì)者從這些輸入類(lèi)中抽兆,抽取出了相同的部分,編寫(xiě)了抽象輸入類(lèi)InputStream族淮,作為所有輸入類(lèi)的基類(lèi)辫红。到目前為止,類(lèi)圖可以整理如下祝辣,為了方便敘述贴妻,省略了一些方法和成員變量。

?

輸入源的結(jié)構(gòu)

其中蝙斜,InputStream是一個(gè)抽象類(lèi)名惩,它是所有輸入源的父類(lèi)。它規(guī)定了輸入源的接口孕荠,其中娩鹉,read()為從輸入源中讀入一個(gè)字節(jié),并以返回值的形式返回稚伍。而read(byte[] b)為從輸入源中讀入一塊數(shù)據(jù)到byte[] b中弯予,其返回值為實(shí)際讀入的字節(jié)數(shù)。而read(byte[] b, int off, int len)則為從輸入源讀入len個(gè)字節(jié)个曙,填充到byte[] b的b[off]及之后的位置上锈嫩。

由于輸入源的讀入操作因輸入源而異,因此垦搬,InputStream中的read()方法是抽象的祠挫,由具體的輸入源子類(lèi)實(shí)現(xiàn)。

在InputStream中悼沿,read(byte[] b)和read(byte[] b, int off, int len)都是調(diào)用read()來(lái)實(shí)現(xiàn)的等舔,即不斷地使用read()來(lái)一個(gè)個(gè)地讀入字節(jié),并放到byte[] b的合適位置上糟趾。但這樣讀取慌植,效率其實(shí)并不高甚牲。以搬磚為例,我們從 A 處搬 10 塊磚給 B 處砌墻的老師傅蝶柿。以InputStream的邏輯來(lái)搬運(yùn)的話(huà)丈钙,我們需要從 A 處拿起一塊磚,跑到 B 處交汤,把磚給老師傅雏赦,跑回 B 處,再拿起一塊……芙扎。多跑了好多趟星岗,浪費(fèi)了好多時(shí)間,力氣大的話(huà)戒洼,完全可以拿起 10 塊磚俏橘,一次性搬完。所以圈浇,在其大多數(shù)子類(lèi)中寥掐,都重寫(xiě)了這些方法。

由于讀取文件需要調(diào)用操作系統(tǒng)的系統(tǒng)調(diào)用磷蜀,需要用C/C++來(lái)完成召耘,所以,在FileInputStream中褐隆,有兩個(gè)native方法污它,read0()和readBytes(byte[] b, int off, int len),分別用來(lái)調(diào)用系統(tǒng)調(diào)用讀取文件中的 1 個(gè)字節(jié)和調(diào)用系統(tǒng)調(diào)用讀取文件中的 1 堆字節(jié)妓灌。其他的讀取方法都是通過(guò)調(diào)用這兩個(gè)方法來(lái)實(shí)現(xiàn)的。

二蜜宪、裝飾器

有了輸入源之后虫埂,我們已經(jīng)可以完成各種讀入數(shù)據(jù)的操作了。我們可以從數(shù)據(jù)源中讀取一個(gè)字節(jié)圃验,或者一堆字節(jié)掉伏。但是,出于性能以及其他方面的考慮澳窑,我們通常還會(huì)給輸入操作添加一些功能斧散,如緩沖。

1. 緩沖

之前講過(guò)一個(gè)搬磚的例子摊聋,我們要從 A 處搬 10 塊磚給 B 處的老師傅鸡捐,考慮到老師傅今天砌墻任務(wù)繁重,之后很可能會(huì)再讓我們?nèi)ソo他搬磚麻裁,于是我們不如一次性多給他搬幾塊過(guò)去放在 B 處箍镜,他再要磚我們直接從 B 處拿給他就好了源祈,就不用再跑去 A 處搬磚過(guò)來(lái)了。這樣就節(jié)省了許多傳輸?shù)臅r(shí)間色迂。

緩沖就是這么個(gè)道理香缺。我們通常會(huì)給輸入和輸出都設(shè)立一個(gè)緩沖區(qū)⌒考慮到之后很可能會(huì)再次讀取數(shù)據(jù)图张,在讀入數(shù)據(jù)時(shí),除了我們需要的數(shù)據(jù)之外诈悍,還會(huì)多讀一些數(shù)據(jù)進(jìn)來(lái)祸轮,放到緩沖區(qū)里。每次讀入數(shù)據(jù)之前写隶,都會(huì)先看看緩沖區(qū)里有沒(méi)有我們要的數(shù)據(jù)倔撞,如果有的話(huà)就從緩沖區(qū)中讀入,沒(méi)有的話(huà)再去數(shù)據(jù)源里讀取慕趴。而在輸出數(shù)據(jù)時(shí)痪蝇,會(huì)先把數(shù)據(jù)輸出到緩沖區(qū)里去,當(dāng)緩沖區(qū)滿(mǎn)了冕房,再將緩沖區(qū)里的數(shù)據(jù)全部輸出到目的地里躏啰。

注意:緩沖區(qū)的讀寫(xiě)還要考慮數(shù)據(jù)的一致性問(wèn)題,這里沒(méi)有過(guò)多的闡述耙册。

2. 裝飾器類(lèi)

就像緩沖一樣给僵,我們通常會(huì)給輸入輸出加上一些額外的功能。于是問(wèn)題來(lái)了详拙,我們?cè)趺床拍茏屆糠N輸入源都具備這些功能呢帝际?最簡(jiǎn)單的,就是為每一種輸入源的每種額外功能都寫(xiě)一個(gè)類(lèi)饶辙,就像下面這樣(為了讓圖小一點(diǎn)蹲诀,省略了其他的輸入源)。

?

不使用裝飾器模式時(shí)的類(lèi)結(jié)構(gòu)

這樣的設(shè)計(jì)會(huì)帶來(lái)許多問(wèn)題弃揽。

首先脯爪,類(lèi)太多了。在不考慮功能組合的情況下矿微,如果有 m 個(gè)輸入源痕慢,要實(shí)現(xiàn) n 個(gè)功能,那就需要寫(xiě) m 乘 n 個(gè)類(lèi)涌矢,考慮功能組合的話(huà)掖举,還要更多。

其次娜庇,重復(fù)代碼太多拇泛。其實(shí)同一個(gè)功能的代碼都差不多滨巴,但要給每個(gè)輸入源都寫(xiě)一遍。寫(xiě)的時(shí)候麻煩俺叭,到時(shí)候要改這個(gè)功能的代碼恭取,還得一個(gè)個(gè)改過(guò)去,不利于維護(hù)熄守。

為了解決上面的問(wèn)題蜈垮,Java 的設(shè)計(jì)人員將各個(gè)功能拎了出來(lái),給每個(gè)功能單獨(dú)寫(xiě)了功能類(lèi)裕照,如通過(guò)BufferedInputStream類(lèi)來(lái)為輸入源提供緩沖功能攒发,通過(guò)DataInputStream類(lèi)來(lái)為輸入源提供基本類(lèi)型數(shù)據(jù)的讀入功能。請(qǐng)注意晋南,此時(shí)惠猿,功能類(lèi)僅僅提供了功能,它本身并不能從輸入源中讀取數(shù)據(jù)负间,所以在功能類(lèi)內(nèi)部都會(huì)有一個(gè)數(shù)據(jù)源類(lèi)的成員變量偶妖,從數(shù)據(jù)源中讀取數(shù)據(jù)的操作都是通過(guò)這個(gè)成員變量來(lái)完成的。就像下面這樣:

class Func1Decorator extends InputStream {

? ? private InputStream in;


? ? Func1Decorator(InputStream in){

? ? ? ? this.in = in;

? ? }


? ? public int read() {

? ? ? ? ...

? ? ? ? a = in.read();

? ? ? ? ...

? ? }

? ? ...

}

知識(shí)點(diǎn):其實(shí)從這里可以看出政溃,組合比繼承要更靈活趾访,因?yàn)榻M合可以和多態(tài)結(jié)合。

在功能類(lèi)初始化時(shí)董虱,就從外界傳入了輸入源對(duì)象扼鞋,其后,從數(shù)據(jù)源讀取數(shù)據(jù)的操作都由這個(gè)對(duì)象負(fù)責(zé)愤诱,而功能類(lèi)僅負(fù)責(zé)對(duì)讀入的數(shù)據(jù)進(jìn)行處理來(lái)完成其功能云头。

注意到,這里的功能類(lèi)還繼承了輸入源類(lèi)InputStream淫半。一方面溃槐,這是因?yàn)閺耐饨缈磥?lái),功能類(lèi)確實(shí)是一個(gè)InputStream撮慨,它實(shí)現(xiàn)了InputStream中所有的接口竿痰。它的語(yǔ)意是一個(gè)帶有Func1功能的InputStream脆粥。另一方面砌溺,這也方便了功能的組合,當(dāng)功能類(lèi)同時(shí)也是InputStream時(shí)变隔,要組合兩個(gè)功能到一起時(shí)规伐,只需要按一定的順序把一個(gè)功能類(lèi)的對(duì)象看作輸入源對(duì)象傳入進(jìn)去即可。如:

DataInputStream in = new DataInputStream(

? ? ? ? ? ? ? ? ? ? ? ? new BufferedInputStream(new FileInputStream("filename")));

上面這段代碼創(chuàng)建了一個(gè)能讀取基本數(shù)據(jù)類(lèi)型數(shù)據(jù)并帶有緩沖的文件輸入對(duì)象匣缘。因?yàn)楣δ茴?lèi)也是一個(gè)InputStream猖闪,它可以被當(dāng)作其他功能類(lèi)的數(shù)據(jù)源類(lèi)鲜棠,其他的功能類(lèi)會(huì)在它的read方法的基礎(chǔ)上,繼續(xù)拓展自己的功能培慌。

其實(shí)豁陆,之前我們所說(shuō)的功能類(lèi)就是裝飾器,用來(lái)給基礎(chǔ)類(lèi)擴(kuò)展功能吵护。而這種用組合語(yǔ)法利用多態(tài)為基礎(chǔ)類(lèi)擴(kuò)展功能的模式就是裝飾模式盒音。

3. 裝飾器模式的優(yōu)點(diǎn)

裝飾模式分離了裝飾類(lèi)和被裝飾類(lèi)的邏輯。裝飾器類(lèi)中保持了一個(gè)被裝飾對(duì)象的引用馅而,當(dāng)裝飾器類(lèi)需要底層的功能時(shí)祥诽,只需要通過(guò)這個(gè)引用調(diào)用對(duì)應(yīng)方法即可,并不需要了解其具體邏輯瓮恭。這對(duì)代碼的維護(hù)有很大的幫助雄坪。

裝飾模式可以減少類(lèi)的數(shù)量。在前面我們已經(jīng)看到了屯蹦,用純繼承語(yǔ)法來(lái)擴(kuò)展功能需要為每種基礎(chǔ)類(lèi)和功能的各種組合編寫(xiě)類(lèi)维哈,類(lèi)的數(shù)量會(huì)非常地多。而通過(guò)裝飾器模式颇玷,我們只需要寫(xiě)幾個(gè)裝飾器類(lèi)就可以了笨农。裝飾器類(lèi)中保持的被裝飾對(duì)象的引用,會(huì)發(fā)揮其多態(tài)性帖渠,我們傳入什么基礎(chǔ)類(lèi)對(duì)象谒亦,就執(zhí)行對(duì)應(yīng)的方法。這使得一個(gè)裝飾器類(lèi)可以和幾乎所有基礎(chǔ)類(lèi)(及其子類(lèi)空郊,從語(yǔ)義上來(lái)說(shuō)份招,子類(lèi)是特殊的父類(lèi))結(jié)合產(chǎn)生相應(yīng)的擴(kuò)展類(lèi)。

裝飾模式的擴(kuò)展性很好狞甚。當(dāng)要為基礎(chǔ)類(lèi)擴(kuò)展新的功能時(shí)锁摔,用純繼承語(yǔ)法需要為每種基礎(chǔ)類(lèi),為另外的各種功能組合編寫(xiě)類(lèi)哼审。但使用裝飾器模式的話(huà)谐腰,只需要編寫(xiě)一個(gè)裝飾器類(lèi)即可。

裝飾模式利用了組合語(yǔ)法涩盾,在復(fù)用代碼時(shí)十气,組合語(yǔ)法與繼承語(yǔ)法相比有一個(gè)明顯的優(yōu)點(diǎn),就是可以利用多態(tài)春霍,從而根據(jù)組合對(duì)象的不同能夠產(chǎn)生不同的語(yǔ)義砸西。

三、結(jié)構(gòu)

裝飾模式的通用類(lèi)圖如下:

?

裝飾器模式的通用類(lèi)圖

在我們之前的敘述中,是沒(méi)有中間這個(gè)Decorator抽象類(lèi)的芹枷。它是所有裝飾器類(lèi)的父類(lèi)衅疙,它一方面可以使類(lèi)的結(jié)構(gòu)更加清晰,另一方面這個(gè)抽象類(lèi)可以減少各個(gè)子類(lèi)中重復(fù)邏輯的書(shū)寫(xiě)鸳慈。當(dāng)然饱溢,我們剛才所敘述的也是裝飾模式,只不過(guò)沒(méi)有了Decorator抽象類(lèi)走芋,所有的裝飾器類(lèi)都是直接繼承自Component的理朋。這是一種簡(jiǎn)化的裝飾模式。當(dāng)裝飾器數(shù)量比較少時(shí)绿聘,可以省略裝飾器基類(lèi)嗽上。另外在確定只有一種Component時(shí),可以不寫(xiě)Component基類(lèi)熄攘,用那一個(gè)ConcreteComponent來(lái)代替Component基類(lèi)兽愤。

下面是 Java IO 的類(lèi)圖,只畫(huà)了字節(jié)流的輸入部分挪圾,其他部分相似浅萧。另外,因?yàn)轫?yè)面的大小是有限的哲思,而且一些類(lèi)在類(lèi)結(jié)構(gòu)中的位置是相似的洼畅,所以省略了一些類(lèi)。

?

Java IO 的結(jié)構(gòu)

其中棚赔,F(xiàn)ilterInputStream就是裝飾模式中的Decorator基類(lèi)帝簇。繼承自它的都是裝飾器類(lèi),它們?yōu)檩斎霐U(kuò)展了功能靠益。

四丧肴、參考資料

JDK文檔

《Thinking in Java》

《設(shè)計(jì)模式之禪》

每天都在分享文章,也每天都有人想要我出來(lái)給大家分享下怎么去學(xué)習(xí)Java胧后。大家都知道芋浮,我們是學(xué)Java全棧的,大家就肯定以為我有全套的Java系統(tǒng)教程壳快。沒(méi)錯(cuò)纸巷,我是有Java全套系統(tǒng)教程,進(jìn)扣裙【47】974【9726】所示眶痰,今天小編就免費(fèi)送!~

后記:對(duì)于大部分轉(zhuǎn)行的人來(lái)說(shuō)瘤旨,找機(jī)會(huì)把自己的基礎(chǔ)知識(shí)補(bǔ)齊,邊工作邊補(bǔ)基礎(chǔ)知識(shí)凛驮,真心很重要裆站。

“我們相信人人都可以成為一個(gè)程序員条辟,現(xiàn)在開(kāi)始黔夭,找個(gè)師兄宏胯,帶你入門(mén),學(xué)習(xí)的路上不再迷茫本姥。這里是ja+va修真院肩袍,初學(xué)者轉(zhuǎn)行到互聯(lián)網(wǎng)行業(yè)的聚集地。"

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婚惫,一起剝皮案震驚了整個(gè)濱河市氛赐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌先舷,老刑警劉巖艰管,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蒋川,居然都是意外死亡牲芋,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)捺球,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缸浦,“玉大人,你說(shuō)我怎么就攤上這事氮兵×阎穑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵泣栈,是天一觀的道長(zhǎng)卜高。 經(jīng)常有香客問(wèn)我,道長(zhǎng)南片,這世上最難降的妖魔是什么篙悯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮铃绒,結(jié)果婚禮上鸽照,老公的妹妹穿的比我還像新娘。我一直安慰自己颠悬,他們只是感情好矮燎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著赔癌,像睡著了一般诞外。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灾票,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天峡谊,我揣著相機(jī)與錄音,去河邊找鬼。 笑死既们,一個(gè)胖子當(dāng)著我的面吹牛濒析,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啥纸,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼号杏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了斯棒?” 一聲冷哼從身側(cè)響起盾致,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荣暮,沒(méi)想到半個(gè)月后庭惜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡穗酥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蜈块,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迷扇。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡百揭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜓席,到底是詐尸還是另有隱情器一,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布厨内,位于F島的核電站祈秕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雏胃。R本人自食惡果不足惜请毛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞭亮。 院中可真熱鬧方仿,春花似錦、人聲如沸统翩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厂汗。三九已至委粉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娶桦,已是汗流浹背贾节。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工汁汗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人栗涂。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓知牌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親戴差。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容