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

基本使用思路

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

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

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

所以,在使用IO流前,你先得確認第一個問題:明確數(shù)據(jù)源和目的地,即明確數(shù)據(jù)流的方向

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

要處理非文本數(shù)據(jù)(字節(jié))就用:
(輸入)InputStream,(輸出)OutputStream
要處理文本數(shù)據(jù)(字符)就用:
(輸入)Reader,(輸出)Writer

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

不管是什么流,其中的方法基本都是一致的,輸入數(shù)據(jù)就用 read(),輸出數(shù)據(jù)就用 write(),一定要記住這一點哦,為啥java這么流行,就是因為不管你操作的是啥數(shù)據(jù)、啥設備、啥流,處理方式都是一樣的:

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

代碼實戰(zhàn)

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

簡單的文本文件處理

好了咱們現(xiàn)在來實戰(zhàn),我要讀取一個文本文件里的文字到我的內存中,該怎么操作?
文本文件,那就是FileReader或者FileWriter唄,讀到內存里,那就是FileReader唄,看到沒,該使用什么對象立馬就確定好了

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

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

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

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

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

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

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

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

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

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

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

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

文件復制

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

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

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

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

上面復制文件的代碼,雖然功能是可以完成,但是性能太慢太慢了!它是一個字節(jié)字節(jié)讀取然后寫入的,大家都知道,內存的讀寫速度要比硬盤的速度快得多!上面代碼操作呢,完全就是在硬盤里進行讀寫,就算復制一個1MB的文件,只怕也要十幾秒。所以上面的方式,只是為了讓大家了解基本的操作,但是在實際運用中是不會這么用的?,F(xiàn)在就介紹一下比較常用的方法來優(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é)數(shù)組呢,因為read()方法里面其實可以放參數(shù),就可以放字節(jié)數(shù)組
// read()參數(shù)里放了字節(jié)數(shù)組后,就代表著將讀取的數(shù)據(jù)全部先存放到數(shù)組里
// 說白了就是創(chuàng)建數(shù)組用來存讀取的數(shù)據(jù),也就是經常說的緩存,數(shù)組的初始化大小有多大,就代表能存多少數(shù)據(jù)
byte[] buf = new byte[1024];
// 開始循環(huán)讀取數(shù)據(jù)到緩存數(shù)組里(這里就和之前有一點不同,多了一個參數(shù))
// 這里返回值是還是int類型,之前返回的是一個字節(jié)數(shù)據(jù),加了參數(shù)后,返回的就是輸入的數(shù)據(jù)長度
while((len = fis.read(buf)) != -1) {
    // 這里也是重點!write也可以放參數(shù),之前放的是字節(jié)數(shù)據(jù),當然也可以放字節(jié)數(shù)組
    // 參數(shù)第一個代表要寫的數(shù)據(jù)數(shù)組,第二個和第三個參數(shù)代表要寫的長度,從0開始寫,寫到結尾
    fos.write(buf,0,len);
}
// 關閉流
fis.close();
fos.close();

這種代碼是比較常用的,運行速度比之前的代碼快了很多很多,最重要的就是加入了一個緩存數(shù)組。之前代碼是一個字節(jié)一個字節(jié)往硬盤里寫,現(xiàn)在代碼就是,先將內存里的緩存存滿,然后再將緩存里的數(shù)據(jù)一次性給存入到硬盤里,說白了,就是讀寫硬盤的次數(shù)變少了,讀寫內存的次數(shù)變多了,自然而然速度就快了。
大家不要懵,一開始我就是在這里挺懵的,為啥好端端加個數(shù)組我開始完全弄不明白,在這里我舉個例子大家就會清楚為什么了:
就好像在超市里購物,如果你看中一樣東西,就立馬得把那個東西拿到收銀臺先放著,然后再繼續(xù)購物,又看中一個東西,又得跑到收銀臺放著,循環(huán)往復,最后再結賬,這樣是不是慢的一批。這個收銀臺就相當于硬盤,超市里的物品就相當于內存中的數(shù)據(jù)。而緩存是啥呢,就是購物車!有了購物車之后,你就能在超市里購物時,看中一個東西了,先放到購物車里然后再繼續(xù)選購,直到你選購完畢再推著購物車里去收銀臺結賬,這樣效率就高多了!
這就是為什么要用到緩存機制了!在代碼里,那個字節(jié)數(shù)組buf就相當于是購物車,先存夠一定的數(shù)據(jù),再跑到“硬盤”那里去“結賬”。

對象的序列化與反序列化

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

首先,咱們弄清楚一下序列化的定義,我看到有些同學在網上查詢序列化相關的知識,越查越懵。其實懵是因為 在沒有掌握基本的使用方法,卻去了解使用原理,這樣是絕對會懵的。
序列化,說白了就是將對象保存到本地文件上,反序列化,說白了就是將本地文件上的對象數(shù)據(jù),讀取出來到內存里:
序列化: 對象數(shù)據(jù) >>> 本地文件
反序列化:本地文件 >>> 對象數(shù)據(jù)
是不是和IO的操作沒啥區(qū)別,事實也確實如此,就是沒啥本質的區(qū)別,只是因為要處理的是對象數(shù)據(jù),所有就要用到序列化相關的流。在介紹序列化流前呢,咱們還是按照之前的思路來走一遍:
現(xiàn)在我要將對象數(shù)據(jù)存到本地文件里,對象數(shù)據(jù)是文本數(shù)據(jù)嗎? 那肯定不是,所以就要用FileInputStream或者FileOutputStream唄。這里咱們要的是輸出,那就是FileOutputStream嘛

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

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

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

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

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

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

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

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

是不是也特別簡單?

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

總結

咱們再次回顧一下思路:

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

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

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

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容