【Java】Reader与Writer的使用和讲解

by img Microanswer 创建时间:Jun 19, 2019 4:38:16 PM 

标签: Java reader writer 编码 字符读取 文本读取



重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

本篇文章承接了上一篇:《【Java】I/O流的操作、认识使用》。建议先阅读了上一篇再阅读本篇。

在讲述 I/O 流时,我们使用了一只桶来舀湖里的水来做比喻,形象的描述 IO流 的运作和处理方式, 同时在文中还列举了读取文件中的字符串以及将字符串写出到文件中的方法。但是你细心会发现, 读取和写入的示列字符串都是英文内容的,而不是中文字符或者什么的。这主要是为了减少写入或读取时出现的 不可预知的问题,实际上上一篇的示列读取和写入字符串的部分是有问题的。到底出了什么问题?本文将通过 ReaderWriter 这两个类的讲解进行解答!

一、思考

在java中使用 InputStreamOutputStream 的确能帮助我们完成许多比较高级一点的功能,但是在处理文字 方面,它们的确显得捉襟见肘了,我先不说为什么捉襟见肘,先做另一个比喻来引入值得思考的这个问题。

假如湖里装的不是流动的一滩水,而是已经使用矿泉水瓶一瓶一瓶包装了的水,而且有些瓶大有些瓶小,这个时候, 你再用那套方案,用一只桶来舀,你也许可以舀起来,但是保不齐你每次都把桶满打满算装满,毕竟变成了一瓶一瓶的了, 这时候就有非常大的几率出现:一桶没满,但是再装一瓶又会多出来,桶装不下!这就是要思考的问题。

你说,直接不装那一瓶,我多跑一趟不就好了!

但是,咱们毕竟是程序,在这件问题上,程序就没有那么灵活。这个问题要反应到程序里,就会出现有些字符读出来或者 写入到文件中会出现个别文字乱码!!的确显得很诡异。这个问题不得不说一下文字在计算机里的保存方式了。先简单的描述 一下,字符串要保存到计算机里面,英文字符,一个字符一个字节,而中文或者其他特殊字符,一个字符可能就需要更多的字节 来保存了。明白了这件事,再看看上一篇使用输入输出流每次读取指定个数的字节,这就是问题所在,因为我们不知道这个数据 量的字节里,在数据开始或者数据结尾处是否正好是一个字符的开始或结尾。要是运气不好,原本第4个字节和第5个正好形成一个 中文字符,可是我们却把前4个数据舀在了上个桶里,后面的数据舀在了下一个桶里,不多不少,正好将这个字给搞成了2半, 使用不完整的字节构建字符时肯定就会出现个别字符乱码了!

为了解决这个问题,就不能再用桶去舀了,而是要按“瓶”的数量去拿了。那么在程序里,也就不能再使用 InputStreamOutputStream 直接去操作了,而是要使用 ReaderWriter 来实现对字符的读写了。

有关更多字符在计算机里存储的方式和支付编码相关的知识,请参考:编码解码(字符集)

二、Reader 读取字符

Reader 类可以帮助我们一个字符一个字符的来读取字符串,避免出现字符被拆分导致乱码的现象,但它是个抽象类,不能直接使用, 不过Java已经为我们封装好了许多字符读取实现类。就像FileInputStream那样,你同样能在Java中发现 FileReader, 它可以十分方便的读取字符文件,而且避免出现个别字符乱码的现象。下面是 FileReader 的示列使用程序:

// 为了保证程序的正常,请在D盘下建立文件:file.txt,文件内容:一只灰色的小熊越过一条清澈的消息,跑进了对面的树林里。
public class Test {

    public static void main(String[] args) throws Exception{

        // 使用 D 盘下的 file.txt 文件进行构建。
        FileReader reader = new FileReader("D:/file.txt");

        // 一次最大读取多少个字符
        char[] datas = new char[10];

        // 每次读取实际读取字符数
        int size = 0;

        // 定义保存结果的变量
        StringBuilder stringBuilder = new StringBuilder();

        // 开始循环读取
        while (size != -1) {

            // 赋值实际读取的字符数量
            size = reader.read(datas);

            // 保存读取的结果
            stringBuilder.append(datas, 0 , size);
        }

        // 读取完成关闭文件。
        reader.close();

        // 输出结果
        System.out.println(stringBuilder.toString());
    }
}

运行程序,输出:一只灰色的小熊越过一条清澈的消息,跑进了对面的树林里。

有了上一篇输出流的相关知识,这部分代码理解起来应该十分容易。现在,我们知道如何更加优雅的读取字符文件了。面对文件我们可以使用功能 FileReader 进行方便的读取, 那如果给我们的数据不是一个文件,而是一个输入流 InputStream 要我们从这个输入流中读取出字符内容,典型的场景就是Http请求响应时,响应的InputStream就需要我们读取里面的字符内容,这又该怎么办?

InputStreamReader

借助 InputStreamReader 可以完成这项需求,见名知意,这是输入流字符读取类,他能帮我们直接从输入流二进制数据中读取出字符内容来。实际上 FileReader 也是继承自 InputStreamReader 实现的。

下面是 InputStreamReader 的使用示列代码:

public class Test {

    public static void main(String[] args) throws Exception{

        // 拿到 D 盘下的 file.txt 文件。
        File f = new File("D:/file.txt");
        
        // 建立文件输入流
        FileInputStream fileInputStream = new FileInputStream(f);
        
        // 建立输入流字符读取类, 我们还可以指定以何种编码来读取。
        InputStreamReader reader = new InputStreamReader(fileInputStream, "UTF-8");

        // 一次最大读取多少个字符
        char[] datas = new char[10];

        // 每次读取实际读取字符数
        int size = 0;

        // 定义保存结果的变量
        StringBuilder stringBuilder = new StringBuilder();

        // 开始循环读取
        while (size != -1) {

            // 赋值实际读取的字符数量
            size = reader.read(datas);

            // 保存读取的结果
            stringBuilder.append(datas, 0 , size);
        }

        // 读取完成关闭文件。
        reader.close();

        // 输出结果
        System.out.println(stringBuilder.toString());
    }
}

阅读这段代码,可以发现,使用方法和 FileReader 在读取部分完全相同。只有实例化 InputStreamReader 的时候,使用了输入流对象。 InputStreamReader 在实例化时可以指定编码方式,而 FileReader 不能,所以,有时候如果使用 FileReader 出现乱码,可以考虑 使用 InputStreamReader 传入合适的 编码 进行字符的读取。

三、Writer 写字符串

Writer 可以帮组我们将字符串顺利的输出到程序外部,而且避免乱码的出现。同样它是一个抽象类,与 Reader 相对,Java也为我们提供了 FileWriter 方便字符文件的写出。使用示列:


public class Test {

    public static void main(String[] args) throws Exception{

        // 写出的文件将保存在 D 盘 fileWrite.txt 文件里。
        FileWriter writer = new FileWriter("D:/fileWrite.txt");

        // 准备要写的数据
        String data = "一直灰色的小熊穿过了一条清澈的小溪,跑进了对面的小树林里!";
        
        // 进行写入
        writer.write(data);

        // 关闭
        writer.close();
    }
}

运行程序,在D盘会产生新文件:fileWrite.txt 打开即可看到内容。

OutputStreamWriter

同样的,OutputStreamWriter 可以帮助我们把字符写出到任何输出流里,而并非只是文件输出流,同时,还支持使用指定编码将字符写出到程序外部, 学会 OutputStreamWriter 也会为以后的开发代码许多帮助。下面是一个将字符以GB2312格式输出到文件的案例:

public class Test {

    public static void main(String[] args) throws Exception{

        // 写出的文件将保存在 D 盘 fileWriteGb2312.txt 文件里。
        File f = new File("D:/fileWriteGb2312.txt");

        // 准备要写的数据
        String data = "一直灰色的小熊穿过了一条清澈的小溪,跑进了对面的小树林里!";

        // 建立输出流
        FileOutputStream fileOutputStream = new FileOutputStream(f);

        // 建立字符写出对象
        OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream, "GB2312");

        // 进行写入
        writer.write(data);

        // 关闭
        writer.close();
    }
}

运行程序,在D盘会产生新文件:fileWriteGb2312.txt, 使用文本编辑器打开,查看编码,可以看到该文件是 GB2312的编码。

全文完, 转载请注明出处。 对你有帮助?不如赞一个吧:
发表评论(发表评论需要登录,你现在还没有登录。)
你需要先登录才可以评论。

评论列表 (0条)