基本使用思路
當很多人學到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這么流行麸俘,就是因為不管你操作的是啥數據辩稽、啥設備、啥流从媚,處理方式都是一樣的:
- 明確數據源和目的地
- 需要處理的數據類型
- 再確認要處理的設備(一般是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)建序列化流對象 >>> 寫數據
- 創(chuàng)建一個對象序列化流
- 連接一個文件輸出流
- 開始寫數據
演示了序列化,那反序列化呢偎行,很簡單嘛川背,將流程線反過來就好了:
獲得文件 >>> 拿到數據 >>> 讀取數據贰拿,即
創(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]);
是不是也特別簡單矗漾?
- 創(chuàng)建一個對象反序列化流
- 連接一個文件輸入流
- 開始讀數據
總結
咱們再次回顧一下思路:
- 明確數據源和目的地
- 源:InputStream 或 Reader
- 目的地:OutputStream 或 Writer
- 需要處理的數據類型
- 源:是純文本:Reader
- 否:InputStream
- 目的:是純文本 Writer
- 否:OutputStream
- 再確認要處理的設備(一般是File,這里也只用File舉例)
- 文件:File
- 對象:Object
- 鍵盤:System.in
- 控制臺:System.out
是不是覺得很簡單了薄料?為啥很多人學到這就懵了呢敞贡,因為 在沒有掌握基本的使用方法,卻去了解使用原理摄职,其實你只要先掌握基本的使用方法誊役,然后慢慢了解就好了!
如果文章幫到了你琳钉,請評論告訴我势木,如果還有疑問,也請告訴我歌懒。當然啦桌,如果覺得文章中有哪些地方需要改進,十分歡迎留言探討及皂!
最后祝大家都稱為大神甫男,咱們一起成長一起進步!