Java 字节流与字符流(1)

目录

Java 的 IO 系统是比较庞杂的,各种流特别多,其中有一种就是字符流。

在本系列前面的一些文章中,也曾涉及过字符流的话题,不过没有详细展开讨论,这次准备具体综合地谈一谈。
你可能听过不少关于字节流与字符流对比的介绍,不过严格地说,我认为把“字节流”和“字符流”去对比这种说法不是特别妥当,为什么呢?

首先,这两种流实际上处在不同的层次,字节流是基础,而字符流是构建在其上的:

字节流 字符流 层级关系

对于不同层次上的事物,我认为用“对比”这个词是不太恰当的。

打个不太恰当的比方,比如说你跟隔壁老王同龄,然后你“拿你的儿子跟隔壁老王对比”,那隔壁老王可能就会质问你:“你什么意思?拿你儿子跟我比?你咋不拿你爹跟我比呢?”

你可以“拿自己跟老王对比”,也可以“拿自己儿子跟老王儿子对比”,但你不能”拿你的儿子跟隔壁老王对比“,这就不恰当了,对吧?

要是一对比,发现儿子长得还挺像隔壁老王,那就尴尬了……
那么这里是想说明一个道理,就是不是一个辈分的,不应该去对比。
再打一个比方,就是好比你把“中国”去跟“美国的加利福尼亚州”去对比,通常意义都不大,我们一般都不会这样去做。但在某些特殊情况下,比如想说明加州的经济特别发达,可以拿加州的 GDP 跟整个中国的 GDP 对比,但这种对比只是为了突出一方而不是为了对比双方。
那么同理,处在不同层次的字节流与字符流,它们也不适合去“对比”。但是,可以研究“字节流”和“字符流”之间的关系,这是没有问题的。那么它们之间到底什么关系呢?其实前面那个图也揭示地很清楚了:
字节流是基础,字符流是其上的抽象与封装。
狭隘地讲,比如仅仅从读取或写入文件方面去探讨的话,那么“字符流”是为方便我们读取或写入“文本文件”而引入的抽象。也就是说:
字符流只对应文本文件;

而字节流则对应所有文件,自然也包括文本文件。

文本文件是开发活动中会大量接触到的一类文件。所有语言的源代码文件,像什么 .java,.js 这些都是;还有很多比如 html,xml,css 之类的以及很多的配置文件也是文本文件。

简单讲,就是你可以用“记事本”打开查看的那类文件。
假如你一个文本文件要读取,自然,最好的选择是使用字符流。当然,你也可以选择用字节流来读取。(在后面,会给出一些具体的例子和代码)
如果只要一下子就把整个文件读取上来,那么用字符流跟字节流的差别并不大。

但是,面临某些具体的需求时,比方说,想一个一个“字符”的读取上来,或者想一行一行地读取时,用字符流就会很方便,而用字节流就会非常的麻烦。

另一方面,如果你要读取的并不是一个文本文件,那就不能用字符流了。比如说,读取一个图片文件,或者是一个压缩包,又或者是一个 word 文件或 pdf 文件。这些都不是“文本文件”,因此你不可以用字符流去读取它们。

简单讲,就是你不可以用“记事本”打开去查看它们的那类文件。当然,如果你硬是要用记事本打开也不是不可以,毕竟在最底层,大家都不过是一堆 0 和 1 而已,但这种强行打开通常只会呈现为一堆乱七八糟的东西。
非文本文件不可以用字符流去读取,但它们都可以用字节流去读取,因为本质上来讲,任何的文件都不过是字节的序列而已。

我看到有些介绍字节流与字符流的文章没有特别去强调这一点,给人一种感觉,就好像这两种流都能做任何事情,这显然是错误的。

其实从名字上也不难想到,既然叫字符流,那肯定跟字符有关了。而对于图片来说,构成元素是一个个“像素”,而不是什么字符,所以自然不能用字符流去读取图片。初学者或许能意识到不能用字符流去读图片,但未必能清楚明白,其实还有很多的文件都不能用字符流去读取的。

所以,前面强调两者不能对比,强调两者处在不同层次,在能力上,两者也是有区别的,而所有的字符流最终它的底层其实还是字节流,字符流只是一种抽象。(甚至还不算是一个好的抽象,我们将在后面说明为什么)

对于构建一个字符流而言,其实有一个很重要的参数,却经常被大家所忽略,就是字符集编码。

为什么你可以忽略它呢?因为有缺省的存在。就好像你用记事本保存一个文本文件时,系统通常只提示你输入文件名,而不会提示你选择一个字符集编码,但这并不是说编码是可有可无的,当你没有选择编码时,其实是系统悄悄地给你分配了一个缺省编码。

某些时候,这带来了方便,但也常常给我们带来很多问题。

其实在前面的图中,我把字符流写成“gbk 字符流”,“utf-8 字符流”也是为了凸显这个编码的存在,它不应该被忽略。

前面说,字符流构建在字节流基础之上,其实更加确切的说法应该是:字符流构建在“字节流+字符集编码”基础之上,像这样:

字符流 字节流 字符集编码 关系

最后,字符流中所谓的“字符”其实跟我们认知还是有区别的,前面也说了,这里的字符其实并不是一个很好的抽象。

这里的“字符”严格说,只是 BMP 内的字符,对于 BMP 外的,情况就不同了。
在下一篇,再给出具体的示例。