1. 程式人生 > >Java IO6:字元流進階及BufferedWriter、BufferedReader

Java IO6:字元流進階及BufferedWriter、BufferedReader

字元流和位元組流的區別

拿一下上一篇文章的例子:

public static void main(String[] args) throws Exception
{
    File file = new File("D:/writer.txt");
    Writer out = new FileWriter(file);
    // 宣告一個String型別物件
    String str = "Hello World!!!";
    out.write(str);
    out.close();
        
    // 讀檔案操作
    Reader in = new FileReader(file);
    // 開闢一個空間用於接收檔案讀進來的資料
    char c0[] = new char[1024];
    int i = 0;
    // 將c0的引用傳遞到read()方法之中,同時此方法返回讀入資料的個數
    i = in.read(c0);
    in.close();
        
    if (-1 == i)
    {
        System.out.println("檔案中無資料");
    }
    else
    {
        System.out.println(new String(c0, 0, i));
    }
}

第8行"out.close()"註釋掉可以看一下效果,"writer.txt"一定是空的,控制檯上輸出的是"檔案中無資料",說明一下原因。

字元流和位元組流非常相似,但也有區別,從網上找了一張圖:

       從圖上看,字元流和位元組流最大的區別在於,位元組流在操作時本身不會用到緩衝區(記憶體),是檔案本身直接操作的,而字元流操作時使用了緩衝區,通過緩衝區再操作檔案。這也解釋了上面程式的那個問題,為什麼不對資源進行close()就無法寫入檔案的原因。因為在關閉字元流時會強制性地將緩衝區中的內容進行輸出,但是如果沒有關閉,緩衝區中的內容是無法輸出的

      什麼是緩衝區?簡單理解,緩衝區就是一塊特殊的記憶體區域為什麼要使用緩衝區?因為如果一個程式頻繁操作一個資源(檔案或資料庫),則效能會很低,為了提升效能,就可以將一部分資料暫時讀入到記憶體的一塊區域之中,以後直接從此區域讀取資料即可,因為讀取記憶體的速度要快於讀取磁碟中檔案內容的速度。

      在字元流的操作中,所有的字元都是在記憶體中形成的,在輸出前會將所有的內容暫時儲存在記憶體之中,所以使用了緩衝區。

       如果不想在關閉時再輸出字元流的內容也行,使用Writer的

flush()方法就可以了。

字元流的原理

       Java支援字元流和位元組流,字元流本身就是一種特殊的位元組流,之所以要專門有字元流,是因為Java中有大量對於字元的操作,所以專門有字元流。位元組流和字元流的轉換是以InputStreamReader和OutputStreamWriter為媒介的,InputStreamReader(位元組解碼到字元)可以將一個位元組流中的位元組解碼成字元,OutputStreamWriter(字元編碼到位元組)可以將寫入的字元編碼成自節後寫入一個位元組流。

       InputStreamReader中的解碼位元組,是由StreamDecoder完成的,StreamDecoder是Reader的類,定義在InputStreamReader的開頭:

public class InputStreamReader extends Reader {

    private final StreamDecoder sd;

     同樣,OutputStreadWriter中的編碼位元組,是由StreamEncoder完成的,StreamEncoder是Writer的類,定義在OutputStreamWriter的開頭:

public class OutputStreamWriter extends Writer {

    private final StreamEncoder se;

      假如不對StreamDecoder和StreamEncoder指定Charset編碼格式,將使用本地環境中的預設字符集,例如中文環境中將使用GBK編碼。

InputStreamReader有兩個主要的建構函式:

1、InputStreamReader(InputStream in)

2、InputStreamReader(InputStream in, String charsetName)

OutputStreamWriter也有兩個主要的建構函式:

1、OutputStreamWriter(OutputStream out)

2、OutputStreamWriter(OutputStream out, String charsetName)

      從建構函式就可以看出,字元流是利用位元組流實現的InputStreamReader和OutputStreamWriter的兩個建構函式的區別在於,一個是使用的預設字符集,一個可以指定字符集名稱。其實FileReader和FileWriter可以看一下原始碼,很簡單,只有建構函式,裡面都是分別根據傳入的檔案絕對路徑或者傳入的File例項,new出FileInputStream和FileOutputStream,再呼叫InputStreamReader和OutputStreamWriter的構造方法。這麼做,幫助開發者省去了例項化FileInputStream和FileOutputStream的過程,讓開發者可以直接以fileName或file作為建構函式的引數

BufferedWriter、BufferedReader

       為了達到最高的效率,避免頻繁地進行字元與位元組之間的相互轉換,最好不要直接使用FileReader和FileWriter這兩個類進行讀寫,而使用BufferedWriter包裝OutputStreamWriter,使用BufferedReader包裝InputStreamReader。同樣,在D盤下沒有"buffered"這個檔案,程式碼示例為:

public static void main(String[] args) throws Exception
{
    File file = new File("D:/buffered.txt");
    Writer writer = new FileWriter(file);
    BufferedWriter bw = new BufferedWriter(writer);
    bw.write("1234\n");
    bw.write("2345\n");
    bw.write("3456\n");
    bw.write("\n");
    bw.write("4567\n");
    bw.close();
    writer.close();
        
    if (file.exists() && file.getName().endsWith(".txt"))
    {
        Reader reader = new FileReader(file);
        BufferedReader br = new BufferedReader(reader);
        String str = null;
        while ((str = br.readLine())!= null)
        {
            System.out.println(str);
        }
        reader.close();
        br.close();
    }    
}

執行一下,首先D盤下多出了"buffered.txt"這個檔案,檔案中的內容為:

然後看一下控制檯的輸出結果:

1234
2345
3456

4567

沒什麼問題,輸出了檔案中的內容。注意兩點:

      1、利用BufferedWriter進行寫操作,寫入的內容會放在緩衝區內,直到遇到close()、flush()的時候才會將內容一次性寫入檔案。另外注意close()的順序,一定要先關閉BufferedWriter,再關閉Writer,不可以倒過來,因為BufferedWriter的寫操作是通過Writer的write方法寫的,如果先關閉Writer的話,就無法將緩衝區內的資料寫入檔案了,會丟擲異常

      2、利用BufferedReader進行讀操作,不可以用父類Reader指向它,因為readLine()這個方法是BufferedReader獨有的,readLine()的作用是逐行讀取檔案中的內容

補充:

首先說明下位元組流和字元流:

FileReader,FileWriter 是讀字元的,1個字元是2個位元組,16位
OutputStream ,InputStream是讀位元組的,1個位元組是8位

如果要讀漢字,一個漢字是2個位元組,用InputStream讀就把漢字折成兩半,不太適合吧。

字元流主要讀文字效率比較高。
位元組流主要讀二進位制檔案,圖片 ,音訊,視訊等,就是為了防止出現讀取的單位放大,出現無法還原本程式的目的

如果把檔案按位元組讀取,然後按照UTF-8的格式編碼顯示,怎麼辦?
InputStreamReader(將位元組編碼成字元)就有這功能,對原位元組再編碼的過程。

下面切入正題:

轉換流:InputStreamReader和OutputStreamWriter

        一。InputStreamReader 是字元流Reader的子類,是位元組流通向字元流的橋樑。你可以在構造器重指定編碼的方式,如果不指定的話將採用底層作業系統的預設編碼方式,例如 GBK 等

要啟用從位元組到字元的有效轉換,可以提前從底層流讀取更多的位元組,使其超過滿足當前讀取操作所需的位元組。

為了達到最高效率,可要考慮在 BufferedReader 內包裝 InputStreamReader。例如:

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

      二。OutputStreamWriter 是字元流Writer的子類,是字元流通向位元組流的橋樑。
每次呼叫 write() 方法都會導致在給定字元(或字符集)上呼叫編碼轉換器。在寫入底層輸出流之前,得到的這些位元組將在緩衝區中累積

為了獲得最高效率,可考慮將 OutputStreamWriter 包裝到 BufferedWriter 中,以避免頻繁呼叫轉換器。例如:

BufferedWriter out  = new BufferedWriter(new OutputStreamWriter(System.out));

    (1)Reader和Writer類(文字字元流讀寫類):提供的對字元流處理的類,它們為抽象類。一般通過其子類來實現。
    (2)InputStreamReader(InputStream in) 和OutputStreamWriter(OutputStream out):它們可以使用指定的編碼規範並基於位元組流生成對應的字元流。

     (3)BufferedReader(InputStreamReader isr, int size) 和 BufferedWriter(OutputStreamWriter osr, int size):
為提高字元流的處理效率,可以採用緩衝機制的流實現對字元流作成批的處理,避免了頻繁的從物理裝置中讀取資訊 。

public static void main(String[] args) {
    try {
      //1.從檔案中獲得讀入位元組到位元組輸入流
        FileInputStream fis = new FileInputStream("D:/01.txt");

        //2. 為FileInputStream加上字元處理功能(位元組通向字元的橋樑 InputStreamReader)
        InputStreamReader isr = new InputStreamReader(fis);

        //3.為了達到最高效率,可考慮在 BufferedReader內包裝 InputStreamReader 避免頻繁轉換
        BufferedReader bufr = new BufferedReader(isr);

        //4.建立將資料寫出到文字檔案的檔案輸出流
        FileOutputStream fos = new FileOutputStream("D:/02.txt");

        //5.為FileOutputStream加上字元處理功能(字元通向位元組橋樑 OutPutStreamWriter)
        OutputStreamWriter osw  = new OutputStreamWriter(fos);

        //6.為了提高效率可考慮在BufferWriter內包裝 OutputStreamWriter 避免頻繁轉換
        BufferedWriter bufw = new BufferedWriter(osw);

        //簡易寫法
        BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream("D:/01.txt")));
        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:/02.txt")));
       // FileReader

        int ch =0;

        //以字元方式顯示檔案內容
        while ((ch = bufr.read()) !=-1 ){
            System.out.print((char)ch);
            bufw.write(ch);
        }
        if(bufr != null){
            bufr.close();
        }if(bufw != null){
            bufw.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}