Java流编程实例之二--文件流

时间:2021-04-11 20:58:40

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格式读入,则会造成乱码。