Java IO(五)——字符流進階及BufferedWriter、BufferedReader

一、字符流和字節流的區別

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

複製代碼
複製代碼
1 package com.demo.io;
2
3 import java.io.File;
4 import java.io.FileReader;
5 import java.io.FileWriter;
6 import java.io.Reader;
7 import java.io.Writer;
8
9 public class FileReaderWriterTest {
10
11 public static void main(String[] args) throws Exception{
12 File file = new File("D:/Files/writer.txt");
13 Writer out = new FileWriter(file);
14 // 聲明一個String類型對象
15 String str = "Hello World!!!";
16 out.write(str);
17 out.close();
18
19 //讀文件操作
20 Reader in = new FileReader(file);
21 // 開闢一個空間用於接收文件讀進來的數據
22 char c0[] = new char[1024];
23 int i = 0;
24 // 將c0的引用傳遞到read()方法之中,同時此方法返回讀入數據的個數
25 i = in.read(c0);
26 in.close();
27 if(i==-1){
28 System.out.println("文件中無數據");
29 }else{
30 System.out.println(new String(c0,0,i));
31 }
32 }
33 }
複製代碼
複製代碼
第17行"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盤Files文件夾下沒有"buffered"這個文件,代碼示例爲:

複製代碼
複製代碼
package com.demo.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Reader;
import java.io.Writer;

public class BufferedWriterReaderTest {

public static void main(String[] args) throws Exception{
    File file = new File("D:/Files/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盤Files文件夾下多出了"buffered.txt"這個文件,文件中的內容爲:

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

1234
2345
3456

4567
沒什麼問題,輸出了文件中的內容。注意兩點:

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

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

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