說起來,我接觸 Java 是從大一下學(xué)期開始的,通過逐步學(xué)習(xí)接觸到了 Java IO 這個很重要的模塊,同時也是讓我很頭疼的一個模塊,好像一直都沒怎么學(xué)會。在我的腦子里,一直都是那種,這么多相關(guān)的類,怎么能記住啊。再加上,后來轉(zhuǎn)戰(zhàn) Android 就更少有直接使用 Java IO 去操作文件的機會。一個偶然的機會,我學(xué)到了可以從另一種視角看 Java IO,突然覺得,Java IO,并不難。現(xiàn)在,我來記錄并分享一點自己的經(jīng)驗。
如果是講操作文件的 API 的話,那估計得寫的老長,事實上,我對 Java IO 相關(guān)的 API 并不太熟,而且我也不打算講。在這里我可能是從另一種我覺得是站在全局的角度去看 Java IO。
通常的,我們使用 Java IO 都是為了去對文件進(jìn)行操作,或讀或?qū)憽Wx:從磁盤、網(wǎng)絡(luò)等渠道將文件內(nèi)容讀入到內(nèi)存中;寫:通過將內(nèi)存中的數(shù)據(jù)寫入到磁盤、網(wǎng)絡(luò)等進(jìn)行持久化保存。
以前啊,我就光想著 Java IO 就是這么讀文件,寫文件,也沒什么的嘛。不就是對文件的操作嘛,沒什么可講的的,現(xiàn)在我不這么想了。現(xiàn)在怎么想呢?我換了一種思路。
我們使用 Java IO 寫東西,寫的是什么?是字符串,字符串是什么?是字符數(shù)組組成的一種的對象,什么是字符?字符就是可以通過字節(jié)碼的形式表現(xiàn)出來一種東西。而字節(jié)碼就是 OutputStream 以及 InputStream 直接能進(jìn)行操作的東西,那什么是它們不是直接操作的?不就是字符串、字符數(shù)組嘛。可是我們在寫程序的時候,誰還記得這個字符串的字節(jié)碼是什么,那個字符的字節(jié)碼是啥。我們都是直接操作字符串以及少部分情況操作字符數(shù)組的。那怎么辦?
有的人說,通過各種轉(zhuǎn)換就可以對應(yīng)的字節(jié)碼,然后進(jìn)行操作。事實上就是這樣的,Java 提供給我們的方法也是如此,通過各種轉(zhuǎn)換得到可以操作 String 或是字符數(shù)組,然后進(jìn)行操作。比如,OutputStreamWriter 就可以使用 OutputStreamWriter.write(String);
來直接操作字符串進(jìn)行寫入文件,還有通過 BufferedReader 就可以使用 String lineString = bufferedReader.readLine()
來直接進(jìn)行讀取一行文字。可是總一些我們可以從另一種角度看的東西。
仔細(xì)看一下,第二幅圖,沒什么啊,不就是數(shù)據(jù)在他們之間進(jìn)行轉(zhuǎn)換嗎,有什么的?是這樣的,但是還可以有另一種看法,字符串就是另種形式的字符數(shù)組,而字節(jié)數(shù)組就是另一種形式的字符數(shù)組。他們還是自己本身,只不過穿上了一件 “衣服” 而已。穿上了這身衣服,你以為他變了,從字符串 String
變成了字符數(shù)組 char[]
實際上,他還是 String ,只是不同的表現(xiàn)形態(tài)而已。沒有什么不同,他還是他,只是你需要看穿這件 “衣服”。
那你說,既然數(shù)據(jù)本身都沒有變化,只是穿了件衣服,那為什么 Java IO 的操作,顯得那么的麻煩?因為,為了方便處理,也給處理過程穿上了衣服。OutputStream 只能處理字節(jié)數(shù)組,而包裹了一層 OutputStream 的 OutputStreamWriter 就能直接的操作 char[] 和 String。
好麻煩啊,就不能有一個直接能用來操作字符串的對象嗎,每回寫文件讀寫的時候,都得寫好幾個對象,煩死了。別煩啊,這樣做是有這樣做的理由的。就拿讀操作(InputStream)來說吧,我曾天真的以為讀操作只有一個來源就是,文件(FileInputStream)。后來才發(fā)現(xiàn),竟然有 ByteArrayInputStream、ObjectInputStream、PipedInputStream、StringBufferInputStream 等等(只列出部分,全部的都 在這 呢。他們分別處理字節(jié)數(shù)組、被序列化的對象、線程間的輸入流以及來自 StringBuffer 的輸入。對應(yīng)的還有寫操作(OutputStream)整體來說,雖然比 InputStream 少,但是也有好幾個呢,ByteArrayOutputStream、 FileOutputStream、FilterOutputStream、 ObjectOutputStream、PipedOutputStream。
如果這么多種輸入或是輸出方式,都要能處理 String、long、int 這種,或是再兼容一些其他方便我們程序員操作的方法,那類就太多了,想想一下,一個 FileInputStream 就可能變成了 StringFileInputStream、LongFileInputStream、IntegerFileInputStream ,想想就覺得可怕。所以,Java 采用了穿 “衣服” 的形式,讓程序員通過對數(shù)據(jù)穿上不同的衣服,從而達(dá)到操作不同數(shù)據(jù)類型的效果。
FileInputStream 只能讀出字節(jié)數(shù)組,InputStreamReader 可以讀出字符數(shù)組,通過不同的對象進(jìn)行轉(zhuǎn)換,就能讀出或是寫出不同形式的數(shù)據(jù)。而且 FileInputStream 可以作為 InputStreamReader 的提供者,這樣的話,就能應(yīng)對足夠復(fù)雜的場景,只要給當(dāng)前的對象穿上另一件"衣服"就能搖身一變成為你想要的結(jié)果。
感覺很神奇啊,只要是 InputStream 相關(guān)的類,都可以作為生成下一個 xxInputStream 對象輸入,只要是一個 OutputStream 相關(guān)的類都可以作為生成下一個 xxOutputStream 對象的輸入。其實啊,知道細(xì)節(jié)之后就不會覺得神奇了,只會覺得設(shè)計的很巧妙。
FilterOutputStream 和 FilterInputStream 這兩個是關(guān)鍵,他們倆分別繼承自 OutputStream 和 InputStream,同時又有非常多的子類,就是通過這些子類來實現(xiàn)各種轉(zhuǎn)換,什么線程的輸入做到輸出字符數(shù)組、被序列化的對象輸出字符串等等。
FilterOutputStream 子類:
- BufferedOutputStream
- CheckedOutputStream
- CipherOutputStream
- DataOutputStream
- DeflaterOutputStream
- DigestOutputStream
- InflaterOutputStream
- PrintStream
FilterInputStream 子類:
- BufferedInputStream
- CheckedInputStream
- CipherInputStream
- DataInputStream
- DeflaterInputStream
- DigestInputStream
- InflaterInputStream
- LineNumberInputStream
- ProgressMonitorInputStream
- PushbackInputStream
這些子類固然是很重要的,便于操作不同的數(shù)據(jù)類型,同樣重要的還有 Reader、Writer。這兩個類并不繼承 InputStream 與 OutputStream 但通過他們的子類,也同樣能實現(xiàn)更簡單的文件讀寫。比如 OutputStreamWriter、InputStreamReader。都可以直接對 String 進(jìn)行操作。再搭配上 InputStream 和 OutputStream 那一大家子,真的是可以對文件可以為所欲為了。
對了,還一個 Buffer,它直譯過來是緩沖的意思,干什么用的呢?是這樣的,磁盤的讀取或?qū)懭氲乃俣炔]有像直接操作內(nèi)存那樣快,所以需要一個緩沖的東西用來緩解雙方操作上的時間差,比如在寫的時候,可以先寫到緩沖區(qū),然后再把緩沖區(qū)的數(shù)據(jù)一點點寫到磁盤上。或者說,從磁盤讀數(shù)據(jù)的時候,先讀一些,讀到緩沖區(qū),等讀的東西,夠多了,然后再一起寫入到內(nèi)存中去。