當不少人學到IO的時候都特別懵,這也難怪,畢竟關於IO有各類流,記都要記糊塗了。其實只要換一個思惟角度來看待IO流,仍是不難的,甚至是很是容易和方便的,至少日常的應用不難。更深層次、更底層或者更高級的咱暫且不談,這篇文章只介紹最基本的運用,讓新手能熟悉得將IO流用到本身的項目中(其實不講高級的緣由是我不會(●′ω`●))java
貼上代碼以前我們先捋一下IO的使用思路,爲啥新手懵,由於流對象太多,開發時不知道用哪一個對象合適,只要理清思路便可知道該使用哪些對象數組
IO,分爲Input和Output,輸入和輸出。將硬盤上的文件讀取到內存裏來,爲輸入;將內存中的數據存儲到硬盤上的文件裏去,爲輸出。(這裏可以處理的數據不光只有硬盤,還有其餘設備,好比鍵盤或者網絡流,可是我們最常處理的就是硬盤上的文件,硬盤上的文件會操做以後,其餘設備天然就會了)
不管輸入仍是輸出,流程線是徹底一致的,只是順序不一樣:
**輸入:拿到文件中的數據 >>> 開始讀取數據 >>> 將數據「轉移」到內存中去 >>> 在內存中操做數據
輸出:拿到內存中的數據 >>> 開始讀取數據 >>> 將數據「轉移」到文件中去 >>> 文件中保存了數據**緩存
因此,在使用IO流前,你先得確認第一個問題:明確數據源和目的地,即明確數據流的方向網絡
明確數據流方向以後,我們就得確認第二個問題:我要處理的是什麼數據。 我們處理的數據能夠分爲兩大類:文本和非文本。 文本須要處理的數據爲字符,非文本須要處理的數據爲字節。性能
要處理非文本數據(字節)就用:
(輸入)InputStream,(輸出)OutputStream
要處理文本數據(字符)就用:
(輸入)Reader,(輸出)Writer優化
OK,這兩個問題確認好後,基本上就知道要用哪一個對象了。以前也說了,數據不光只有硬盤上的文件,其實還有其餘設備,可是爲了方便你們理解我們就以硬盤上的文件來操做。 既然要操做文件,那就確定要用到File,流也要用處理File設備的流,即:
(輸入)FileInputStream,(輸出)FileOutputStream
(輸入)FileReader,(輸出)FileWriter編碼
不論是什麼流,其中的方法基本都是一致的,輸入數據就用 read(),輸出數據就用 write(),必定要記住這一點哦,爲啥java這麼流行,就是由於無論你操做的是啥數據、啥設備、啥流,處理方式都是同樣的:code
注意哈,爲了方便演示,代碼中就沒有寫上 異常的捕捉和聲明,正常使用的過程當中是須要進行異常處理的!對象
好了我們如今來實戰,我要讀取一個文本文件裏的文字到個人內存中,該怎麼操做?
文本文件,那就是FileReader或者FileWriter唄,讀到內存裏,那就是FileReader唄,看到沒,該使用什麼對象立馬就肯定好了。內存
/*假設如今硬盤有一個文件爲1.txt 內容爲:哈哈哈哈哈哈*/ // 首先我們開始得建立一個FileReader對象 // 流建立的同時你確定也要指定操做哪一個東西嘛,因此 // 文件流的對象參數裏天然就是放的File對象,或者文件路徑,這裏放的是文件路徑 FileReader fr = new FileReader("src/1.txt"); // 還記得以前說的嘛,輸入就用read()方法,因此這裏咱就要調用read方法來開始讀數據了 // 由於是文本文件,因此處理的是字符,read()方法則每次讀取都是讀的一個字符 // read()方法返回值是字符的字符編碼,即int類型,因此聲明一個int變量來接受 int len; // 既然要讀文本,天然就建立一個字符串來接受文件中的文本 String str = ""; // 開始循環讀取數據,若是read()的返回值是-1,就表明沒有內容可讀取了,因此循環的判斷條件即不爲-1 while((len = fr.read()) != -1) { // 每讀一次,就將讀取到的字符編碼轉換成字符存到咱們剛纔的字符串裏 str += (char)len; } // 流都操做完了,就不要留着佔資源了嘛,記得每次用完關掉流哦 fr.close(); // 循環完畢了,就表明全部文本已經讀取完畢了,打印便可 System.out.println(str);
剛纔的代碼咋一看很複雜,其實內容很是簡單,能夠回顧一下以前說的流程線:
拿到數據 >>> 讀取數據 >>> 操做數據,即
建立流對象 >>> 用read()方法讀取數據 >>> 打印字符串
輸入流過了一遍,咱再過一下輸出流。我要將一個字符串輸出到一個文本文件裏,該怎麼操做?
文本文件,那就是FileReader或者FileWriter唄,輸出到文件裏,那就是FileWriter唄:
// 老套路,建立一個流對象,流對象參數裏放上文件路徑 FileWriter fw = new FileWriter("src/1.txt"); // 記得以前說的嘛,輸出用write()方法 fw.write("嘿嘿嘿嘿"); /*爲啥這裏不用循環呢,由於直接將要輸出的全部數據都一次性給流了,read()是一個字符一個字符讀,天然要用循環*/ // 輸出完了,記得關閉流 fw.close(); /*這時候文件裏的文本內容就變成了「嘿嘿嘿嘿」,要注意哦,這裏輸出是會覆蓋原文件的文本的*/
看到沒,三句話搞定,徹底對應了流程線,是否是簡單的一批?
拿到數據 >>> 輸出數據 >>> 保存數據,即
建立流對象 >>> 用write()方法輸出數據 >>> 文件內容已被覆蓋
注意哈,上面我演示的是很是簡單的輸入和輸出方法,運行性能也並非特別好,可是先掌握這個,我們慢慢來加難度。
剛纔我們處理的是文本文件,那麼如何處理非文本文件呢? 非文本文件,我們就從文件的複製來開始入手。複製這個功能,確定要將文件A的數據,轉移到文件B(這個文件B是要本身建立),這表明既要輸入又要輸出,因此(輸入)FileInputStream,(輸出)FileOutputStream兩個對象都要建立。
// 先建立一個文件讀取流,流對象參數裏放上須要複製的文件的路徑 FileInputStream fis = new FileInputStream("src/1.gif"); // 再建立一個文件輸出流,流對象參數裏放上目標文件路徑 // 文件輸出的時候,若是沒有該文件,則會自動建立文件(注意,讀取的時候可不行,輸入的源文件必須存在,不然報錯) FileOutputStream fos = new FileOutputStream("src/2.gif"); // 以前說過,字符流處理的數據是字符,字節流是字節,以前字符流的read()方法讀取的是一個字符,那字節流的read()方法天然就是字節了 // 字節流read()方法返回的字節數據,即int類型,因此建立一個變量來接收 int len; // 開始循環讀取數據,操做方式和流程和字符類是同樣的 while((len = fis.read()) != -1) { // 每讀取到一個字節,就寫到目標文件中去 fos.write(len); } // 關閉流 fis.close(); fos.close();
就算建立了兩個流對象,可是操做流程仍是同樣地簡單:
拿到數據 >>> 輸出數據 >>> 保存數據,即
建立流對象 >>> 讀數據 >>> 寫數據
在這裏基本上就能印證以前的思路了:無論IO你要處理啥,怎樣處理,本質的操做都是同樣的!就算業務複雜的一批,無非就是 先讀數據,再寫(操做)數據
必定要記住這個基本的思路,思路解決後,我們再來進行優化和進步!
上面複製文件的代碼,雖然功能是能夠完成,可是性能太慢太慢了!它是一個字節字節讀取而後寫入的,你們都知道,內存的讀寫速度要比硬盤的速度快得多!上面代碼操做呢,徹底就是在硬盤裏進行讀寫,就算複製一個1MB的文件,只怕也要十幾秒。因此上面的方式,只是爲了讓你們瞭解基本的操做,可是在實際運用中是不會這麼用的。如今就介紹一下比較經常使用的方法來優化,下面代碼要仔細看一下注釋:
// 這個確定是不變的,建立輸入和輸出流 FileInputStream fis = new FileInputStream("src/1.gif"); FileOutputStream fos = new FileOutputStream("src/2.gif"); // read()的返回值一直是int,因此得建立一個變量來接受,這個也不會變 int len; // 這裏是重點,爲啥要建立一個字節數組呢,由於read()方法裏面其實能夠放參數,就能夠放字節數組 // read()參數裏放了字節數組後,就表明着將讀取的數據所有先存放到數組裏 // 說白了就是建立數組用來存讀取的數據,也就是常常說的緩存,數組的初始化大小有多大,就表明能存多少數據 byte[] buf = new byte[1024]; // 開始循環讀取數據到緩存數組裏(這裏就和以前有一點不一樣,多了一個參數) // 這裏返回值是仍是int類型,以前返回的是一個字節數據,加了參數後,返回的就是輸入的數據長度 while((len = fis.read(buf)) != -1) { // 這裏也是重點!write也能夠放參數,以前放的是字節數據,固然也能夠放字節數組 // 參數第一個表明要寫的數據數組,第二個和第三個參數表明要寫的長度,從0開始寫,寫到結尾 fos.write(buf,0,len); } // 關閉流 fis.close(); fos.close();
這種代碼是比較經常使用的,運行速度比以前的代碼快了不少不少,最重要的就是加入了一個緩存數組。以前代碼是一個字節一個字節往硬盤裏寫,如今代碼就是,先將內存裏的緩存存滿,而後再將緩存裏的數據一次性給存入到硬盤裏,說白了,就是讀寫硬盤的次數變少了,讀寫內存的次數變多了,天然而然速度就快了。
你們不要懵,一開始我就是在這裏挺懵的,爲啥好端端加個數組我開始徹底弄不明白,在這裏我舉個例子你們就會清楚爲何了:
就好像在超市裏購物,若是你看中同樣東西,就立馬得把那個東西拿到收銀臺先放着,而後再繼續購物,又看中一個東西,又得跑到收銀臺放着,循環往復,最後再結帳,這樣是否是慢的一批。這個收銀臺就至關於硬盤,超市裏的物品就至關於內存中的數據。而緩存是啥呢,就是購物車!有了購物車以後,你就能在超市裏購物時,看中一個東西了,先放到購物車裏而後再繼續選購,直到你選購完畢再推着購物車裏去收銀臺結帳,這樣效率就高多了!
這就是爲何要用到緩存機制了!在代碼裏,那個字節數組buf就至關因而購物車,先存夠必定的數據,再跑到「硬盤」那裏去「結帳」。
談到Java,就確定要談到面向對象,那麼問題就來了,對象這種東西,又不是文本我該怎樣去保存對象數據到文件裏呢? Java固然貼心的爲你提供瞭解決的方案:那就是對象的序列化。在這裏,我只講怎樣用IO實現序列化,至於序列化的一些細節等我之後單獨寫一篇文章再說。
首先,我們弄清楚一下序列化的定義,我看到有些同窗在網上查詢序列化相關的知識,越查越懵。其實懵是由於 在沒有掌握基本的使用方法,卻去了解使用原理,這樣是絕對會懵的。
序列化,說白了就是將對象保存到本地文件上,反序列化,說白了就是將本地文件上的對象數據,讀取出來到內存裏:
序列化: 對象數據 >>> 本地文件
反序列化:本地文件 >>> 對象數據
是否是和IO的操做沒啥區別,事實也確實如此,就是沒啥本質的區別,只是由於要處理的是對象數據,全部就要用到序列化相關的流。在介紹序列化流前呢,我們仍是按照以前的思路來走一遍:
如今我要將對象數據存到本地文件裏,對象數據是文本數據嗎? 那確定不是,因此就要用FileInputStream或者FileOutputStream唄。這裏我們要的是輸出,那就是FileOutputStream嘛
// 老套路,建立一個輸出流,設置好文件路徑名(序列化不必定要這個文件後綴,其餘的也能夠,沒有特別規定) FileOutputStream fos = new FileOutputStream("src/obj.data");
假設我們要存(序列化)的是數組,咋存呢,直接用write()嗎?那確定不行,字節流write()裏只能放字節數組或者int類型的字節數據,放不了其餘的玩意。這裏只要額外加一個東西就行了,就是對象序列化流
// 須要保存(序列化)的數據 int[] array = {1,2,3}; // 老套路,建立一個輸出流,設置好文件路徑名 FileOutputStream fos = new FileOutputStream("src/obj.data"); /*注意,這裏是重點了*/ // 建立一個對象輸出流,構造方法裏放的是一個輸出流對象 // 這就表明着,我要處理的是對象,可是呢,我本身只能處理對象還處理不了文件 // 因此就得鏈接一個能處理文件的文件輸出流 ObjectOutputStream oos = new ObjectOutputStream(fos); // 調用序列化流對象的方法,參數裏面就是你要序列化的對象數據,一句話序列化完畢了! oos.writeObject(array); // 關閉流 oos.close(); fos.close();
是否是處理對象的操做流程也是同樣的?甚至比操做普通的文件還簡單吧!
拿到數據 >>> 輸出數據 ,即
建立序列化流對象 >>> 寫數據
演示了序列化,那反序列化呢,很簡單嘛,將流程線反過來就行了:
得到文件 >>> 拿到數據 >>> 讀取數據,即
建立反序列化流對象 >>> 讀數據
// 老套路,要讀數據嘛,建立一個文件輸入流,設置好文件路徑名 FileInputStream fis = new FileInputStream("src/obj.data"); // 建立一個反序列化流,就是把Output改爲Input就能夠了,記得鏈接輸出流 ObjectInputStream oos = new ObjectInputStream(fis); // 將對象數據讀回來,注意哦,反序列化拿到的對象都是Object對象,因此要強制轉換類型 int[] arrays = (int[])oos.readObject(); // 正常使用數據 System.out.println(arrays[1]);
是否是也特別簡單?
我們再次回顧一下思路:
源:是純文本:Reader
目的:是純文本 Writer
是否是以爲很簡單了?爲啥不少人學到這就懵了呢,由於 在沒有掌握基本的使用方法,卻去了解使用原理,其實你只要先掌握基本的使用方法,而後慢慢了解就行了!