IO是輸入和輸出的簡稱,在實際的使用時,輸入和輸出是有方向的。就像現實中兩個人之間借錢一樣,例如A借錢給B,相對於A來說是借出,而相對於B來說則是借入。所以在程式中提到輸入和輸出時,也需要區分清楚是相對的內容。 在 程式中,輸入和輸出都是相對於當前程式而言的,例如從硬碟上讀取一個配置文件的內容到程式
IO是輸入和輸出的簡稱,在實際的使用時,輸入和輸出是有方向的。就像現實中兩個人之間借錢一樣,例如A借錢給B,相對於A來說是借出,而相對於B來說則是借入。所以在程式中提到輸入和輸出時,也需要區分清楚是相對的內容。
在 程式中,輸入和輸出都是相對於當前程式而言的,例如從硬碟上讀取一個配置文件的內容到程式中,則相當於將文件的內容輸入到程式內部,因此輸入和“讀”對 應,而將程式中的內容保存到硬碟上,則相當於將文件的內容輸出到程式外部,因此輸出和“寫”對應。熟悉輸入和輸出的對應關係,將有助於後續內容的學習。
在Java語言中,輸入和輸出的概念要比其它語言的輸入和輸出的概念涵蓋的內容廣泛得多,不僅包含文件的讀寫,也包含網路數據的發送,甚至記憶體數據的讀寫以及控制台數據的接收等都由IO來完成。
為了使輸入和輸出的結構保持統一,從而方便程式員使用IO相關的類,在Java語言的IO類設計中引入了一個新的概念——Stream(流)。
由於在進行IO操作時,需要操作的種類很多,例如文件、記憶體和網路連接等,這些都被稱作數據源(data source),對於不同的數據源處理的方式是不一樣的,如果直接交給程式員進行處理,對於程式員來說則顯得比較複雜。所以在所有的IO類設計時,在讀數據時,JDK API將數據源的數據轉換為一種固定的數據序列,在寫數據時,將需要寫的數據以一定的格式寫入到數據序列,由JDK API完成將數據序列中的數據寫入到對應的數據源中。這樣由系統完成複雜的數據轉換以及不同數據源之間的不同的變換,從而簡化程式員的編碼。
IO的 這種設計就和城市中的供水和排水系統設計是一樣的,在供水的時候,水源有江河水、湖水和地下水等不同類型,由自來水公司完成把水源轉換為對應的水流。而在 排水系統設計時,只需要將污水排入污水管道即可,至於這些污水是怎麼被處理的,則不需要關心,這樣也簡化了家庭用水的處理。
IO設計中這種數據序列被形象的稱作流(Stream)。通過使用流的概念,使程式員面對不同的數據源時只需要建立不同的流即可,而底層流實現的複雜性則由系統完成,從而使程式員不必深入的瞭解每種數據源的讀寫方式,從而降低了IO編程的複雜度。
在整個IO處理中,讀數據的過程分為兩個步驟:
1、將數據源的內容轉換為流結構,該步驟由JDK API完成,程式員只需要選擇合適的流類型即可。
2、從流中讀取數據,該步驟由程式員完成,流中數據的順序和數據源中數據的存儲順序保持一致。
寫數據的過程也分為兩個步驟:
1、為連接指定的數據源而建立的專門的流結構,該步驟由JDK API完成,程式員只需要選擇合適的流類型即可。
2、將數據以一定的格式寫入到流中,該步驟由程式員完成,寫入流中的數據的順序就是數據在數據源中的存儲順序。
最後,當數據寫入流中以後,可以通過一定的方式把流中的數據寫入數據源,或者當流被關閉時,系統會自動將流中的數據寫入數據源中。
這樣,在整個IO類設計時,將最複雜的和數據源操作的部分由JDK API進行完成,而程式員進行編程時,只需要選擇合適的流類型,然後進行讀寫即可。和現實的結構一樣,IO中的流也是有方向的,用於讀的流被稱作輸入流(Input Stream),用於寫的流被稱作輸出流(Output Stream)。則進行讀寫的時候需要選擇合適的流對象進行操作。
由於Java語言使用面向對象技術,所以在實現時,每個流類型都使用專門的類進行代表,而把讀或寫該類型數據源的邏輯封裝在類的內部,在程式員實際使用時創建對應的對象就完成了流的構造,後續的IO操作則只需要讀或寫流對象內部的數據即可。這樣IO操作對於Java程式員來說,就顯得比較簡單,而且比較容易操作了。
I/O類體系
在JDK API中,基礎的IO類都位於java.io包,而新實現的IO類則位於一系列以java.nio開頭的包名中,這裡首先介紹java.io包中類的體繫結構。
按照前面的說明,流是有方向的,則整個流的結構按照流的方向可以劃分為兩類:
1、輸入流:
該類流將外部數據源的數據轉換為流,程式通過讀取該類流中的數據,完成對於外部數據源中數據的讀入。
2、輸出流:
該類流完成將流中的數據轉換到對應的數據源中,程式通過向該類流中寫入數據,完成將數據寫入到對應的外部數據源中。
而在實際實現時,由於JDK API歷史的原因,在java.io包中又實現了兩類流:位元組流(byte stream)和字元流(char stream)。這兩種流實現的是流中數據序列的單位,在位元組流中,數據序列以byte為單位,也就是流中的數據按照一個byte一個byte的順序實現成流,對於該類流操作的基本單位是一個byte,而對於位元組流,數據序列以char為單位,也就是流中的數據按照一個char一個插入的順序實現成流,對於該類流操作的基本單位是一個char。
另外位元組流是從JDK1.0開始加入到API中的,而字元流則是從JDK1.1開始才加入到API中的,對於現在使用的JDK版本來說,這兩類流都包含在API的內部。在實際使用時,字元流的效率要比位元組流高一些。
在實際使用時,字元流中的類基本上和位元組流中的類對應,所以在開始學習IO類時,可以從最基礎的位元組流開始學習。
在SUN設計JDK的IO類時,按照以上的分類,為每個系列的類設計了一個父類,而實現具體操作的類都作為該系列類的子類,則IO類設計時的四個體系中每個體系中對應的父類分別是:
位元組輸入流InputStream
該類是IO編程中所有位元組輸入流的父類,熟悉該類的使用將對使用位元組輸入流產生很大的幫助,下麵做一下詳細的介紹。
按照前面介紹的流的概念,位元組輸入流完成的是按照位元組形式構造讀取數據的輸入流的結構,每個該類的對象就是一個實際的輸入流,在構造時由API完成將外部數據源轉換為流對象的操作,這種轉換對程式員來說是透明的。在程式使用時,程式員只需要讀取該流對象,就可以完成對於外部數據的讀取了。
InputStream是所有位元組輸入流的父類,所以在InputStream類中包含的每個方法都會被所有位元組輸入流類繼承,通過將讀取以及操作數據的基本方法都聲明在InputStream類內部,使每個子類根據需要覆蓋對應的方法,這樣的設計可以保證每個位元組輸入流子類在進行實際使用時,開放給程式員使用的功能方法是一致的。這樣將簡化IO類學習的難度,方便程式員進行實際的編程。
預設情況下,對於輸入流內部數據的讀取都是單向的,也就是只能從輸入流從前向後讀,已經讀取的數據將從輸入流內部刪除掉。如果需要重覆讀取流中同一段內容,則需要使用流類中的mark方法進行標記,然後才能重覆讀取。這種設計在使用流類時,需要深刻進行體會。
在InputStream類中,常見的方法有:
a、available方法
public int available() throws IOException
該方法的作用是返回當前流對象中還沒有被讀取的位元組數量。也就是獲得流中數據的長度。
假設初始情況下流內部包含100個位元組的數據,程式調用對應的方法讀取了一個位元組,則當前流中剩餘的位元組數量將變成99個。
另外,該方法不是在所有位元組輸入流內部都得到正確的實現,所以使用該方法獲得流中數據的個數是不可靠的。
b、close方法
public void close() throws IOException
該方法的作用是關閉當前流對象,並釋放該流對象占用的資源。
在IO操作結束以後,關閉流是進行IO操作時都需要實現的功能,這樣既可以保證數據源的安全,也可以減少記憶體的占用。
c、markSupported方法
public boolean markSupported()
該方法的作用是判斷流是否支持標記(mark)。標記類似於讀書時的書簽,可以很方便的回到原來讀過的位置繼續向下讀取。
d、reset方法
public void reset() throws IOException
該方法的作用是使流讀取的位置回到設定標記的位置。可以從該位置開始繼續向後讀取。
e、mark方法
public void mark(int readlimit)
為流中當前的位置設置標誌,使得以後可以從該位置繼續讀取。變數readlimit指設置該標誌以後可以讀取的流中最大數據的個數。當設置標誌以後,讀取的位元組數量超過該限制,則標誌會失效。
f、read方法
read方法是輸入流類使用時最核心的方法,能夠熟練使用該方法就代表IO基本使用已經入門。所以在學習以及後期的使用中都需要深刻理解該方法的使用。
在實際讀取流中的數據時,只能按照流中的數據存儲順序依次進行讀取,在使用位元組輸入流時,讀取數據的最小單位是位元組(byte)。
另外,需要註意的是,read方法是阻塞方法,也就是如果流對象中無數據可以讀取時,則read方法會阻止程式繼續向下運行,一直到有數據可以讀取為止。
read方法總計有三個,依次是:
public abstract int read() throws IOException
該方法的作用是讀取當前流對象中的第一個位元組。當該位元組被讀取出來以後,則該位元組將被從流對象中刪除,原來流對象中的第二個位元組將變成流中的第一個位元組,而使用流對象的available方法獲得的數值也將減少1。如果需要讀取流中的所以數據,只要使用一個迴圈依次讀取每個數據即可。當讀取到流的末尾時,該方法返回-1。該返回值的int中只有最後一個位元組是流中的有效數據,所以在獲得流中的數值時需要進行強制轉換。返回值作成int的目的主要是處理好-1的問題。
由於該方法是抽象的,所以會在子類中被覆蓋,從而實現最基礎的讀數據的功能。
public int read(byte[] b) throws IOException
該方法的作用是讀取當前流對象中的數據,並將讀取到的數據依次存儲到數組b(b需要提前初始化完成)中,也就是把當前流中的第一個位元組的數據存儲到b[0],第二個位元組的數據存儲到b[1],依次類推。流中已經讀取過的數據也會被刪除,後續的數據會變成流中的第一個位元組。而實際讀取的位元組數量則作為方法的返回值返回。
public int read(byte[] b, int off, int len) throws IOException
該方法的作用和上面的方法類似,也是將讀取的數據存儲到b中,只是將流中的第一個數據存儲到b中下標為off的位置,最多讀取len個數據,而實際讀取的位元組數量則作為方法的返回值返回。
g、skip方法
public long skip(long n) throws IOException
該方法的作用是跳過當前流對象中的n個位元組,而實際跳過的位元組數量則以返回值的方式返回。
跳過n個位元組以後,如果需要讀取則是從新的位置開始讀取了。使用該方法可以跳過流中指定的位元組數,而不用依次進行讀取了。
從流中讀取出數據以後,獲得的是一個byte數組,還需要根據以前的數據格式,實現對於該byte數組的解析。
由於InputStream類是位元組輸入流的父類,所以該體系中的每個子類都包含以上的方法,這些方法是實現IO流數據讀取的基礎。
位元組輸出流OutputStream
該類是所有的位元組輸出流的父類,在實際使用時,一般使用該類的子類進行編程,但是該類內部的方法是實現位元組輸出流的基礎。
該體系中的類完成把對應的數據寫入到數據源中,在寫數據時,進行的操作分兩步實現:第一步,將需要輸出的數據寫入流對象中,數據的格式由程式員進行設定,該步驟需要編寫代碼實現;第二步,將流中的數據輸出到數據源中,該步驟由API實現,程式員不需要瞭解內部實現的細節,只需要構造對應的流對象即可。
在實際寫入流時,流內部會保留一個緩衝區,會將程式員寫入流對象的數據首先暫存起來,然後在緩衝區滿時將數據輸出到數據源。當然,當流關閉時,輸出流內部的數據會被強制輸出。
位元組輸出流中數據的單位是位元組,在將數據寫入流時,一般情況下需要將數據轉換為位元組數組進行寫入。
在OutputStream中,常見的方法有:
a、close方法
public void close() throws IOException
該方法的作用是關閉流,釋放流占用的資源。
b、flush方法
public void flush() throws IOException
該方法的作用是將當前流對象中的緩衝數據強制輸出出去。使用該方法可以實現立即輸出。
c、write方法
write方法是輸出流中的核心方法,該方法實現將數據寫入流中。在實際寫入前,需要實現對應的格式,然後依次寫入到流中。寫入流的順序就是實際數據輸出的順序。
write方法總計有3個,依次是:
public abstract void write(int b) throws IOException
該方法的作用是向流的末尾寫入一個位元組的數據。寫入的數據為參數b的最後一個位元組。在實際向流中寫數據時需要按照邏輯的順序進行寫入。該方法在OutputStream的子類內部進行實現。
public void write(byte[] b) throws IOException
該方法的作用是將數組b中的數據依次寫入當前的流對象中。
public void write(byte[] b, int off, int len) throws IOException
該方法的作用是將數組b中從下標為off(包含)開始,後續長度為len個的數據依次寫入到流對象中。
在實際寫入時,還需要根據邏輯的需要設定byte數值的格式,這個根據不同的需要實現不同的格式。
字元輸入流Reader
字 符輸入流體系是對位元組輸入流體系的升級,在子類的功能上基本和位元組輸入流體系中的子類一一對應,但是由於字元輸入流內部設計方式的不同,使得字元輸入流的 執行效率要比位元組輸入流體系高一些,在遇到類似功能的類時,可以優先選擇使用字元輸入流體系中的類,從而提高程式的執行效率。
Reader體系中的類和InputStream體系中的類,在功能上是一致的,最大的區別就是Reader體系中的類讀取數據的單位是字元(char),也就是每次最少讀入一個字元(兩個位元組)的數據,在Reader體系中的讀數據的方法都以字元作為最基本的單位。
Reader類和InputStream類中的很多方法,無論聲明還是功能都是一樣的,但是也增加了兩個方法,依次介紹如下:
a、read方法
public int read(CharBuffer target) throws IOException
該方法的作用是將流內部的數據依次讀入CharBuffer對象中,實際讀入的char個數作為返回值返回。
b、ready方法
public boolean ready() throws IOException
該方法的作用是返回當前流對象是否準備完成,也就是流內部是否包含可以被讀取的數據。
其它和InputStream類一樣的方法可以參看上面的介紹。
字元輸出流Writer
字 符輸出流體系是對位元組輸出流體系的升級,在子類的功能實現上基本上和位元組輸出流保持一一對應。但由於該體系中的類設計的比較晚,所以該體系中的類執行的效 率要比位元組輸出流中對應的類效率高一些。在遇到類似功能的類時,可以優先選擇使用該體系中的類進行使用,從而提高程式的執行效率。
Writer體系中的類和OutputStream體系中的類,在功能上是一致的,最大的區別就是Writer體系中的類寫入數據的單位是字元(char),也就是每次最少寫入一個字元(兩個位元組)的數據,在Writer體系中的寫數據的方法都以字元作為最基本的操作單位。
Writer類和OutputStream類中的很多方法,無論聲明還是功能都是一樣的,但是還是增加了一些方法,依次介紹如下:
a、append方法
將數據寫入流的末尾。總計有3個方法,依次是:
public Writer append(char c) throws IOException
該方法的作用和write(int c)的作用完全一樣,既將字元c寫入流的末尾。
public Writer append(CharSequence csq) throws IOException
該方法的作用是將CharSequence對象csq寫入流的末尾,在寫入時會調用csq的toString方法將該對象轉換為字元串,然後再將該字元串寫入流的末尾。
public Writer append(CharSequence csq, int start, int end)throws IOException
該方法的作用和上面的方法類似,只是將轉換後字元串從索引值為start(包含)到索引值為end(不包含)的部分寫入流中。
b、write方法
除了基本的write方法以外,在Writer類中又新增了兩個,依次是:
public void write(String str) throws IOException
該方法的作用是將字元串str寫入流中。寫入時首先將str使用getChars方法轉換成對應的char數組,然後實現依次寫入流的末尾。
public void write(String str, int off, int len)throws IOException
該方法的作用是將字元串str中索引值為off(包含)開始,後續長度為len個字元寫入到流的末尾。
使用這兩個方法將更方便將字元串寫入流的末尾。
其它和OutputStream類一樣的方法可以參看上面的介紹。
小結
在實際使用IO類時,根據邏輯上的需要,挑選對應體系中的類進行實際的使用,從而實現程式中IO的相關功能。
熟悉了IO類的體系以後,就可以首先熟悉基本的IO類的使用,然後再按照IO類體系中相關類的使用方式逐步去瞭解相關的IO類的使用,從而逐步熟悉java.io包中類的使用,然後再掌握IO編程。
在實際使用時,一般都使用這4個類中對應的子類,每個子類完成相關的功能。對於這些子類,也可以根據這些類是否直接連接數據源,將這些IO類分類為:
1、實體流
指直接連接數據源的IO流類
2、裝飾流
指不直接連接數據源,而是建立在其它實體流對象的基礎之上。