Reader和Writer是另一种体系的I/O类,上一篇文章中学习的InputStream和OutputStream体系的类是按照字节(byte)读写数据,Reader和Writer体系的类则是使用字符(char)读写数据,Reader和Writer是体系的根类,它们都是抽象类。

为什么需要Reader和Writer

String底层使用char数组来存储数据,每个char占用两个字节,每个char表示一个Unicode,String实际存储的是文字的Unicode序列,测试代码如下:

String str = "你好吗";
char[] chs = str.toCharArray();
for (char ch: chs) {
    System.out.printf("%04x\n", (int)ch);
}
/*
输出:
4f60
597d
5417
 */

“你好吗”对应的Unicode编码分别为4f60、597d、5417,和上例中的输出一致。 Java使用String来表示文本或者字符串, 怎么把一个字符串按照某种编码格式保存到文件,或者怎么把一个按照某种编码格式保存的文本文件读取到String对象里面?上例中我们已经看到String是按照Unicode来保存数据,使用两个字节来表示一个文字,一个按照某种格式编码的文本文件可能是用一个字节也可能是用三个字节来表示一个文字,所以要读写编码过的文本就需要进行转码操作。 Reader和Writer主要用来处理文本的读写,它内部已经封装了各种转码操作,使用Reader和Writer可以很方便的读写各种编码格式的文本。

Reader和Writer体系的类可以分为三种类型:

  • 原始型
  • 适配型
  • 装饰型

原始型

原始型的类直接继承自Reader或Writer且不依赖其它的I/O类。常用的原始型类有:

  • StringReader和StringWriter
  • CharArrayReader和CharArrayWriter

StringReader和StringWriter用来读写字符串,使用示例如下:

//StringReader使用示例
String s = "hello, world!";
StringReader sr = new StringReader(s);
int ch = sr.read();
if (ch != -1)
{
    System.out.printf("%04x\n", (int)ch);
}
/*
输出:
0068
 */

CharArrayReader和CharArrayWriter用来读写字符数组,使用示例如下:

//CharArrayReader使用示例
char[] charBuf = new char[1024];
charBuf[0] = 'h';
CharArrayReader cr = new CharArrayReader(charBuf);
int ch = cr.read();
if (ch != -1){
    System.out.printf("%04x\n", (int)ch);
}
/*
输出:
0068
//CharArrayWriter使用示例
CharArrayWriter cw = new CharArrayWriter();
cw.write('h');
char[] buf = cw.toCharArray();
System.out.printf("%04x\n", (int)buf[0]);
/*
输出:
0068
 */

适配型

适配型的类是对InputStream和OutputStream的适配,有了适配机制就可以把InputStream或OutputStream对象转换为Reader或Writer对象,从而可以使用Reader或Writer的方法来读写InputStream和OutputStream。常用的适配型类有:

  • InputStreamReader和OutputStreamWriter
  • FileReader和FileWriter

InputStreamReader和OutputStreamWriter可以用来读写各种编码格式的文本文件,使用示例如下:

public class Test {
    void writerText(String text, String charSetName) throws IOException{
        OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("rw.txt"),
                Charset.forName(charSetName));
        ow.write(text);
        ow.close();
    }
    void readText(String charSetName) throws IOException {
        InputStreamReader ir = new InputStreamReader(new FileInputStream("rw.txt"),
                Charset.forName(charSetName));
        char[] charBuf = new char[1024];
        int n = ir.read(charBuf);
        for (int i=0; i<n; i++){
            System.out.print(charBuf[i]);
        }
        System.out.println();
    }
    public static void main(String[] args) throws IOException{
        Test t = new Test();
        t.writerText("你好java", "GBK");
        t.readText("GBK");
        t.readText("UTF-8");
    }
}
/*
输出:
你好java
���java
 */

上例中先使用GBK编码格式保存字符串“你好java”然后分别用GBK编码和UTF-8编码格式读取,可以看到使用GBK编码可以正常读取,使用UTF-8读取到乱码。

InputStreamReader和OutputStreamWriter在构造时可以指定编码格式,只有指定正确的文本文件编码格式才能正常读写数据,否则会出现乱码。InputStreamReader和OutputStreamWriter封装了转码的细节,我们使用时只需传递文本的编码格式即可。有了InputStreamReader和OutputStreamWriter我们可以很轻松的读写不同编码格式的文本文件。

FileReader和FileWriter分别继承自InputStreamReader和OutputStreamWriter,FileReader和FileWriter使用默认的编码格式读写文本文件,并且封装了流对象的创建过程,使用时只需指定一个文件名即可,下面是使用示例。

//默认编码格式
System.out.println("默认编码格式:" + Charset.defaultCharset().toString());

//FileWriter使用
String str = "你好java";
FileWriter fw = new FileWriter("f.txt");
System.out.println("写入的文件内容为:" + str);
fw.write(str);
fw.close();

//FileReader使用
char charBuf[] = new char[1024];
FileReader fr = new FileReader("f.txt");
int n = fr.read(charBuf);
System.out.print("读取到的文件内容为:");
for (int i=0; i<n; i++){
    System.out.print(charBuf[i]);
}

/*输出:
默认编码格式:UTF-8
写入的文件内容为:你好java
读取到的文件内容为:你好java
 */

上例中系统默认的字符编码是:UTF-8,FileReader和FileWriter都是使用UTF-8格式编解码文本,FileReader和FileWriter使用比InputStreamReader和InputStreamWriter简单,但是它有字符编码限制即只能使用默认编码。实际开发中优先使用FileReader和FileWriter除非不能满足需求。

装饰型

装饰型类和InputStream和OutputStream的装饰器类相似,Reader和Writer的装饰型类没有使用装饰器设计模式。装饰型类依赖于其它的Reader或Writer的对象,它是对已有的Reader或Writer对象的封装,在构造装饰型对象时需要传入一个已有的Reader或Writer对象作为待修饰对象。常用的装饰型类有:

  • BufferedReader和BufferedWriter
  • PrintWriter

BufferedReader和BufferedWriter使用缓存机制来提高读写效率,使用示例如下:

//BufferedWriter使用
String str = "你好java";
BufferedWriter bw = new BufferedWriter(new FileWriter("f.txt"));
System.out.println("写入的文件内容为:" + str);
bw.write(str);
bw.close();

//BufferedReader使用
char charBuf[] = new char[1024];
BufferedReader br = new BufferedReader(new FileReader("f.txt"));
int n = br.read(charBuf);
System.out.print("读取到的文件内容为:");
for (int i=0; i<n; i++){
    System.out.print(charBuf[i]);
}

/*输出:
写入的文件内容为:你好java
读取到的文件内容为:你好java
 */

PrintWriter提供格式化功能,它封装了基本数据类型和字符串的转换操作,调用PrintWriter的方法可以把基本数据类型写到Writer。使用示例如下:

//PrintWriter使用
PrintWriter pr = new PrintWriter(new FileWriter("f.txt"));
pr.println(10);
pr.println(0.001);
pr.println("end");
pr.close();

//读取写入的内容
char charBuf[] = new char[1024];
BufferedReader br = new BufferedReader(new FileReader("f.txt"));
int n = br.read(charBuf);
System.out.println("读取到的文件内容为:");
for (int i=0; i<n; i++){
    System.out.print(charBuf[i]);
}

/*输出:
读取到的文件内容为:
10
0.001
end
 */

类图


java readerFile 行数 java reader writer_java readerFile 行数


java readerFile 行数 java reader writer_为什么使用Reader和Writer_02


最后

Reader和Writer基于字符(char)读写数据,它主要用来读写文本文件,它可以很方便的读写各种编码格式的文本文件。Reader和Writer不是用来取代InputStream和OutputStream,后者基于字节(byte)读取数据,可以用来读取二进制数据,如图像、音频、视频等。使用时优先使用Reader和Writer,如果不能满足需求时再使用InputStream和OutputStream。