字元流和位元組流的區別
拿一下上一篇文章的例子:
1 public static void main(String[] args) throws Exception 2 { 3 File file = new File("D:/writer.txt"); 4 Writer out = new FileWriter(file); 5 // 宣告一個String型別物件 6 String str = "Hello World!!!"; 7 out.write(str); 8 out.close(); 9 10 // 讀檔案操作 11 Reader in = new FileReader(file); 12 // 開闢一個空間用於接收檔案讀進來的資料 13 char c0[] = new char[1024]; 14 int i = 0; 15 // 將c0的引用傳遞到read()方法之中,同時此方法返回讀入資料的個數 16 i = in.read(c0); 17 in.close(); 18 19 if (-1 == i) 20 { 21 System.out.println("檔案中無資料"); 22 } 23 else 24 { 25 System.out.println(new String(c0, 0, i)); 26 } 27 }
第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()的作用是逐行讀取檔案中的內容