輕松掌握Java中IO流的核心使用思路

基本使用思路

當很多人學到IO的時候都特別懵玉雾,這也難怪翔试,畢竟關于IO有各種流,記都要記糊涂了复旬。其實只要換一個思維角度來看待IO流垦缅,還是不難的,甚至是非常容易和方便的驹碍,至少平常的應用不難壁涎。更深層次凡恍、更底層或者更高級的咱暫且不談,這篇文章只介紹最基本的運用怔球,讓新手能熟悉得將IO流用到自己的項目中(其實不講高級的原因是我不會(●′ω`●))

貼上代碼之前咱們先捋一下IO的使用思路嚼酝,為啥新手懵,因為流對象太多竟坛,開發(fā)時不知道用哪個對象合適革半,只要理清思路即可知道該使用哪些對象

IO,分為Input和Output流码,輸入和輸出又官。將硬盤上的文件讀取到內存里來,為輸入漫试;將內存中的數據存儲到硬盤上的文件里去六敬,為輸出。(這里能夠處理的數據不光只有硬盤驾荣,還有其他設備外构,比如鍵盤或者網絡流,但是咱們最常處理的就是硬盤上的文件播掷,硬盤上的文件會操作之后审编,其他設備自然就會了)
無論輸入還是輸出,流程線是完全一致的歧匈,只是順序不同
輸入:拿到文件中的數據 >>> 開始讀取數據 >>> 將數據“轉移”到內存中去 >>> 在內存中操作數據
輸出:拿到內存中的數據 >>> 開始讀取數據 >>> 將數據“轉移”到文件中去 >>> 文件中保存了數據

所以垒酬,在使用IO流前,你先得確認第一個問題:明確數據源和目的地件炉,即明確數據流的方向

明確數據流方向之后勘究,咱們就得確認第二個問題:我要處理的是什么數據。 咱們處理的數據可以分為兩大類:文本非文本斟冕。 文本需要處理的數據為字符口糕,非文本需要處理的數據為字節(jié)

要處理非文本數據(字節(jié))就用:
(輸入)InputStream磕蛇,(輸出)OutputStream
要處理文本數據(字符)就用:
(輸入)Reader景描,(輸出)Writer

OK,這兩個問題確認好后秀撇,基本上就知道要用哪個對象了超棺。之前也說了,數據不光只有硬盤上的文件捌袜,其實還有其他設備说搅,但是為了方便大家理解咱們就以硬盤上的文件來操作。 既然要操作文件虏等,那就肯定要用到File弄唧,流也要用處理File設備的流适肠,即:
(輸入)FileInputStream,(輸出)FileOutputStream
(輸入)FileReader候引,(輸出)FileWriter

不管是什么流侯养,其中的方法基本都是一致的,輸入數據就用 read()澄干,輸出數據就用 write()逛揩,一定要記住這一點哦,為啥java這么流行麸俘,就是因為不管你操作的是啥數據辩稽、啥設備、啥流从媚,處理方式都是一樣的:

  1. 明確數據源和目的地
  2. 需要處理的數據類型
  3. 再確認要處理的設備(一般是File逞泄,這里也只用File舉例)

代碼實戰(zhàn)

注意哈,為了方便演示拜效,代碼中就沒有寫上 異常的捕捉和聲明喷众,正常使用的過程中是需要進行異常處理的!

簡單的文本文件處理

好了咱們現在來實戰(zhàn)紧憾,我要讀取一個文本文件里的文字到我的內存中到千,該怎么操作?
文本文件赴穗,那就是FileReader或者FileWriter唄憔四,讀到內存里,那就是FileReader唄望抽,看到沒加矛,該使用什么對象立馬就確定好了履婉。

/*假設現在硬盤有一個文件為1.txt
內容為:哈哈哈哈哈哈*/

// 首先咱們開始得創(chuàng)建一個FileReader對象
// 流創(chuàng)建的同時你肯定也要指定操作哪個東西嘛煤篙,所以
// 文件流的對象參數里自然就是放的File對象,或者文件路徑毁腿,這里放的是文件路徑
FileReader fr = new FileReader("src/1.txt");

// 還記得之前說的嘛辑奈,輸入就用read()方法,所以這里咱就要調用read方法來開始讀數據了
// 因為是文本文件已烤,所以處理的是字符鸠窗,read()方法則每次讀取都是讀的一個字符

// read()方法返回值是字符的字符編碼,即int類型胯究,所以聲明一個int變量來接受
int len; 
// 既然要讀文本稍计,自然就創(chuàng)建一個字符串來接受文件中的文本
String str = "";

// 開始循環(huán)讀取數據,如果read()的返回值是-1裕循,就代表沒有內容可讀取了臣嚣,所以循環(huán)的判斷條件即不為-1
while((len = fr.read()) != -1) {
    // 每讀一次净刮,就將讀取到的字符編碼轉換成字符存到我們剛才的字符串里
    str += (char)len;
}
// 流都操作完了,就不要留著占資源了嘛硅则,記得每次用完關掉流哦
fr.close();
// 循環(huán)完畢了淹父,就代表所有文本已經讀取完畢了,打印即可
System.out.println(str);

剛才的代碼咋一看很復雜怎虫,其實內容非常簡單暑认,可以回顧一下之前說的流程線:
拿到數據 >>> 讀取數據 >>> 操作數據,即
創(chuàng)建流對象 >>> 用read()方法讀取數據 >>> 打印字符串

輸入流過了一遍大审,咱再過一下輸出流蘸际。我要將一個字符串輸出到一個文本文件里,該怎么操作徒扶?
文本文件捡鱼,那就是FileReader或者FileWriter唄,輸出到文件里酷愧,那就是FileWriter唄:

// 老套路驾诈,創(chuàng)建一個流對象,流對象參數里放上文件路徑
FileWriter fw = new FileWriter("src/1.txt");
// 記得之前說的嘛溶浴,輸出用write()方法
fw.write("嘿嘿嘿嘿");
/*為啥這里不用循環(huán)呢乍迄,因為直接將要輸出的所有數據都一次性給流了,read()是一個字符一個字符讀士败,自然要用循環(huán)*/

// 輸出完了闯两,記得關閉流
fw.close();

/*這時候文件里的文本內容就變成了“嘿嘿嘿嘿”,要注意哦,這里輸出是會覆蓋原文件的文本的*/

看到沒谅将,三句話搞定漾狼,完全對應了流程線,是不是簡單的一批饥臂?
拿到數據 >>> 輸出數據 >>> 保存數據逊躁,即
創(chuàng)建流對象 >>> 用write()方法輸出數據 >>> 文件內容已被覆蓋

注意哈,上面我演示的是非常簡單的輸入和輸出方法隅熙,運行性能也并不是特別好稽煤,但是先掌握這個,咱們慢慢來加難度囚戚。

文件復制

剛才咱們處理的是文本文件酵熙,那么如何處理非文本文件呢? 非文本文件驰坊,咱們就從文件的復制來開始入手匾二。復制這個功能,肯定要將文件A的數據,轉移到文件B(這個文件B是要自己創(chuàng)建)察藐,這代表既要輸入又要輸出借嗽,所以(輸入)FileInputStream,(輸出)FileOutputStream兩個對象都要創(chuàng)建转培。

// 先創(chuàng)建一個文件讀取流,流對象參數里放上需要復制的文件的路徑
FileInputStream fis = new FileInputStream("src/1.gif");
// 再創(chuàng)建一個文件輸出流恶导,流對象參數里放上目標文件路徑
// 文件輸出的時候,如果沒有該文件浸须,則會自動創(chuàng)建文件(注意惨寿,讀取的時候可不行,輸入的源文件必須存在删窒,否則報錯)
FileOutputStream fos = new FileOutputStream("src/2.gif");
// 之前說過裂垦,字符流處理的數據是字符,字節(jié)流是字節(jié)肌索,之前字符流的read()方法讀取的是一個字符蕉拢,那字節(jié)流的read()方法自然就是字節(jié)了
// 字節(jié)流read()方法返回的字節(jié)數據,即int類型诚亚,所以創(chuàng)建一個變量來接收
int len;
// 開始循環(huán)讀取數據晕换,操作方式和流程和字符類是一樣的
while((len = fis.read()) != -1) {
    // 每讀取到一個字節(jié),就寫到目標文件中去
    fos.write(len);
}
// 關閉流
fis.close();
fos.close();

就算創(chuàng)建了兩個流對象站宗,但是操作流程還是一樣地簡單:
拿到數據 >>> 輸出數據 >>> 保存數據闸准,即
創(chuàng)建流對象 >>> 讀數據 >>> 寫數據

在這里基本上就能印證之前的思路了:不管IO你要處理啥,怎樣處理梢灭,本質的操作都是一樣的夷家!就算業(yè)務復雜的一批,無非就是 先讀數據敏释,再寫(操作)數據
一定要記住這個基本的思路库快,思路解決后,咱們再來進行優(yōu)化和進步钥顽!

上面復制文件的代碼义屏,雖然功能是可以完成,但是性能太慢太慢了耳鸯!它是一個字節(jié)字節(jié)讀取然后寫入的湿蛔,大家都知道,內存的讀寫速度要比硬盤的速度快得多县爬!上面代碼操作呢,完全就是在硬盤里進行讀寫添谊,就算復制一個1MB的文件财喳,只怕也要十幾秒。所以上面的方式,只是為了讓大家了解基本的操作耳高,但是在實際運用中是不會這么用的≡浚現在就介紹一下比較常用的方法來優(yōu)化,下面代碼要仔細看一下注釋:

// 這個肯定是不變的,創(chuàng)建輸入和輸出流
FileInputStream fis = new FileInputStream("src/1.gif");
FileOutputStream fos = new FileOutputStream("src/2.gif");
// read()的返回值一直是int泌枪,所以得創(chuàng)建一個變量來接受概荷,這個也不會變
int len;
// 這里是重點,為啥要創(chuàng)建一個字節(jié)數組呢碌燕,因為read()方法里面其實可以放參數误证,就可以放字節(jié)數組
// read()參數里放了字節(jié)數組后,就代表著將讀取的數據全部先存放到數組里
// 說白了就是創(chuàng)建數組用來存讀取的數據修壕,也就是經常說的緩存愈捅,數組的初始化大小有多大,就代表能存多少數據
byte[] buf = new byte[1024];
// 開始循環(huán)讀取數據到緩存數組里(這里就和之前有一點不同慈鸠,多了一個參數)
// 這里返回值是還是int類型蓝谨,之前返回的是一個字節(jié)數據,加了參數后青团,返回的就是輸入的數據長度
while((len = fis.read(buf)) != -1) {
    // 這里也是重點譬巫!write也可以放參數,之前放的是字節(jié)數據督笆,當然也可以放字節(jié)數組
    // 參數第一個代表要寫的數據數組缕题,第二個和第三個參數代表要寫的長度,從0開始寫胖腾,寫到結尾
    fos.write(buf,0,len);
}
// 關閉流
fis.close();
fos.close();

這種代碼是比較常用的烟零,運行速度比之前的代碼快了很多很多,最重要的就是加入了一個緩存數組咸作。之前代碼是一個字節(jié)一個字節(jié)往硬盤里寫锨阿,現在代碼就是,先將內存里的緩存存滿记罚,然后再將緩存里的數據一次性給存入到硬盤里墅诡,說白了,就是讀寫硬盤的次數變少了桐智,讀寫內存的次數變多了末早,自然而然速度就快了。
大家不要懵说庭,一開始我就是在這里挺懵的然磷,為啥好端端加個數組我開始完全弄不明白,在這里我舉個例子大家就會清楚為什么了:
就好像在超市里購物刊驴,如果你看中一樣東西姿搜,就立馬得把那個東西拿到收銀臺先放著寡润,然后再繼續(xù)購物,又看中一個東西舅柜,又得跑到收銀臺放著梭纹,循環(huán)往復,最后再結賬致份,這樣是不是慢的一批变抽。這個收銀臺就相當于硬盤,超市里的物品就相當于內存中的數據氮块。而緩存是啥呢绍载,就是購物車!有了購物車之后雇锡,你就能在超市里購物時逛钻,看中一個東西了,先放到購物車里然后再繼續(xù)選購锰提,直到你選購完畢再推著購物車里去收銀臺結賬曙痘,這樣效率就高多了!
這就是為什么要用到緩存機制了立肘!在代碼里边坤,那個字節(jié)數組buf就相當于是購物車,先存夠一定的數據谅年,再跑到“硬盤”那里去“結賬”茧痒。

對象的序列化與反序列化

談到Java,就肯定要談到面向對象融蹂,那么問題就來了旺订,對象這種東西,又不是文本我該怎樣去保存對象數據到文件里呢超燃? Java當然貼心的為你提供了解決的方案:那就是對象的序列化区拳。在這里,我只講怎樣用IO實現序列化意乓,至于序列化的一些細節(jié)等我以后單獨寫一篇文章再說樱调。

首先,咱們弄清楚一下序列化的定義届良,我看到有些同學在網上查詢序列化相關的知識笆凌,越查越懵。其實懵是因為 在沒有掌握基本的使用方法士葫,卻去了解使用原理乞而,這樣是絕對會懵的
序列化为障,說白了就是將對象保存到本地文件上晦闰,反序列化放祟,說白了就是將本地文件上的對象數據鳍怨,讀取出來到內存里:
序列化: 對象數據 >>> 本地文件
反序列化:本地文件 >>> 對象數據
是不是和IO的操作沒啥區(qū)別呻右,事實也確實如此,就是沒啥本質的區(qū)別鞋喇,只是因為要處理的是對象數據声滥,所有就要用到序列化相關的流。在介紹序列化流前呢侦香,咱們還是按照之前的思路來走一遍:
現在我要將對象數據存到本地文件里落塑,對象數據是文本數據嗎? 那肯定不是罐韩,所以就要用FileInputStream或者FileOutputStream唄憾赁。這里咱們要的是輸出,那就是FileOutputStream嘛

// 老套路散吵,創(chuàng)建一個輸出流龙考,設置好文件路徑名(序列化不一定要這個文件后綴,其他的也可以矾睦,沒有特別規(guī)定)
FileOutputStream fos = new FileOutputStream("src/obj.data");

假設咱們要存(序列化)的是數組晦款,咋存呢,直接用write()嗎枚冗?那肯定不行缓溅,字節(jié)流write()里只能放字節(jié)數組或者int類型的字節(jié)數據,放不了其他的玩意赁温。這里只要額外加一個東西就好了坛怪,就是對象序列化流

// 需要保存(序列化)的數據
int[] array = {1,2,3};
// 老套路,創(chuàng)建一個輸出流股囊,設置好文件路徑名
FileOutputStream fos = new FileOutputStream("src/obj.data");

/*注意袜匿,這里是重點了*/
// 創(chuàng)建一個對象輸出流,構造方法里放的是一個輸出流對象
// 這就代表著,我要處理的是對象毁涉,但是呢沉帮,我自己只能處理對象還處理不了文件
// 所以就得連接一個能處理文件的文件輸出流
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 調用序列化流對象的方法,參數里面就是你要序列化的對象數據贫堰,一句話序列化完畢了穆壕!
oos.writeObject(array);
// 關閉流
oos.close();
fos.close();

是不是處理對象的操作流程也是一樣的?甚至比操作普通的文件還簡單吧其屏!
拿到數據 >>> 輸出數據 喇勋,即
創(chuàng)建序列化流對象 >>> 寫數據

  1. 創(chuàng)建一個對象序列化流
  2. 連接一個文件輸出流
  3. 開始寫數據

演示了序列化,那反序列化呢偎行,很簡單嘛川背,將流程線反過來就好了:
獲得文件 >>> 拿到數據 >>> 讀取數據贰拿,即
創(chuàng)建反序列化流對象 >>> 讀數據

// 老套路,要讀數據嘛熄云,創(chuàng)建一個文件輸入流膨更,設置好文件路徑名
FileInputStream fis = new FileInputStream("src/obj.data");
// 創(chuàng)建一個反序列化流,就是把Output改成Input就可以了缴允,記得連接輸出流
ObjectInputStream oos = new ObjectInputStream(fis);
// 將對象數據讀回來荚守,注意哦,反序列化拿到的對象都是Object對象练般,所以要強制轉換類型
int[] arrays = (int[])oos.readObject();
// 正常使用數據
System.out.println(arrays[1]);

是不是也特別簡單矗漾?

  1. 創(chuàng)建一個對象反序列化流
  2. 連接一個文件輸入流
  3. 開始讀數據

總結

咱們再次回顧一下思路:

  1. 明確數據源和目的地
  • 源:InputStream 或 Reader
  • 目的地:OutputStream 或 Writer
  1. 需要處理的數據類型
  • 源:是純文本:Reader
    • 否:InputStream
  • 目的:是純文本 Writer
    • 否:OutputStream
  1. 再確認要處理的設備(一般是File,這里也只用File舉例)
  • 文件:File
  • 對象:Object
  • 鍵盤:System.in
  • 控制臺:System.out

是不是覺得很簡單了薄料?為啥很多人學到這就懵了呢敞贡,因為 在沒有掌握基本的使用方法,卻去了解使用原理摄职,其實你只要先掌握基本的使用方法誊役,然后慢慢了解就好了!

如果文章幫到了你琳钉,請評論告訴我势木,如果還有疑問,也請告訴我歌懒。當然啦桌,如果覺得文章中有哪些地方需要改進,十分歡迎留言探討及皂!

最后祝大家都稱為大神甫男,咱們一起成長一起進步!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末验烧,一起剝皮案震驚了整個濱河市板驳,隨后出現的幾起案子,更是在濱河造成了極大的恐慌碍拆,老刑警劉巖若治,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異感混,居然都是意外死亡端幼,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門弧满,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婆跑,“玉大人,你說我怎么就攤上這事庭呜』” “怎么了犀忱?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扶关。 經常有香客問我阴汇,道長,這世上最難降的妖魔是什么驮审? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任鲫寄,我火速辦了婚禮吉执,結果婚禮上疯淫,老公的妹妹穿的比我還像新娘。我一直安慰自己戳玫,他們只是感情好熙掺,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咕宿,像睡著了一般币绩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上府阀,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天缆镣,我揣著相機與錄音,去河邊找鬼试浙。 笑死董瞻,一個胖子當著我的面吹牛,可吹牛的內容都是我干的田巴。 我是一名探鬼主播钠糊,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壹哺!你這毒婦竟也來了抄伍?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤管宵,失蹤者是張志新(化名)和其女友劉穎奕删,沒想到半個月后劫拗,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年赴肚,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乖菱。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡助币,死狀恐怖,靈堂內的尸體忽然破棺而出燕雁,到底是詐尸還是另有隱情诞丽,我是刑警寧澤鲸拥,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站僧免,受9級特大地震影響刑赶,放射性物質發(fā)生泄漏。R本人自食惡果不足惜懂衩,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一撞叨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浊洞,春花似錦牵敷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至苫亦,卻和暖如春毛肋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屋剑。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工润匙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唉匾。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓孕讳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肄鸽。 傳聞我的和親對象是個殘疾皇子卫病,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內容