Java I/O 好複雜,傻傻分不清楚,別擔憂,咱們有線索了。。。

IO 類圖

小帥最近在學Java的IO類庫,這麼多類看得小帥人頭昏眼花,經常是學了這個類,忘了那個類,再過一陣子就全忘了。。。編程

每次用到的時候,小帥都要從新讀文檔,看代碼,如此循環,身心疲憊。設計模式

小帥沒辦法只好向好朋友小會求助:IO類庫太複雜了,我毫無頭緒,能不能幫我梳理一下?緩存

小會想了一下,說道:整體來講IO類庫分爲兩大類:字節流字符流,字節流是按字節讀取數據,字符流是按字符讀取數據。微信

小帥不解:全部的數據在計算機中都是二進制表示的,都用字節來讀取不就好了嗎?編碼

爲何還要加個字符流,我用字節讀出來,再轉成字符不行嗎?url

小會說:Java中的字符都是用Unicode表示的,即對應一個數字,也就是碼點。spa

若是咱們用二進制的字節流讀出來是沒法看懂的,咱們須要用對應的編碼格式(好比:UTF-8,UFT-16等)轉換成咱們可讀的字符。.net

字符流就是專門用來讀寫人們可讀的字符的,這樣會方便不少,一步到位,不用再手工轉換成字符了。設計

我把IO類都放在一張圖裏,這樣看上去就清爽了:3d


小帥:仍是好多類啊。。。
小會:別急,咱們往下看。

 

 

看個例子

 

咱們看一下用字節流,把int數據1到9,寫進txt文件的例子:

FileOutputStream outputStream = new FileOutputStream("text.txt");

DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
for(int i = 0; i < 10; i++) {
    dataOutputStream.writeInt(i);
}
dataOutputStream.close();

text.txt文件內容:

裏面保存的都是二進制數據,可是咱們是用int的數據類型寫進去的(dataOutputStream.writeInt(i);),而不是以二進制的格式寫進去的。

小帥疑惑:爲何要用 DataOutputStream寫入int數據呢?

我直接用 FileOutputStream 不能寫嗎?

DataOutputStream有什麼做用呢?

小會微微一笑:若是不用DataOutputStream也能夠,不過要本身拼成int數據類型的格式,一個int類型佔四個字節。

好比1用二進制表示就是 0000 0000 0000 0000 0000 0000 0000 0001,用十六進制表示就是 00 00 00 01。

咱們試一下用 FileOutputStream 寫入int數字 0,1,2:

FileOutputStream outputStream = new FileOutputStream("text.txt");
// int 0
 outputStream.write(0);
 outputStream.write(0);
 outputStream.write(0);
 outputStream.write(0);
 // int 1
 outputStream.write(0);
 outputStream.write(0);
 outputStream.write(0);
 outputStream.write(1);
 // int 2
 outputStream.write(0);
 outputStream.write(0);
 outputStream.write(0);
 outputStream.write(2);
 
 outputStream.close();

寫入結果:

若是用DataOutputStream 寫就是:dataOutputStream.writeInt(0),dataOutputStream.writeInt(1),dataOutputStream.writeInt(2),這樣是不簡單不少呢?

小帥彷佛有點懂了:我知道了,DataOutputStream 是對 FileOutputStream 類功能的加強,讓FileOutputStream 類更增強大,起到了裝飾的做用。

小會開心道:你說到重點了,IO類看似凌亂,其實有一個精巧的設計模式,貫穿其中,把這麼多類有序的組織起來了。

這個設計模式是理解IO類的鑰匙,你知道是哪個設計模式嗎?

裝飾者模式?小帥疑惑道。

 

裝飾者模式

 

是的,就是裝飾者模式,我之前寫過一篇介紹裝飾者模式的文章,能夠點開看看: 裝飾模式--小美的生日蛋糕

裝飾者模式的類圖:

 

OutputStream家族類:

這裏的FilterOutputStream類就是裝飾模式中的抽象裝飾類。

它的子類BufferedOutputStream,DataOutputStream,PrintStream就是具體的裝飾類,起到了功能加強的做用。

它們自己並無實現寫數據的功能。

看下FilterOutputStream的代碼:

寫數據的功能是靠被修飾的類實現的,這裏的OutputStream out 是要從外面傳進來的:


DataOutputStream的writeInt方法實現了功能的加強,能夠直接寫int類型的數據:

BufferedOutputStream類實現了緩存的功能加強:

也就是說裝飾類是給主類錦上添花,主類是錦,裝飾類是花,花不能代替錦,主要的功能還得靠「錦」實現的。

 

清晰起來了

 

同理咱們來看看其餘流:

InputStream家族類:

Writer家族類:


小帥一眼看出了問題:奇怪,FilterWriter裝飾類怎麼沒有子類呢?是否是Writer家族沒有用裝飾模式呢?

小會微微一笑:不是的,其實仍是用了裝飾模式,只是實現的方式有點不同,例如OutputStreamWriter類:


實際上是對OutputStream類的裝飾,換句話說字符流的底層實際上是調用了字節流。

這也很容易理解,由於計算機只能處理二進制數據,本質上仍是經過字節流實現的。

Reader家族類:


FilterReader充當了抽象裝飾類,PushbackReader是具體的裝飾類。

一樣的InputStreamReader類其實也實現了裝飾模式:

再看個例子

public static void main(String[] args) throws IOException {
try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("把酒問月·故人賈淳令予問之.txt")))) {
writer.write("青天有月來幾時?我今停杯一問之。");
       writer.newLine();
       writer.write("人攀明月不可得,月行卻與人相隨。");
       writer.newLine();
       writer.write("皎如飛鏡臨丹闕,綠煙滅盡清輝發。");
       writer.newLine();
       writer.write("但見宵從海上來,寧知曉向雲間沒。");
       writer.newLine();
       writer.write("白兔搗藥秋復春,嫦娥孤棲與誰鄰。");
       writer.newLine();
       writer.write("今人不見古時月,今月曾經照古人。");
       writer.newLine();
       writer.write("古人今人若流水,共看明月皆如此。");
       writer.newLine();
       writer.write("惟願當歌對酒時,月光長照金樽裏。");
}

try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("把酒問月·故人賈淳令予問之.txt")))) {
   String line;
   while((line = reader.readLine()) != null) {
       System.out.println(line);
   }
}}

輸出:

青天有月來幾時?我今停杯一問之。
人攀明月不可得,月行卻與人相隨。
皎如飛鏡臨丹闕,綠煙滅盡清輝發。
但見宵從海上來,寧知曉向雲間沒。
白兔搗藥秋復春,嫦娥孤棲與誰鄰。
今人不見古時月,今月曾經照古人。
古人今人若流水,共看明月皆如此。
惟願當歌對酒時,月光長照金樽裏。

OutputStreamWriter加強了FileOutputStream,讓它擁有了直接寫字符的能力,BufferedWriter加強了OutputStreamWriter,讓它擁有了緩存的能力。

一樣的,InputStreamReader加強了FileInputStream,讓它擁有了直接讀字符的能力,BufferedReader加強InputStreamReader,讓它擁有了緩存的能力。

 

最後的話

 

Java的IO類庫之前我也看得一臉懵逼,老是以爲太繁瑣,太難記了。後來學了裝飾者模式才知道,要搞懂Java的IO類庫,其實重點是要搞懂裝飾者模式。

若是不懂裝飾者模式,看多少次也不會理解爲何要這麼設計。

當一把鎖被鎖上的時候,你一直盯着鎖看是沒有用的,由於鑰匙確定不是插在鎖上,必定要去別的地方找鑰匙啊。

 

 

 

 

 

 

 

本文分享自微信公衆號 - 編程我也會(zhanyingda)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索