3. 文件流
3.1 如何選擇文件流的類
文件流應該是Java流中使用最普遍、最廣泛的流了。文件流分爲兩組,一組是操作字節的FileInputStream和FileOutputStream,另一組是操作字符的FileReader和FileWriter,事實上,我們還經常用到FileReader和FileWriter的父類InputStreamReader和OutputStreamWriter,原因後面會闡述。
讓我們從最簡單的開始,使用FileWriter將一個字符串寫入某文件:
private static void writeString2File(String info,String filename){
try {
BufferedWriter bufferedWriter=new BufferedWriter(new FileWriter(filename));
bufferedWriter.write(info);
bufferedWriter.flush();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void exeWriteString2File() {
String info = "Today is a good day.\r\n今天天氣很好。";
try {
String outputfile = new File(".").getCanonicalPath() + File.separator + "output1.txt";
writeString2File(info,outputfile);
} catch (IOException e) {
e.printStackTrace();
}
}
調用exeWriteString2File方法將一個包含兩行字符的字符串寫入了文件output1.txt。
接下來,使用FileReader從output1.txt中讀取信息:
private static String readFile(String filename) {
String str = null;
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader(filename));
StringBuilder sb = new StringBuilder();
while ((str = bufferedReader.readLine()) != null) {
sb.append(str + "\n");
}
str = sb.toString();
bufferedReader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
private static void exeReadFile() {
try {
String outputfile = new File(".").getCanonicalPath() + File.separator + "output1.txt";
String str = readFile(outputfile);
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
}
調用exeReadFile方法從文件output1.txt中讀取了信息,一切都很簡單。
以上代碼的簡單性來自一個假設,那就是我們假設文件編碼都是統一的,但是實際編程中你會經常碰到文件編碼不一致的情景。例如中文編碼最常用的有GBK(多個版本的windows經常將GBK設置爲默認編碼格式)、UTF-8(網絡傳輸、XML文檔中經常使用這個編碼格式)和UTF-16。下面的代碼將同一個字符串分別使用三種編碼格式寫入了三個文件:
private static void writeString2FileWithEncoding(String info,String filename,String charset){
try {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filename));
byte[] bytes = info.getBytes(charset);
bos.write(bytes);
bos.flush();
bos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void exeWriteString2FileWithEncoding() {
try {
String info = "Today is a good day.\r\n今天天氣很好。";
String output_gbk = new File(".").getCanonicalPath() + File.separator + "output_gbk.txt";
writeString2FileWithEncoding(info,output_gbk,"gbk");
String output_utf8 = new File(".").getCanonicalPath() + File.separator + "output_utf-8.txt";
writeString2FileWithEncoding(info,output_utf8,"utf-8");
String output_utf16 = new File(".").getCanonicalPath() + File.separator + "output_utf-16.txt";
writeString2FileWithEncoding(info,output_utf16,"utf-16");
} catch (IOException e) {
e.printStackTrace();
}
}
調用exeWriteString2FileWithEncoding將會創建三個不同編碼格式的文件。在操作系統(windows、linux、Mac)中使用恰當的工具都能正確的打開這些文件。
但如果我們使用前面的FileReader類來直接打開這三個文件,就會出現亂碼:
Today is a good day.
���������ܺá�
Today is a good day.
今天天氣很好。
�� T o d a y i s a g o o d d a y .
N�Y)Y)l _�Y}0
因此我們知道,在對文件進行讀取時,需要考慮編碼的問題。因此FileInputStream/FileOutputStream,FileReader/FileWriter和InputStreamReader和OutputStreamWriter這三組類都有了用武之地,下面是我總結的文件流使用指南:
當對文件進行拷貝、加密、壓縮、摘要等與編碼不相關的操作時,儘量使用字節流FileInputStream/FileOutputStream,文件的加密、壓縮、摘要等功能留待後續章節(加密流、壓縮流和摘要流)介紹;
當需要對文件讀取內容或者寫入指定編碼格式的內容時,使用InputStreamReader和OutputStreamWriter,因爲它們可以指定編碼;也可以使用FileInputStream/FileOutputStream進行字節的讀寫,然後利用String和byte數組的轉換來得到指定編碼的內容;
當程序員可以確認默認的編碼一定能滿足要求時,直接使用FileReader/FileWriter來進行文件的讀寫。
3.2 FileInputStream和FileOutputStream
FileInputStream和FileOutputStream都是處理字節的類,因此使用它們時需要把信息轉換爲字節數組,然後進行輸入輸出操作。
將一個字符串以GBK編碼格式轉換爲字節後寫入當前目錄下的output.txt文件中:
private static void fileOutputStreamExam() {
try {
//得到當前目錄下名爲output.txt文件的路徑
String outputfile = new File(".").getCanonicalPath() + File.separator + "output.txt";
System.out.println(outputfile);
FileOutputStream fos = new FileOutputStream(outputfile);
String str = "Today is a good day.\r\n今天天氣很好。";
byte[] buf = str.getBytes("GBK");
fos.write(buf, 0, buf.length);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
將當前目錄下的output.txt文件以字節流方式讀入,並將讀入的字節數組從GBK編碼格式轉換爲字符(即轉換爲java內部使用的unicode)串:
private static void fileInputStreamExam() {
try {
//得到當前目錄下名爲output.txt文件的路徑
String outputfile = new File(".").getCanonicalPath() + File.separator + "output.txt";
FileInputStream fis = new FileInputStream(outputfile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
baos.write(buf, 0, len);
}
String s = baos.toString("GBK");
System.out.println(s);
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
注意上面這段代碼中使用了後面要講到的字節數組流ByteArrayOutputStream,這個流可以存儲動態長度的字節數組,因此非常適合在這裏作爲信息暫存的對象。
3.3 InputStreamReader和OutputStreamWriter
FileReader和FileWriter只能使用默認的編碼格式來輸入輸出字符,當需要使用其他編碼格式時,可以使用更加通用的類InputStreamReader和OutputStreamWriter。
將字符串以GBK編碼格式輸出到文件output3.txt中:
private static void outputStreamWriterExam() {
try {
String outputfile = new File(".").getCanonicalPath() + File.separator + "output3.txt";
//此處可以指定編碼
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(outputfile), "GBK");
String str = "Today is a good day.\r\n今天天氣很好。";
outputStreamWriter.write(str);
outputStreamWriter.flush();
outputStreamWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
將output3.txt文件中的字符串以GBK編碼格式讀入程序中:
private static void inputStreamReaderExam() {
try {
String outputfile = new File(".").getCanonicalPath() + File.separator + "output3.txt";
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(outputfile), "GBK");
CharArrayWriter charArrayWriter = new CharArrayWriter();
char[] buf = new char[1024];
int len = 0;
while((len=inputStreamReader.read(buf))!=-1){
charArrayWriter.write(buf,0,len);
}
String s = charArrayWriter.toString();
System.out.println(s);
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
注意上面這段代碼中使用了字符數組流CharArrayWriter,這個流可以存儲動態長度的字符數組,因此非常適合在這裏作爲信息暫存的對象。
3.4 FileReader和FileWriter
這兩個類的使用前面已經介紹過了,就不贅述了。
3.5 文件編碼和亂碼
對於Java編程來說,如果使用IDE開發,則在IDE中會指定具體項目的編碼,例如UTF-8或者GBK,那麼在運行代碼時IDE會自動加上-Dfile.encoding=UTF-8等參數,使得當前的默認編碼被設置爲UTF-8。如果在java運行時沒有指定編碼,則會使用操作系統的默認編碼格式,中文windows一般默認是GBK。
一般來說,輸出文件時不太可能產生亂碼,因爲無論你以何種格式編碼將字符流轉換爲字節流並存儲到文件中時,該編碼一定能夠被識別出來,只要你找到合適的文件瀏覽工具。
但是當輸入文件時,如果使用了錯誤的編碼格式進行字節–字符轉換,例如將GBK編碼的文件以UTF-8格式讀入,則會造成亂碼。