编码/解码

概念

字符:是各种文字和符号的总称,包括各个国家的文字、符号、图形符号和数字等。

字符集:是一个系统支持的所有抽象字符的集合。

字符编码:是把字符集中的字符编码成特定的二进制数,以便在计算机中存储,每个字符集中的字符对应唯一的一个二进制数。

编码方式:一个字符的Unicode编码是确定的。但是在实际传输中,不同系统平台的设计不一致,对Unicode的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称UTF)。UTF-8UTF-16UTF-32都是将字符转换为二进制数字的编码方式。

编码

人类先有了自己的语言,交流了若干个世纪,然后出现了计算机。可惜计算机只认0和1,人类只能认文字,双方都不能妥协,那就必须要有一个从文字到0、1的映射了。从文字到0、1的映射称为编码,反过来从0、1到文字叫解码。

计算机里只有0和1,怎么来表示“Hello World”呢(假如我们在美国),那就需要将字母数字及标点符号编一个号。一个字节可以表示256个数字,表示字母数字标点足够了,所以用一个字节就可以对应一个字符了。这样一来计算机在显示文字的时候,先将0、1解码成对应的文字,然后在屏幕上渲染出来就可以了。我们将“Hello World”叫做字符,计算机实际存储的是字符对应的编号,这些编号就叫字节流。

上边这种编码就是ASCII码,如果计算机只在美国用或者只显示英语,那编码就是透明的,谁都不需要去关心编码,一切都觉得理所当然。可是计算机应用到了像中国这样的国家,这些国家的语言哪里只是几个字母啊,有成千上万种不同的字符。很显然ASCII码就不能满足需求了,怎么办呢,每个国家都研制自己的编码呗,很显然这样做并不长久,每个国家都有自己的编码实在有点乱,连两个国家的语言都不能放在一起。所以可以将世界当成一个整体,把所有的文字统一编号,这时候就出现了Unicode编码。用一个字节来表示一个字符显然是不够的,Unicode编码用了两个字节来表示一个字符。其实,编码的发展过程并没有这么顺利,中间还是出现了很多其他的编码,以后的文章可以详细说一下几种常用的编码。那问题岂不是解决了,大家都用Unicode不就完事了吗,哪有这么简单呢,Unicode出现之前计算机领域已经有很多成型的操作系统软件甚至标准,不可能都统一改成Unicode编码。所以到现在还是会遇到编码问题,Unicode只是给我们提供了一种统一解释所有文字的编码方案。要搞清楚,这里讨论的编码都是针对文本字符的。

UTF-8

Unicode字符集为每一个字符分配一个码位,例如“知”的码位是30693,记作U+77E5

UTF-8 顾名思义,是一套以 8 位为一个编码单位的可变长编码。会将一个码位编码为 1 到 4 个字节:

img

根据上面的规则,“知”字的码位U+77E5属于第三行的范围:

img

这就是将U+77E5按照UTF-8编码为字节序列E79FA5的过程,反义亦然。

乱码

编码之所以受到关注,乱码几乎起到了决定性的作用,如果没有乱码,一切都让大家觉得顺理成章,那谁还会关注编码呢。

出现乱码的原因就是文本字符编码过程与字节流解码过程使用了不同的编码格式,这个往往归咎于解码格式选择错误,也就是说在解码的过程中出现了问题。如果我的字符是用utf-8编码,你用GBK解码那肯定出问题。因为文字按照utf-8的编码规则编成的0、1,按照GBK的规则解码回来的文字并不是原来的文字,这时候就会出现乱码了。这种问题会出现在文件读写、网络编码传输、数据库存取上。只要牵涉到字符都有可能出现乱码,因为只要有字符就会有解码过程。

还有一种情况就是文件压根不是文本文件,也就是说根本就没有经过编码这个过程,那你去解码当然乱码了。比如64,你如果看做文本字符就是6和4两个字符,可以对应编码格式进行编码。如果看做是数字64,那对应的存储结构是01000000,就没有编码过程,也就不需要去解码。

要搞清楚的一点就是同样的文本字符,经过不同的编码,在存储结构上是不一样的,但是代表的字符是一样的,不同编码真正的区别在于存储结构。反过来,相同的存储结构,经过不同的解码,对应的文本字符并不一样,但是在内存上结构上并没有改变。如果碰到乱码,不要慌张,因为原始存储结构一动没动,只不过用错了解码方式。就像一千个读者有一千个哈姆雷特一样,真实的哈姆雷特就在那里。

乱码是显示在屏幕上才被认为是乱码,也就是说乱码取决于人的感官,乱码只有人才知道,计算机不认为这是乱码。

文件编码

不管是文本还是图片或视频,在计算机存储上都是一视同仁,全都是字节流。但是从方便人们阅读的角度上还是分为文本文件和二进制文件。文本文件的可视形式就是文本字符,在存储和显示时有文本字符编解码的过程,可以直接用文本编辑器阅读。除文本文件以外就是二进制文件,不同类型的二进制文件都有相应的结构标准,例如javaclass文件,前四个字节代表文件类型,后边两个字节代表大版本号,再后边两个字节代表小版本号。具体哪些字节代表什么意思,值是float类型还是int类型,都有一定的标准,所以需要特定的软件按照标准去读取解析。

在不同的编程语言中,往往提供不同的类对文本文件和二进制文件进行读写。最常用的就是文本文件的读写例如C#中有StreamReaderStreamWriterJava中有BufferedReaderBufferedWriter。还有二进制文件的读写例如C#中有BinaryReaderBinaryWriterJava中有DataInputStreamDataOutputStream。当然读写二进制文件的类也可以读写文本文件,因为文本文件和二进制文件的存储在本质上是没有区别的,都是二进制。只不过专门读写文本文件的类封装的更好,读写文本文件更方便。