Java IO基礎從這裡開始(上)
我計劃寫三篇比較長的部落格,來詳細梳理一下java中的IO操作,大致內容是:第一篇介紹相關的類,第二篇整理相關的面試題,第三篇計劃使用這些類和方法完成一個小專案。
每篇文章都不是以往的風格,不在單純的將其作為自己的筆記瞎寫,而是認真的對自己的學習情況整理一下,達到複習的目的,同時如果這些東西可以幫助的一些初學者(好吧,其實我也是),那我將十分的開心。
寫作目的
基本掌握Java IO部分的類,瞭解IO操作的大致面貌,梳理和整理各種類的關係。
寫在前面
java的IO操作給我的第一感覺就是涉及的類太多了,自己看書的時候感覺根本無從下手,但是隻要弄清楚了每個類的關係,其實學起來並不是很難。
我們首先得建立起一個概念,流。什麼是流呢,我們可以舉一些生活中的例子,水流,電流等等。水流是從一個地方流向另一個地方的水,而電流則是從正極流向負極的電子。凡是涉及流的東西,都有一個共同的特點,那就是一定是什麼東西從一個地方到另一個地方。
那麼把這個概念放在計算機中理解一下,首先,流的物件是什麼,水流的物件是水,電流的物件是電子,而在我們檔案操作中,流的物件自然就是檔案的內容,這些內容就是一個一個字元,但是站在計算機的角度來看,這些字元在計算機中其實就是一個一個位元組儲存的物件。所以我們可以按照物件來講流分成兩種,位元組流和字元流。前面說到了流的特徵是從一個地方流向另一個地方,那麼對於檔案流來說,就是從一個檔案向另一個檔案。
其實從上面我們可以得到兩概念:流出和流入,這兩個詞對應兩個概念(output,input),後面我們會經常看到這兩個詞。
一.java中的檔案物件
java有一句話叫做“萬物皆物件”,這句話java的確體現的淋漓盡致,對於磁碟中的檔案來說,java中也有相應的類與之物件,這個類就是下面要講的File類。通過File類例項化出來的物件實際上就對應著磁碟中的某個檔案,這常常就是我們希望操作的檔案物件(ps,這裡不區分目錄與普通檔案的概念,因為目錄也是一種檔案,所以本文所說的檔案,指的就是普通檔案和目錄)。通過File創建出來的物件,就與某個磁碟檔案相對應,我們可以簡單的認為操作這個File物件就是操作相關的檔案。
File類的簡介:
1. File是一個類。可以建立其物件,該物件對應著一個檔案或者目錄
2. File中的方法僅涉及到如何建立,刪除,重新命名等。
3. File類物件與平臺無關
4. File物件經常作為io流的構造器形參
下面是File類例項化的一種方式
File file1=new File("test.txt");
File類的建構函式擁有一個引數,這個引數就是檔案的路徑,有可能是相對路徑,也有可能是絕對路徑。但這裡要特別注意一下對於windows系統,由於路徑的分割符是’\’,二這個符號恰好是轉義字元,所以應該替換成‘\’,比如’C:\Users\zeng\Desktop’應該寫成’C:\\Users\\zeng\\Desktop’,或者使用’/’代替’\’.
還有其他構造方式,這裡不做介紹。
到這裡為止我們可以獲取到一個需要操作的磁碟檔案了,下面將說明File類的一些常用方法。在此之前,我先將這些方法進行分類。
- get相關
get開頭的方法的特點就是獲取檔案的一些相關資訊,如檔名,檔案路徑,檔案的上一級目錄是什麼等等
- set相關
set開頭的方法涉及給檔案設定某種屬性
- is相關
以is開頭的方法的返回值一般是boolean型,這些方法通常用來判斷物件的狀態。
can相關
以can開頭的結尾通常用來判斷檔案是否具有某種許可權(讀,寫,執行等)
建立與修改
很多時候我們所建立的File類物件在磁碟中並沒有這樣一個檔案存在,我們可以選擇建立。同時如果存在我們也可以進行刪除操作。
其他方法
這些方法同樣十分重要,之所以歸為其他方法只是筆者實在無法將他們歸類
最後,特別要注意的是,凡是與檔案本身有關的東西,如名字,狀態,屬性等等,都需要用到File類,File類無法對檔案的內容作出任何動作,可以理解為在你的windows桌面裡有一個檔案,你不可以去開啟它,但是你可以刪除他,檢視他的屬性,設定他的屬性等等。File類也是其他IO類的基礎。這裡我並沒有去詳細介紹每個方法,而是將其分類簡單的描述了一下他們的功能,因為日後用的時候總能體會到他們的用處,這裡只簡單的告訴你有什麼即可。
二.FileInputStream和FileOutputStream
下面介紹兩個類,這兩個類的中文名稱分別是檔案輸入流和檔案輸出流。注意這裡的輸入與輸出是對於程式來說的,輸入就是將檔案的內容輸入到程式(也就是讀),輸出就是將程式中的內容輸出到檔案(也就是寫),我們站在上面角度上去理解十分重要,切記,輸入輸出都是站在程式的角度。
我們的FileInputStream與FileOutputStream類就是建立在檔案與程式之間的一座橋樑。有了這些,我們可以先來看看這兩個類的構造方法。
FileInputStream f1=new FileInputStream(new File(filePath));
FileOutputStream f2=new FileOutputStream(new File(filePath));
通過這兩個類各自都構造方法就分別將一個檔案與一個流對應起來了,以後如果要從檔案中讀內容,只需要從FileInputStream物件中讀取,將內容寫入檔案,只需將內容寫入FileOutputStream中就可以了。
他們各自的方法很簡單
1.FileInpustream對應著讀操作
2.FileOutputStream對應著寫操作
為了安全起見,檔案流在使用完成後是需要進行關閉的,雖然java的垃圾回收機制可以回收程式所不使用的資源,但是注意垃圾回收機制覺得不會回收一個不再使用的流,因此需要我們手動關閉,他的方法很簡單
下面使用這兩個類的方法寫一個複製的小程式。
本小節最後的一個小問題:上面的byte陣列 buf在程式中起的是什麼作用?
三.BufferedInputStream類和BufferedOutputStream類
首先來回答上面一小節中的問題,buf的作用是什麼。在上面的那個程式中程式將讀取的東西放在buf陣列中,接著又從buf’陣列中取出內容,放入另一個檔案對應的流。這裡的buf起的就是緩衝作用,仔細分析一下緩衝區的作用,如果緩衝區過小,那麼不斷讀取的次數將會變多,相應的效率就有可能降低,而如果緩衝區過大,則會浪費記憶體空間,所以合適的緩衝區將會影響程式執行的效率。之前提到過,java中萬物皆物件,那麼緩衝區是否對應有物件呢,還真有,這就是下面要講的BufferedInputStream類和BufferedOutputStream類;
上面這幅圖描繪了這兩個類的作用,他們只是在程式與輸出輸入流中建立相應的緩衝區,此後程式讀寫檔案都將與快取區打交道,有了快取區的加入,程式的相應效率就會變高。
下面是這兩個類的用法,將相應的檔案輸入輸出流作為建構函式的引數即可建立緩衝流物件
緩衝流的關閉
一旦相應的緩衝流被關閉,其對於的輸入輸出流也會被關閉,則不需要再去關閉輸入輸出流。
四.FileReader類與FileWriter類
最開始我們說過,水流的物件是水,電流的物件是電子。對於IO流來說,資料可以分為位元組和字元。前面所涉及到的類,無論讀取還是寫入的都是位元組。但很多我們的工作時與文字檔案有關的,使用位元組的形式的效率並不高,這是我們希望IO流中流動的是字元,怎麼辦?
本小節索要講的就是與字元流有關的類,這兩個類分別從InputStream和OutputStream類繼承而來的。
下面是具體用法
這兩個類也分別用於read和write方法,但是注意此時讀取寫入的就已經不是位元組了(不需要byte陣列),而是字串形式(String)。
基於之前的講解這裡不進行單個方法的介紹。
注意:字元流與位元組流的方法其實基本差不多,不同的那個方法readline指的是讀取一行字元。特別特別要注意的是,這兩個類只能處理純文字內容,視訊音訊二進位制檔案是不能他們處理的。之所以存在這麼兩個類是因為,對於純文字檔案的操作來說字元流的確比位元組流要高效。
五.InputStreamReader類和OutputStreamWriter類
這兩個類用來將位元組流轉化為字元流。
這一節的內容比較簡單,當我們需要將位元組流轉換成字元流的時候可以使用這兩個類的方法。在此之前我們需要了解一些小知識。
計算機儲存的最小單位是位元組,計算機內部是不存在字元這種東西的,之所以我們可以看到螢幕上顯示的數字字母漢字都是因為提前將相應的位元組進行了編碼,當需要顯示的時候之需將其解碼。那麼這就一定涉及到某種編碼方式了,學過C語言一定知道一個將ascii碼的東西,其實編碼不只這麼一種,還有ASCC,ISO8859-1,GB2312,GBK,Unicode,UTF-8等,這裡不討論各種編碼的不同之處,但是要記住的是以什麼方式編碼就應該以什麼方式解碼。
字元流轉換成位元組流的過程就是編碼,位元組流轉化為字元流的過程就是解碼。
具體用法,這裡以GBK編碼為準,實際程式設計應以實際情況為準。
這時候的到InputStreamReader類物件inputStreamReader和OutputStreamWriter類的物件outputStreamWriter與FileReader與FileWriter類的物件的用法是一樣的,這裡不做詳細介紹。
物件流
通過前面的學習,各位對Java的IO流有了一定的瞭解,下面要介紹的內容是我覺得Java IO部分最炫酷的東西,那就是物件流,那麼什麼是物件流呢。
我們都知道java例項出來的物件都存在於堆記憶體中,如果程式結束,物件就會消失。對於普通的資料來說,比如說字串數字,我們可以把它寫入檔案中,下次要使用時只需重新讀取檔案即可,這樣這些資料就得到了永久儲存,那麼物件是否也可以進行這樣的操作呢,將物件寫入檔案中,下一次從檔案中讀取出這個物件繼續使用它。答案是肯定的,java中存在一種序列化和反序列化的機制。
什麼是序列化?
序列化是將記憶體中的物件轉化為二進位制流,儲存在硬碟檔案中
什麼是反序列化?
序列化是將檔案中的資料轉化為物件,載入進記憶體。
我們先來看序列化的程式碼
再來看看反序列化的程式碼
這兩個類的使用對於看到現在的讀者來說應該是非常熟悉了,他們與普通的檔案流的區別在於,構建物件流的時候是以普通檔案流作為引數的。而寫入的操作,其引數是一個例項化了的物件。而讀取的也是一個物件,野人需要一個物件引用來接受。
注意事項:
並不是每個類都可以被序列化,可以被序列化的類應該滿足一下條件:
- 該類實現了Serializable介面
- 該類是所有成員變數都需要實現了Serializable介面
以上兩個條件缺一個都將導致序列化失敗,同時不要被這個Serializable介面嚇跑,我們來看看這個介面要我們實現什麼吧。
什麼都沒有,開不開心,這個介面的實現更像是一種宣告。
RandomAccessFile類
前面所提到的類,都與在不同的檔案中讀寫有關,下面要介紹的這個類則是專注於檔案的內容,這個類就是RandomAccessFile類。首先從名字出發,這個類大概可以翻譯為“隨機訪問檔案”。隨機的意思就是指可以訪問檔案的不同的位置,就如同我們修改word一樣,我們的游標是可以隨機移動的,而訪問則代表著讀寫操作,檔案指的是檔案內容。一句話簡述這個類的作用就是:可以在任何位置讀寫檔案內容的類。有了以上的感性的認識將會有助於我們對該類的理解。
1.獲取一個檔案
這個類的建構函式由兩個引數,第一個是需要獲取的檔案路徑,可以以字串的形式傳入,也可以使用前面講到的File物件。第二個引數是開啟方式,r是read的首字母,w是write的首字母。r代表以可讀的方式開啟,w以可寫的方式開啟,可以使用的開啟模式有:
- rw
可讀可寫這樣開啟的檔案,我們可以做寫操作也可以做讀操作。 - r
以只讀的方式開啟,這樣開啟問檔案只能讀而不能寫
特別要注意w不能單獨作為開啟模式,並且指定的模式不能超過檔案所擁有的許可權,也就是還說如果一個檔案只可以讀,那麼就不能以rw的形式開啟。
2.寫方法
3.讀方法
其實讀與寫的方法遠遠不止這麼一點,這裡只是列舉出來一些常用的方法,其他的方法可以在程式設計中閱讀jdk原始碼得到。
下面講的一些方法和讀寫沒有關係,但是確實非常重要的。
首先我想在這裡問一個問題,在前面的讀寫方法中,程式是如何直到每次是從哪裡開始讀寫,讀完以後,下次讀寫時又怎麼知道上次讀寫的位置呢?這就涉及到檔案指標的東西了。
檔案開啟後,指標預設指向開頭的位置,讀和寫的過程將會是檔案指標向後移動一個單位,如圖,如果讀取一個字元,則最先讀出的是a,然後檔案指標向後移動一個單位,下次讀的就是b。假如是寫的話,則會將該位置原有的內容覆蓋掉,然後向後移動一個位置,比如說我想寫入1,則a就變成了1,而檔案指標將會移動到b。
如何判斷檔案結尾呢。
檔案的結尾有一個特殊的標誌,這個特殊的標誌就是EOF,也就是end of file的縮寫,意思就是檔案結尾。當出現讀取到了這個字元就會返回EOF或者-1,只要我們通過返回值就可以判斷是否讀取完畢。
以上介紹的內容與下面的知識有關,既然我們開頭說過了,該類可以隨機的訪問檔案,可是通過現在的介紹,即使有了指標,指標也不過能向後移動而已,而且還是程式自己的行為,現在要說的是檔案指標可以隨便控制,達到隨機讀寫的目的。
通過這是三個方法就可以隨意控制檔案指標的位置了,同時這也是對檔案內容操作的關鍵,並且切記不要超過檔案的範圍。
其他方法:
這一部分是後來補充的,之前竟然忘記了,現在終於完整了,圖書館外面下著雨,我還能再戰一會
寫在後面
這篇部落格自認為是自己所有部落格中質量最高的了,因為恰好大學星期五的課不上很多,所以我有充足的時間來撰寫著篇部落格,我希望這篇部落格可以高質量的記錄我的學習過程,同時如果可以順便幫助的有需要的同學,那我就更加開心了。
下一篇文章蒐集了博主做過的面試題(不定期更新)