Java I/O流基礎知識點詳解

目錄

一、Java-IO流的概述

二、流的概念和作用

三、流的分類

       3.1  輸入流和輸出流

       3.2 字節流和字符流

       3.3 節點流和處理流

四、字節流和字符流

       4.1 字節輸入流InputStream

       4.2 輸出流OutputStream和Writer


一、Java-IO流的概述

Java輸入/輸出(Input/Output)系統簡稱IO,又稱爲輸入/輸出流,它是程序設計語言中最基礎的部分。輸入/輸出是指應用程序與外部設備及其他計算機進行數據交流的操作,可以在程序設計中根據需要實現不同類型的輸入/輸出功能。

Java中IO是通過“流”的形式進行數據的輸入和輸出,所有數據被串行化寫入輸出流或從輸入流讀入。Java的核心庫java.io提供了全面的IO接口,包括文件的讀寫和標準設備輸出等。

二、流的概念和作用

 Java中將輸入/輸出抽象稱爲流,可以理解爲輸入/輸入的途徑。就好像水管,將兩個容器連接起來。當程序需要讀取或者寫入數據時,就會開啓一個通向數據源或者目的地的通道,而流是數據在數據源和目的地之間有序運動着的數據序列,該數據序列是有順序、有起點和終點的,整個過程就好像數據在其中“流”動一樣。

個人觀點:Java輸入/輸出流就是在數據源和目的地建立傳輸通道(作用),根據數據的類型有序的運輸到目的地中(本質)。

三、流的分類

3.1  輸入流和輸出流

       流可以分爲兩類:輸入流和輸出流。輸入流不關心數據源自何種設備,輸出流也不關心數據的目的是何種設備。

        輸入流:只能從中讀取數據,而不能向其寫入數據。(只能讀)

       輸出流:只能向其寫入數據,而不能從中讀取數據。(只能寫)

       相對於程序來說,輸出流是往存儲介質或數據通道寫入數據,而輸入流是從存儲介質或數據通道中讀取數據,一般來說關於流的特性有下面幾點:

       a. 先進先出。最先寫入輸出流的數據最先被輸入流讀取到。

       b. 順序存取。一個接一個地往流中寫入一串字節,讀出時也將按寫入順序讀取一串字節,不能隨機訪問中間的數據(RandomAccessFile可以從文件的任意位置進行存取操作

       c. 只讀或只寫。每個流只能是輸入流或輸出流的一種,不能同時具備兩個功能輸入流只能進行讀操作,對輸出流只能進行寫操作。在一個數據傳輸通道中,如果既要寫入數據,又要讀取數據,則要分別提供兩個流。 

       注意的是,Java的輸入流主要由InputStream和Reader作爲基類,而輸出流則主要由OutputStream和Writer作爲基類。他們都是一些抽象基類,無法直接創建實例,但是可以通過他們的子類來創建流。例如針對存儲在磁盤、光盤或其他存儲設備上的文件的字節流,可以通過FileInputStream和FileOutputStream的子類來創建。

3.2 字節流和字符流

       字節流和字符流的用法幾乎完全一樣,區別在於兩者所操作的數據單元不同。流序列中的數據可以是沒有進行加工的原始數據(二進制字節數據),也可以是經過編碼的符合某種編碼格式的數據。其中,字節流以字節爲基本單位來處理數據的輸入/輸出,一般用於對二進制數據的讀寫,例如聲音、圖像等數據;字符流以字符爲基本單位來處理數據的輸入和輸出,一般用於文本類型數據的讀寫,例如文本文件、網絡中發送的文本信息等。

       字節流和字符流主要由4個抽象類來表示:InputStream、OutputStream、Reader、Writer,輸入和輸出各兩種。其中InputStream和OutputStream表示字節流,Reader和Writer表示字符流,其他各種各樣的流均是繼承這4個抽象類而來的。

       個人觀點:字節流以字節爲單位,通過InputStream和OutputStream抽象類進行讀寫,而字符流是以字符爲單位,通過Reader和Writer抽象類進行讀寫。

3.3 節點流和處理流

       按照流的角色來分,可以分爲節點流和處理流。

       可以從/向一個特定的IO設備(如磁盤、網絡)讀/寫數據的流,稱爲節點流。節點流也被稱爲低級流。

       處理流則用於對一個已存在的流進行連接或封裝,通過封裝後的流來實現數據讀/寫功能。處理流也被稱爲高級流。

       從圖15.4中可以看出,當使用處理流進行輸入/輸出時,程序並不會直接連接到實際的數據源,沒有和實際的輸入/輸出節點連接。使用處理流的一個明顯好處時,只要使用相同的處理流,程序就可以採用相同的輸入/輸出代碼來訪問不同的數據源,隨着處理流所包裝節點流的變化,程序實際所訪問的數據源也相應地發生變化。

四、字節流和字符流

       InputStream和Reader是所有輸入流的抽象基類,本身並不能創建實例來執行輸入,但它們將稱爲所有輸入流的模板,所以他們的方法是所有輸入流都可以使用的方法。

4.1 字節輸入流InputStream

       1.InputStream主要包含如下四個方法

       * int read() 從輸入流讀取一個字節,並轉換爲0~255的整數最後返回這個整數。爲了提高I/O操作的效率,建議使用下面兩種形式。

       * int read(byte[] b) 從輸入流中最多讀取b.length個字節的數據,並將其存儲在數組b中,返回實際讀取的字節數。

       * int read(byte[] b,int int off,int length) 從輸入流中的off位置開始,最多讀取length個字節的數據,並將其存儲在數組b中,該方法返回實際讀取的字節數。

       * void close() 關閉輸入流。在讀操作完成後,應該關閉輸入流,系統將會釋放與這個輸入流相關的資源。注意,InputStream類本身的close()方法不執行任何操作,但是它的子類重寫了close()方法。

       InputStream類的子類

       * ByteArrayInputStream類將字節數組轉換爲字節輸入流,從中讀取字節。

       * FileInputStream類從文件中讀取數據。

       * PipedInputStream類連接到一個PipedOutputStream(管道輸出流)。

       * SequenceInputStream類將多個字節輸入流串聯成一個字節輸入流。

       * ObjectInputStream類將對象反序列化

 

       2. 在Reader裏包含如下三個方法

       * int read()  從輸入流讀取單個字符,返回所讀取的字符數據

       * int read(char[] cbuf)  從輸入流中最多讀取cbuf.length個字符的數據,並將其存儲在字符數組cbuf中,返回實際讀取的字符數

       * int read(char[] cbuf,int int off,int length)  從輸入流中的off位置開始,最多讀取length個字符的數據,並將其存儲在字符數組cbuf中,該方法返回實際讀取的字符數

       Reader類的子類

       * CharArrayReader類將字符數組轉換爲字符輸入流,從中讀取字符。

       * StringReader類字符串轉換爲字符輸入流,從中讀取字符。

       * BufferedReader類爲其他字符輸入流提供讀緩衝區。

       * PipedReader類連接到一個PipedWriter。

       * InputStreamReader類將字節輸入流轉換爲字符輸入流,可指定字符編碼。

       InputStream()的基本方法時沒有參數的read()方法。這個方法從輸入流的源中讀取1字節數據,作爲一個0~255的int返回,流的結束通過返回-1來表示。read()方法會等待並阻塞其後任何代碼的執行,直到有1字節的數據可供讀取。輸入和輸出很慢,所以如果程序在做其他重要的工作,要儘量將I/O放在單獨的線程中,下面是一個簡單的例子。

//創建文本字節輸入流
FileInputStream fis = new FileInputStream("文件路徑");
//創建一個長度爲1024的“竹筒”
byte[] bbuf = new byte[1024];
//用於保存實際讀取字節數
for(int i=0;i<bbuf.length;i++){
    int b = fis.read();
    if(b == -1){
        System.out.println("關閉");
        break;
    }else{
        bbuf[i] = (byte)b;
        System.out.println(b);
    }
}
//關閉文件輸入流,放在finally塊裏更安全
fis.close();

       read()方法都用返回-1來表示流的結束。如果流已經結束,而又沒有讀取的數據,多字節read()方法會返回這些數據,直到緩衝區清空。-1永遠不會放進數組中,因爲數組只包含實際的數據,所以如果獲取數據的字節數小於數據的存儲長度,一定要判斷流是否讀取完畢,如果讀取完畢則結束讀取並關閉輸入流。例如上面的例子那樣,存儲數組的長度大於實際的數據長度,下面我們看看存儲長度小於實數據長度的情況:
 

      從上圖可以看到,循環中沒有輸出關閉的字符串。和輸出流一樣,當結束對輸入流的操作時,通過調用close()方法將其關閉,釋放與這個流關聯的所有數據。一旦輸入流已經關閉,進一步讀取這個流會拋出IOException異常。

 

4.2 輸出流OutputStream和Writer

      1.OutputStream主要包含如下五個方法

      * void write(int b)  向輸出流寫入一個字節,其中c既可以代表字節,也可以代表字符。這裏的參數是int類型,但是它允許使用表達式,而不用強制轉換成byte類型。

      * void write(byte[] b)  將b字節數組/字符數組的所有數據寫入到輸出流中。

      * void write(byte[] b,int off,int length)  從b字節數組中的off位置開始,長度爲length的子字節寫入輸出流中。

      * void flush()  將數據緩衝區中全部數據寫入到輸出流,並清空緩衝區。(爲提高效率,在向輸出流中寫入數據時,數據一般會保存到內存緩衝區,只有當緩衝區中的數據達到一定程度時,緩衝區的數據纔會被寫入到輸出流中。而flush方法則可以強制將緩衝區的數據輸入到輸出流中)

      * void close()  關閉輸出流並釋放與流相關的系統資源。

      OutputStream類的子類

      * ByteArrayOutputStream類向內存緩衝區中的字節數組中寫入數據

      * FileOutputStream類向文件中寫入數據

      * PipedOutputStream類連接到一個PipedIntputStream

      * ObjectOutputStream類將對象序列化

  

      2. Writer主要包含如下幾個方法

      因爲字符流可以直接以字符作爲操作單位,所以Writer可以用字符串來代替字符數組,即以String對象作爲參數

      * void write(int b)  向輸出流中寫入一個字符

      * void write(char[] buf)  將buf字符數組中的所有字符寫入到輸出流中

      * void write(char[] buf,int off,int length)  從buf字符數組中的off位置開始,長度爲length的子字符寫入輸出流。

      * void write(String Str)  向輸出流中寫入一個字符串

      * void write(String str,int off,int length)  從str字符串中的off起始偏移量開始,長度爲length的子字符寫入輸出流。

      Writer類的子類

      * CharArrayWriter類向內存緩衝區中的字符數組寫數據

      * StringWriter類向內存緩衝區中的字符串寫數據

      * BufferedWriter類爲其他字符輸出流提供寫緩衝區

      * PipedWriter類連接到一個PipedReader

      * OutputStreamWriter類將字節輸出流轉換爲字符輸出流,可指定字符編碼

      下面程序使用FileInputStream來執行輸出,並使用FileOutputSteam來執行輸出,實現複製指定文件的功能。

try{
    //創建字節輸入流
    FileInputStream fis = new FileInputStream("文件路徑");
    //創建字節輸出流
    FileOutputStream fos = new FileOutputStream("newFile.java");
    //創建字節存儲數據,並定義長度
    byte[] bbuf = new byte[32];
    //創建讀取結果的存儲變量
    int hasRead = 0;
    while((hasRead = fis.read(bbuf)) > 0){
        //如果讀取返回的值不爲-1,則將每次讀取到的32個字節數據寫入到輸出流
        fos.write(bbuf,0,hasRead);
    }
}catch(IOException ex){
    System.err.println(ex.getMessage());
}

      注意:每執行一次fis.read(bbuf)函數,流中的數據都會往前移動32位字節。

       和網絡硬件中緩存一樣,流還可以在軟件中得到緩衝。一般說來,可以通過把BufferedOutputStream或BufferedWriter串聯到底層流上來實現。因此,在寫入數據之後,刷新(flush)輸出流非常重要。例如,如果輸出流有一個1024字節的緩衝區,那麼這個流在發送緩衝區中的數據之前會等待更多的數據到達。在服務器響應到達之前不會向流寫入更多的數據,但是響應也不會到來,因爲請求還沒有發送,而flush()方法可以強迫緩衝的流發送數據,即使緩衝區還沒有滿,以此來打破這種死鎖狀態。

       個人觀點:在緩衝區的數據沒有充滿之前,請求和數據是不會發送到服務器中,通過flush()方法可以釋放緩衝區的數據。

       最後,當結束一個流的操作時,通過調用close()方法將其關閉,釋放與這個流關聯的所有數據。如果流來自一個網絡,那麼關閉這個流也會終止這個連接。一旦輸出流關閉,繼續寫入時就會拋出IOException異常。不過,有些流仍允許對這個對象做一些處理,例如:ByteArrayOutputStream仍轉換爲實際的字節數組,關閉的DigestOutputStream仍然可以返回其摘要。

       爲了得到正確的變量作用域,必須在try塊之外聲明流變量,但是必須在try塊內完成初始化。另外,爲了避免NullPointerExcepiton異常,在關閉流之前需要檢查流變量是否爲null。最後,通常都希望希望忽略關閉流時出現的異常,或者最多隻是將這些異常寫入日誌。

OutputStream Out = null;
try{
    Out = new FileOutputStream(“/test/data.txt”);
    //處理輸出流...
}catch(IOException ex){
    System.err.println(ex.getMessage());
}finally{
    if(out != null){
        try{
            Out.close();
        }catch(IOException ex){
            //忽略
        }
    }
}

       這個技術有時稱爲釋放模式(dispose pattern),這對於需要在垃圾回收前先進行清理的對象很常見,這個技術不僅用於流,還可以用於Socket、通道、JDBC連接等。

       Java 7引入了“帶資源的try”構造,不需要在try塊之外聲明流變量,完全可以在try塊的參數表中聲明,所以也就不需要在Finally字句。例如:

try(OutputStream Out = new FileOutputStream(“/test/data.txt”)){
    //處理輸出流...
}catch(IOException ex){
    System.err.println(ex.getMessage());
}

       Java會對try塊參數表中聲明的所有AutoCloseable對象自動調用close();

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章