Java IO之Reader與Writer對象常用操作(包含了編碼問題的處理)
涉及到文件(非文件夾)內容的操作,如果是純文本的情況下,除了要用到File(見之前文章),另外就必須用到字符輸入流或字符輸出流。
字符輸入流:該流處理時,數據由外部流向程序(內存),一般指代“讀取字符”,更清晰點地說:從外部讀取字符數據到內存中。
字符輸出流:該流處理時,數據由程序(內存)流向外部,一般指代“寫入字符”,更清晰點地說:將字符數據從內存寫入到外部。
在Java中,可使用:Reader 與 Writer 及其子類。
對字符的操作,采用 Reader與Writer。它們的為聲明分別為:
public class FileReader extends InputStreamReader
public abstract class Writer implements Appendable, Closeable, Flushable
它們都是抽象類,需要由具體子類進行實例化。
Reader主要子類有:
- BufferedReader:緩存區字符輸入流。從緩存區中讀取字符數據。類似於BufferedInputStream。
- CharArrayReader:從字符數組中讀取字符數據。類似於ByteArrayInputStream。
- FileReader:文件字符輸入流。從文件中讀取字符數據。類似於FileInputStream。
- FilterReader:過濾器字符輸入流。用於裝飾。其子類有:PushbackReader, BufferedReader。類似於FilterInputStream。
- InputStreamReader:字節字符轉化流。從字節中讀取字符數據。
- PipedReader:管道字符流。用於從管道中讀取字符數據。類似於PipedInputStream。
- StringReader :字符讀取器。從字符中讀取數據。
常用的有FileIReader,我們將以它為例。對比InputStream,Reader少了一些從字節中讀取數據的類:AudioInputStream,ObjectInputStream,SequenceInputStream,但多了一些從字符中讀取數據的類:InputStreamReader, StringReader。
Writer主要子類有:
- BufferedWriter:緩沖字符輸出流。將字符數據寫入緩沖區。類似於BufferedOutputStream。
- CharArrayWriter:字符數組輸出流。將字符數據寫入字符數組。類似於ByteArrayInputStream。
- FileWriter:文件輸出流。將字符數據寫入文件。類似於FileOutputStream。
- FilterWriter:過濾輸出流。用於裝飾。類似於FilterOutputStream。
- OutputStreamWriter:字節字符轉化流。
- PipedWriter:管道字符流。用於將字符數據寫入管道。類似於PipedOutputStream。
- PrintWriter:打印輸出流。
- StringWriter :字符輸出流。
常用的有FileWriter,我們將以它為例。對比OutputStream,Writer少了一些將字節數據寫入的類:ObjectOutputStream,但多了一些將字符數據寫入的類:OutputStreamWriter,StrngWriter,PrintWriter
Reader與Writer的方法
Reader字符輸入流,其主要方法有:
abstract void close() :釋放流對象。用於關閉資源。close()后無法進行read及skip等操作。所以一般在流操作結束后進行close()調用。
void mark(int readAheadLimit) :標記流的位置。系統不一定支持。不推薦使用。
boolean markSupported() :檢測是否支持流位置標記。(mark方法與reset方法在其支持下才可用。一般能不用則不用此3個方法)
int read() :從流中讀取一個字符(一個或兩個或三個字節,依據編碼決定),如果沒有數據,返回-1。
int read(char[] cbuf) :從流中讀取字符,並將數據存入到指定的字符數組中,讀取的字符數為指定數組的長度。如果沒有數據,返回-1。
abstract int read(char[] cbuf, int off, int len) :從流中讀取字符,並將數據存入到指定的字符數組中,讀取的字符數為len,存入時從數組off開始存。如果沒有數據,返回-1。
int read(CharBuffer target) :從流中讀取字符,並將數據存入到指定的字符緩沖上,讀取的字符數為指定字符緩沖的大小。如果沒有數據,返回-1。
boolean ready() :判斷流是否可以被讀取。
void reset() :從新設置流的開始。系統不一定支持。不推薦使用。
long skip(long n):跳過指定數目字符。可以是正數負數或0(對於文件流來說,該方法雖然已被重載,但不可以用來解決系統不支持reset()問題。負數或0是不支持的,會拋出異常)。
輸入字符流Reader用於“讀取”,其中最常用的方法是:read(), read(char[] b), read(char[] b, int off, int len)
代碼:(源代碼文件編碼為:GBK)
1 import java.io.File; 2 import java.io.FileReader; 3 import java.io.IOException; 4 import java.io.Reader; 5 6 public class Reader001 { 7 public static void main(String[] args) throws IOException { 8 Reader r = new FileReader(new File("g:/java2019/file.txt"));//gbk編碼的文件,內容為:123abc我愛你 9 int c = 0; 10 while((c=r.read())!=-1){ 11 System.out.println(c+"(0x"+Integer.toHexString(c).toUpperCase()+"):"+(char)c); 12 } 13 r.close(); 14 } 15 }
輸出:
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
改變file.txt的編碼為UTF-8,再次運行,輸出:
38168(0x9518):鍩
65533(0xFFFD):?
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?
產生亂碼,可見,使用FileReader讀取數據時,當源文件(程序)與被讀取的文件編碼不一致的時候,會產生亂碼。
將源文件編碼也修改為UTF-8,執行輸出:
65279(0xFEFF):
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
沒有產生亂碼,但是頭部多了一些東西:65279(0xFEFF)。這個是utf-8的BOM
為什么會多這個標記呢?這個與WINDOWS記事本保存時的編碼設置有關。
要去除這個BOM標記,可以用IDE或高級文本編碼器重新保存一下為UTF-8(去BOM)則可以
如果還是不行,則可以先用WINDOWS記事本保存為GBK(保存為GBK一般會去除BOM),再次在IDE或高級文本編碼器中保存為UTF-8。
對於字符流,往往涉及到編碼問題。所以,在談及字符操作之前,先來說明一下編碼的問題。
WINDOW記事本保存文件時對編碼的處理:
1。可以保存為ANSI(系統默認編碼,簡體中文中一般指GBK,繁體中文中可能是BIG5)或者UTF-8等編碼。
2。如果保存為ANSI內置編碼,會去除文件中包含的BOM標記。
3。如果保存為UTF-8編碼,會自動添加BOM標記。
4。當保存為UTF-8編碼時,如果文件內容當中沒有任何中文(ANSI)相關的內容出現,經常會自動保存為ANSI編碼並去除BOM(不是一定,有些會有些不會。並且這個不僅僅是記事本會產生的問題,而是WINDOWS系統的問題,在WINDOWS下,其它文本編輯工具也會產生這樣的問題)。很多時候明明保存為UTF-8保存,卻變成了GBK就是這個原因,包括很多用代碼生成和寫入的純文本文件。
UTF-8對於Java而言最大的問題是:Java編譯器不支持帶BOM的UTF-8,只支持無BOM的UTF-8源文件的編譯。所以如果要采用UTF-8編寫源文件的話,必須去除BOM。
去除BOM的方法:
- 1。用WINDOWS記事本先將編碼設置為ANSI,再用支持UTF-8的高級文本編輯器(這種類型的編碼器在從ANSI轉碼為UTF-8時不會主動加BOM,但從有BOM的UTF-8無法轉ANSI無法去BOM。比如:Eclipse)保存為UTF-8無BOM編碼。
- 2。直接用支持去BOM的高級文本編碼器保存為UTF-8無BOM編碼(比如:Editplus,Notepad++)。
WINDOWS中查看文件編碼的方法:
用記事本打開該文件,然后點擊”文件“,再點擊”另存為“,這時有”編碼“項,當前顯示的編碼就是該文件的編碼。
不要太確信高級文本編輯器(或IDE)顯示的編碼,因為有時候是錯誤的。
有時候是因為純英文無ANSI文字造成了顯示UTF-8而真實卻是ANSI,有時候是因為UTF-8帶BOM設置成ANSI(GBK)無效造成了顯示ANSI真實卻是UTF-8帶BOM。在Eclipse,Notepad++都遇到過這種情形。
另外某些IDE進行編碼指定時,並不會改變文件的編碼,而是指定讀取文件或者運行該文件生成的字節碼時的附加編碼。所以特別注意:IDE設置的編碼真的不一定可靠!
所以要確認文件的編碼還是要通過記事本的方法。不要太輕信高級編輯器或IDE。包括上邊說到的“去除BOM的方法”還需要最終再用記事本打開的方法再確認一下。
隨着上邊的方案,我們可以做到無BOM的UTF-8。
為了避免BOM或編碼問題,編輯器的選擇原則是:通用一種編輯器進行文件編輯和編碼,不采用另外的編輯器對文件進行修改或二次保存,並且不采用WINDOWS記事本進行編輯(它無法解決帶BOM的UTF-8問題),只用WINDOWS記事本來確認編碼。
java源文件的編碼:是指.java文件的編碼,在保存的時候可以選擇ANSI(系統默認編碼,簡體中文中一般指GBK,繁體中文中可能是BIG5),UTF-8等。一般都會在ansi和utf-8之間選擇一個。由上邊的問題可知:如果是UTF-8編碼的源文件,必須是無BOM的UTF-8源文件,否則編譯時會出現:錯誤: 需要class, interface或enum
java類文件的編碼:是指.class文件的編碼。由於.class文件由.java文件產生的,所以源文件什么編碼會決定生成的類文件什么編碼。但是類文件的編碼,並不一定代表程序執行時的最終編碼。
程序執行時的最終編碼:由Java執行器當中的參數:java -Dfile.encoding=編碼 決定。並且,一般情況下,如果沒有指定(即省略-Dfile.encoding=編碼), 則為類文件的編碼。也就是說,默認情況(無指定)下,file.encoding的值由類文件編碼決定。
在程序中輸出 System.getProperty("file.encoding")或Charset.defaultCharset()可以獲取file.encoding的指定值。這個值或是默認的(類文件編碼),或是外部設置的(高級編輯器(或IDE)通過設置編碼指定的,或是某些WEB運行環境下配置指定的)所以輸出的值如果在命令行或IDE或WEB環境下,都可能會不相同。由於外部可以設置,所以說程序執行時的編碼不一定是類文件的編碼。另一方面證明了:雖然高級編輯器(或IDE)保存純英文化的無BOM的UTF-8源文件不成功導致生成了GBK編碼的類文件,但執行時仍然是UTF-8形式,就是因為設置UTF-8同時指定了file.encoding。所以在該高級編輯器(或IDE)下進行執行是沒問題的,但要保證移植到其它編輯或運行環境下仍然想要正確的結果,那么就要對其它編輯或運行環境設置相同編碼約定(有時不需要設定是因為已經一致)。
純英文代碼的GBK編碼的類文件與UTF編碼無BOM的類文件對於執行器執行來說是兼容的,但file.encoding,它會對字符流之類以及對與編碼相關的代碼產生影響。所以說來說去最重要的還是file.encoding。這個是我們代碼無法保證的,因環境變化而變化,我們要確保的是所有環境保持相同的編碼,以避免開發與運行的環境不同導致錯誤的結果。但是另一個問題是,很多時候環境並不是我們能確保的(比如很多第三方環境無法進行配置),這時候涉及到與字符流或編碼相關的代碼時,我們應當使用支持編碼設定的類並配合Charset類進行編碼處理,這個是更推薦的辦法。還是要強調:不要太依賴於源文件的編碼。
有了以上編碼基礎現在開始分析運行FileReader程序讀取file.txt的情況。
1。頭部輸出多余的FEFF :這個屬於UTF-8帶BOM,先按照“去除BOM的方法”去除掉UTF-8中的BOM。再次輸出的時候就不會有多余的BOM標記。
2。IDE與file.txt設置的編碼不同的情況下,會出現亂碼,設置的編碼的情況下,不會出現亂碼。
3。構造三個編碼不同的文件:GBK編碼文件(file-gbk.txt),有BOM的UTF-8編碼文件(file-bom.txt),無BOM的UTF-8編碼文件(file-nobom.txt)進行測試。以驗證上邊結論。
(1) 假定IDE設置的源文件編碼為:GBK。
分別讀取上邊3個文件進行輸出驗證(文件內容都為:123abc我愛你)
測試代碼:
1 import java.io.File; 2 import java.io.FileReader; 3 import java.io.IOException; 4 import java.io.Reader; 5 import java.nio.charset.Charset; 6 7 public class Reader002 { 8 public static void main(String[] args) throws IOException { 9 System.out.println("Charset.defaultCharset():" + Charset.defaultCharset()); 10 System.out.println("System.getProperty(\"file.encoding\"):" + System.getProperty("file.encoding")); 11 12 String[] paths = new String[] { 13 "g:/java2019/file-gbk.txt", 14 "g:/java2019/file-nobom.txt", 15 "g:/java2019/file-bom.txt" }; 16 17 for (String path : paths) { 18 System.out.println("---------"+path+"----------"); 19 Reader r = new FileReader(new File(path)); 20 int c = 0; 21 while ((c = r.read()) != -1) { 22 System.out.println(c + "(0x" + Integer.toHexString(c).toUpperCase() + "):" + (char) c); 23 } 24 r.close(); 25 } 26 27 } 28 }
輸出:
Charset.defaultCharset():GBK
System.getProperty("file.encoding"):GBK
---------g:/java2019/file-gbk.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
---------g:/java2019/file-nobom.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?
---------g:/java2019/file-bom.txt----------
38168(0x9518):鍩
65533(0xFFFD):?
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?
(2) 假定IDE設置的源文件編碼為:UTF-8。
測試代碼不變,仍然執行,輸出:
Charset.defaultCharset():UTF-8
System.getProperty("file.encoding"):UTF-8
---------g:/java2019/file-gbk.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
65533(0xFFFD):�
1200(0x4B0):Ұ
65533(0xFFFD):�
65533(0xFFFD):�
65533(0xFFFD):�
---------g:/java2019/file-nobom.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
---------g:/java2019/file-bom.txt----------
65279(0xFEFF):
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
另外,通過命令行也可以分別進行測試:
> java Reader002 回車輸出:
Charset.defaultCharset():GBK
System.getProperty("file.encoding"):GBK
---------g:/java2019/file-gbk.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
---------g:/java2019/file-nobom.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?
---------g:/java2019/file-bom.txt----------
38168(0x9518):鍩
65533(0xFFFD):?
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?
> java -Dfile.encoding=utf-8 Reader002 回車輸出:
Charset.defaultCharset():UTF-8
System.getProperty("file.encoding"):utf-8
---------g:/java2019/file-gbk.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
65533(0xFFFD):?
1200(0x4B0):?
65533(0xFFFD):?
65533(0xFFFD):?
65533(0xFFFD):?
---------g:/java2019/file-nobom.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
---------g:/java2019/file-bom.txt----------
65279(0xFEFF):?
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
以上測試驗證了上邊的結論:
(1).執行程序的編碼/Charset.defaultCharset()/System.getProperty("file.encoding") 是由運行環境的-Dfile-encoding=XXX決定的(通常可以由外部環境指定。外部環境一般指:命令行參數或IDE編碼設置或WEB環境文件配置)。如果沒有指定時,-Dfile-encoding默認等於類文件的編碼(這時才由類文件編碼決定)
(2).執行程序的編碼與要處理的文本文件的編碼不一致時,在沒有任何代碼相關的編碼處理時(上邊的測試程序沒有應用編碼相關的代碼處理,而FileReader默認使用Charset.defaultCharset()),會出現亂碼。另外,程序中使用System.setProperty("file.encoding","utf-8");是沒有作用的。
如果要處理的文件資源編碼不可變(比如現在要讀取file-nobom.txt這個UTF-8無BOM文件)同時file.encoding也無法設置(假定當前為GBK)。那么顯示編碼是沖突了,使用FileReader使用的是默認的無法改變的編碼(與file.encoding相同:GBK),如果要正常輸出,這是不可能的,不過Reader的子類:InputStreamReader可以處理編碼沖突的情況,它通過InputStream並指定編碼,可以用來處理字符並解決編碼問題。測試代碼:
1 import java.io.File; 2 import java.io.FileInputStream; 3 import java.io.IOException; 4 import java.io.InputStreamReader; 5 import java.io.Reader; 6 import java.nio.charset.Charset; 7 8 public class Reader003 { 9 public static void main(String[] args) throws IOException { 10 System.out.println("Charset.defaultCharset():"+Charset.defaultCharset()); 11 System.out.println("System.getProperty(\"file.encoding\"):"+System.getProperty("file.encoding")); 12 File file = new File("g:/java2019/file-nobom.txt"); 13 Reader r = new InputStreamReader(new FileInputStream(file),"utf-8");//內容為:123abc我愛你 14 int c = 0; 15 while((c=r.read())!=-1){ 16 System.out.println(c+"(0x"+Integer.toHexString(c).toUpperCase()+"):"+(char)c); 17 } 18 r.close(); 19 } 20 }
輸出:
Charset.defaultCharset():GBK
System.getProperty("file.encoding"):GBK
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):愛
20320(0x4F60):你
說明:通過InputStreamReader,可以在編碼不匹配的情況下解決沖突問題,解決了FileReader默認編碼的問題。在不能進行環境設置的情況下特別有用!!!
InputStreamReader的類聲明如下:
public class InputStreamReader extends Reader
InputStreamReader主要的構造方法有:
public InputStreamReader(InputStream in):采用默認編碼利用InputStream進行構造。相同於:InputStreamReader(InputStream in, System.getProperty("file.encoding")) 或 InputStreamReader(InputStream in, Charset.defaultCharset())
public InputStreamReader(InputStream in, String charsetName):通過字符串指明編碼利用InputStream進行構造。常用。
public InputStreamReader(InputStream in, Charset cs):通過Charset對象指明編碼利用InputStream進行構造。
public InputStreamReader(InputStream in, CharsetDecoder dec):較為復雜,少用。
現在繼續說Reader的讀取操作,可以更高效的處理的方法,是采用:read(char[] c, int off, int len) 來減少讀取次數,畢竟每次讀取會阻塞,減少了讀取次數,效率就提高了。
一次性讀取不同編碼的文件內容,代碼 :
1 import java.io.File; 2 import java.io.FileInputStream; 3 import java.io.IOException; 4 import java.io.InputStreamReader; 5 import java.io.Reader; 6 7 public class Reader004 { 8 public static void main(String[] args) throws IOException { 9 File file = new File("g:/java2019/file-nobom.txt"); 10 Reader r = new InputStreamReader(new FileInputStream(file),"utf-8");//內容為:123abc我愛你 11 12 char[] c = new char[(int)file.length()]; 13 while((r.read(c))!=-1){ 14 System.out.println(c); 15 } 16 r.close(); 17 } 18 }
輸出:123abc我愛你
非一次性讀取代碼:
1 import java.io.File; 2 import java.io.FileInputStream; 3 import java.io.IOException; 4 import java.io.InputStreamReader; 5 import java.io.Reader; 6 import java.util.Arrays; 7 8 public class Reader005 { 9 public static void main(String[] args) throws IOException { 10 File file = new File("g:/java2019/file-nobom.txt"); 11 Reader r = new InputStreamReader(new FileInputStream(file),"utf-8");//內容為:123abc我愛你 12 13 int len=0; 14 char[] c = new char[3]; 15 while((len=r.read(c))!=-1){ 16 System.out.println(Arrays.copyOfRange(c, 0, len)); 17 } 18 r.close(); 19 } 20 }
輸出:
123
abc
我愛你
Writer字符輸出流,其主要方法有:
Writer append(char c) :以追加的方式寫入一個字符。可鏈式調用。
Writer append(CharSequence csq) :以追加的方式寫入一個字符序列。可鏈式調用。
Writer append(CharSequence csq, int start, int end) :以追加的方式寫入一個指定開始與結束位置的字符序列。可鏈式調用。
abstract void close() :釋放流對象。用於關閉資源。close()后無法進行read及skip等操作。所以一般在流操作結束后進行close()調用。
abstract void flush() :刷新輸出流。特別在使用緩沖區時,如果在進行write和append方法當緩沖區超過輸出數據量時,會自動刷新並寫入,如果沒有超過則不會進行刷新與寫入。如果沒有close()方法時末端未超過的可能不會進行寫入。
不過如果采用JDK1.7的try-resource異常處理,會進行自動close()關閉,倒可以放心。
void write(char[] cbuf) :寫入一個字符數組數據。
abstract void write(char[] cbuf, int off, int len) :寫入一個指定開始與結束的字符數組數據。
void write(int c) :寫入一個字符數據。
void write(String str) :寫入一個字符串的數據。
void write(String str, int off, int len):寫入一個指定開始與結束的字符串的數據。
輸出字符流Writer用於“寫入”,其中最常用的方法是:write(int c), write(char[] b), write(char[] b, int off, int len), writer(String str) , Write(String str, int off, int len)
測試代碼:
1 import java.io.FileWriter; 2 import java.io.IOException; 3 import java.io.Writer; 4 import java.nio.charset.Charset; 5 6 public class Write001 { 7 public static void main(String[] args) throws IOException { 8 System.out.println(Charset.defaultCharset()); 9 Writer w = new FileWriter("g:/java2019/file-001.txt"); 10 w.write("我愛你"); 11 w.close(); 12 } 13 }
輸出:UTF-8
生成或改變的文件格式為:UTF-8
內容為:我愛你
說明:FileWriter按照默認的file.encoding生成了utf-8格式的文件,並且寫入了相應的字符串“我愛你”
改下程序:將寫入的字符串“我愛你”改成“123abc"
1 import java.io.FileWriter; 2 import java.io.IOException; 3 import java.io.Writer; 4 import java.nio.charset.Charset; 5 6 public class Write001 { 7 public static void main(String[] args) throws IOException { 8 System.out.println(Charset.defaultCharset()); 9 Writer w = new FileWriter("g:/java2019/file-001.txt"); 10 w.write("123abc"); 11 w.close(); 12 } 13 }
輸出:UTF-8
文件格式變為:ANSI(GBK)
文件內容:123abc
可見:純英文下的UTF-8在生成的時候,會自動變成GBK。
如果需要在純英文下也能生成UTF-8,則可以采取添加BOM頭,一旦有BOM頭並指明UTF-8,則必須是UTF-8格式。代碼如:
1 import java.io.File; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.OutputStreamWriter; 5 import java.nio.charset.Charset; 6 7 public class Write001_2 { 8 public static void main(String[] args) throws IOException { 9 System.out.println(Charset.defaultCharset()); 10 File file = new File("g:/java2019/file-001.txt"); 11 FileOutputStream fos = new FileOutputStream(file); 12 //fos.write BOM header 13 fos.write(0xEF); 14 fos.write(0xBB); 15 fos.write(0xBF); 16 fos.close(); 17 fos = new FileOutputStream(file,true); 18 OutputStreamWriter w = new OutputStreamWriter(fos,"utf-8"); 19 w.write("123abc"); 20 w.close(); 21 } 22 }
一旦頭部添加了BOM標記,即使new OutputStreamWriter(fos,"utf-8") 設置編碼為gbk,仍然會生成UTF-8(帶BOM)。不過生成的帶BOM的UTF-8文件如果要程序中要再次進行讀取操作的話,就會出現頭部有BOM的問題。除非一定要生成UTF-8編碼文本或者生成之后不進行后續的被代碼程序讀取,否則指定UTF-8而被生成GBK格式的文件也是無所謂的(后續遇到程序要處理,也可以配合轉換流進行操作)
不考慮復制后是否UTF-8格式,現在進行純文本文件的復制,代碼為:
1 import java.io.FileReader; 2 import java.io.FileWriter; 3 import java.io.IOException; 4 import java.io.Reader; 5 import java.io.Writer; 6 import java.nio.charset.Charset; 7 8 public class Write002 { 9 public static void main(String[] args) { 10 System.out.println(Charset.defaultCharset()); 11 try (Reader r = new FileReader("g:/java2019/file.txt"); 12 Writer w = new FileWriter("g:/java2019/file-001.txt");) { 13 int len = 0; 14 char[] c = new char[1024]; 15 while ((len = r.read(c)) != -1) { 16 w.write(c, 0, len); 17 } 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 } 22 }
輸出:utf-8
生成的UTF-8格式文件file-001.txt內容為:123abc�Ұ���
顯然亂碼了,原因在於讀取的資源文件編碼為GBK與file-encoding沖突。解決辦法上邊已經使用過多次。要么將file.txt編碼更換為utf-8,要么采用讀取轉換流。這里用后者,代碼更改為:
1 import java.io.FileInputStream; 2 import java.io.FileWriter; 3 import java.io.IOException; 4 import java.io.InputStreamReader; 5 import java.io.Writer; 6 import java.nio.charset.Charset; 7 8 public class Write003 { 9 public static void main(String[] args) { 10 System.out.println(Charset.defaultCharset()); 11 try (InputStreamReader r = new InputStreamReader(new FileInputStream("g:/java2019/file.txt"),Charset.forName("GBK")); 12 Writer w = new FileWriter("g:/java2019/file-001.txt");) { 13 int len = 0; 14 char[] c = new char[1024]; 15 while ((len = r.read(c)) != -1) { 16 w.write(c, 0, len); 17 } 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 } 22 }
現在不會出現亂碼了!!
另外,Java還為高效的操作字符流提供了緩沖字符輸入流BufferedReader與緩沖字符輸出流BufferedWriter,操作代碼:
1 import java.io.BufferedReader; 2 import java.io.BufferedWriter; 3 import java.io.FileInputStream; 4 import java.io.FileWriter; 5 import java.io.IOException; 6 import java.io.InputStreamReader; 7 import java.nio.charset.Charset; 8 9 public class Write004 { 10 public static void main(String[] args) { 11 System.out.println(Charset.defaultCharset()); 12 try (BufferedReader r = new BufferedReader( 13 new InputStreamReader(new FileInputStream("g:/java2019/file.txt"), Charset.forName("GBK"))); 14 BufferedWriter w = new BufferedWriter(new FileWriter("g:/java2019/file-001.txt"));) { 15 int len = 0; 16 char[] c = new char[1024]; 17 while ((len = r.read(c)) != -1) { 18 w.write(c, 0, len); 19 } 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 }
可見,完全可以兼容我們自己寫的復制代碼,只需要替換特定的輸入流與輸出流的構造部分,其它部分可以不需要變化。另外,緩沖的字符流還提供了比較好用的一行一行的讀取與寫入的方法,以及寫入換行的方法,見代碼:
1 import java.io.BufferedReader; 2 import java.io.BufferedWriter; 3 import java.io.FileInputStream; 4 import java.io.FileWriter; 5 import java.io.IOException; 6 import java.io.InputStreamReader; 7 import java.nio.charset.Charset; 8 9 public class Write005 { 10 public static void main(String[] args) { 11 System.out.println(Charset.defaultCharset()); 12 try (BufferedReader r = new BufferedReader( 13 new InputStreamReader(new FileInputStream("g:/java2019/file.txt"), Charset.forName("GBK"))); 14 BufferedWriter w = new BufferedWriter(new FileWriter("g:/java2019/file-001.txt"));) { 15 String s = null; 16 while ((s = r.readLine())!=null) { 17 w.write(s); 18 w.newLine(); 19 } 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 }
另外還可以使用打印流PrintWriter進行文件復制:
1 import java.io.BufferedReader; 2 import java.io.FileInputStream; 3 import java.io.FileWriter; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.io.PrintWriter; 7 import java.nio.charset.Charset; 8 9 public class Write006 { 10 public static void main(String[] args) { 11 System.out.println(Charset.defaultCharset()); 12 try (BufferedReader r = new BufferedReader( 13 new InputStreamReader(new FileInputStream("g:/java2019/file.txt"), Charset.forName("GBK"))); 14 PrintWriter w = new PrintWriter(new FileWriter("g:/java2019/file-001.txt"));) { 15 String s = null; 16 while ((s = r.readLine())!=null) { 17 w.println(s); 18 } 19 } catch (IOException e) { 20 e.printStackTrace(); 21 } 22 } 23 }
說明:打印流有println可以輸出並換行(內部調用了newLine())。另外,打印流的write(int c)與print(int c)區別:前者會轉化為字符進行寫入,后者直接原樣寫入。
其它的字符處理類:暫不介紹。
總結:
- FileReader與FileWriter可以以字符流形式處理文件,進行讀取或寫入操作。
- 要進行高效的字符流處理,可以使用內置的BufferedReader及BufferedWriter緩沖字符流,它可以處理各種Reader及Writer(不僅僅是FileReader及FileWriter)
- 使用read(char[] b, int off, int len)及write(char[], int off, int len) 可以更高效的進行字符復制(可以替代緩沖字符流)
- 對於文件編碼不一致時,可以采用轉換輸入流InputStreamReader或轉換輸出流OutputStreamWriter進行轉碼操作