关于字符集和字符编码的关系,ASCII、GBK、Unicode,utf-8、shitf-jis、GB2312

2025/02/24 encode decode Charset Character Encoding 共 2236 字,约 7 分钟
Mikatsuki

引言

最近在工作过程中遇到了客户需求,想要自定义输出文件的编码格式,比如区分utf-8和shift-jis。在输入shift-jis文件的时候,经过一系列处理后再导出shitf-jis时竟然出现了乱码,结果是因为在读取文件的时候采用了cp932(shift-jis扩展编码),所以在解码的时候因为多出的无法映射的字符导致了乱码,可以为什么utf-8没有问题呢,因为utf-8是字符集unicode的编码方式,unicode包含了所有字符,所以编解码是没有问题的。

字符编解码也是一个老生长谈的问题了,这里整理梳理一下他们之间的关系。

字符集 CharSet

字符集是一个系统支持的所有抽象的字符的集合,字符包括国家文字,
标点符号,图形符号,数字等。

所谓字符集也就是把人类的文字转换给计算机识别的字符的映射图,因为计算机中都是用二进制数据存储数据,所以需要这样一个转换功能把人类用的符号用二进制数据表示。由于计算机是美国人发明的,所以最开始只有大小写英文字母及常见符号的转换字符集,也就是所谓的ASCII码,这个字符集只有127个字符,当后来计算机慢慢普及起来,因为各个国家的文字并不相同,ASCII码也就不足以表示所有的字符了,所以出现了各式各样的字符集,当然最后出现了一个能够表示全世界所有字符的字符集也就是unicode。

把人类符号存储进入计算机,即编码,如"A"用"01000001"表示
把计算机存储的二进制数转换成人类符号显示,即解码,如把"01000001"转换成"A"

Unicode的问题以及字符编码

需要注意的是,字符集仅仅是给每个字符分配了独一无二的二进制码,并没有指定它到底需要怎么存储,例如在ASCII中所有字符都只需要一个字节存储,但是在unicode中例如汉字则通常需要2个或者更多字节去表示。在这里就有两个主要问题需要解决:

  1. 如何让计算机知道一个字符需要多少个字节去表示呢?
  2. 在使用大量英语的场景,仅需要ACSII表就可以表示全,如果为了配合其他字符而导致需要两到三个字节空间去存储一个字节的有效内容,也会导致很大的空间浪费。

为了解决上述问题,UTF-8就是最为普遍的一种Unicode实现方式,注意,utf8是一种Unicode的编码方式,这个概念很重要,初次之外还有utf16 utf32等等编码方式。

utf-8(8位变长编码)的规则:

  1. 对于单字节符号:字节的第一位设为0,后7位为这个符号的unicode编码。
  2. 对于多字节符号:第一个字节的前n位设置为1,第n+1位设置为0,后面的字节的前两位全部设置为10。剩下的没有作为符号位的部分,全部作为unicode码。

我们来用一些例子来解释上面的规则,首先是规则1,因为我们知道ASCII码表仅127个字符,除去第一位设置为0作为标记,剩下的7位刚好可以表示127个字符,所以对于英文字符,utf-8编码和ASCII码是一样的。

例如英文字母a,utf-8表示为0x61,即用一个字节表示十进制数97,十六进制数61,它的Unicode是U+0061,二进制数是01100001。这也说明了utf-8是兼容ASCII的。

再来看下规则2,例如汉字严,Unicode 是4E25(100111000100101),4E25的utf-8编码需要三个字节,这里需要查询一下16进制数和utf-8字节数的范围关系,三字节的utf-8格式是1110xxxx 10xxxxxx 10xxxxxx,“1110”表示了接下来三个字节用作表示一个字符,后面的“10”也是标志头。 接下来把严的二进制数从后往前依次填充进入格式中的xxxx,多出的位补0,也就得到了严的 UTF-8 编码是11100100 10111000 10100101(从后往前依次填入100101 111000 0100),这个数字转换成十六进制就是E4B8A5。

可以看到严的 Unicode码 是4E25,UTF-8 编码是E4B8A5

utf-8使用可变长的方式解决了字符空间浪费的问题,也解决了计算机识别字节位数的问题。当然也有很多其他的编码方式,例如UCS-16编码,用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)

编码转换

上述的例子我们也可以看到,字符集是一个字符表示的全集,而字符编码是用来表示这个字符集里字符的方式,当然还有更多的字符集以及对应的编码方式,例如GBK字符集对应的GB2312编码,JIS X 0208字符集对应的shift-jis编码,通常我们在选择编码的时候也默认选择了它对应的字符集,但是他们并非一一对应,一个字符集可以由多个字符编码方式,例如unicode对应的utf-8或者utf-16,只是不同的编码方式会导致不同的存储二进制数。

另外相同的字符,在不同的字符集可能被定义成不同的二进制数,如"汉"的Unicode值与gbk就是不一样的,假设Unicode为a040,GBK为b030。如果要进行编码转换,以UTF-8为例,UTF-8码完全只针对Unicode来组织的,如果GBK要转UTF-8必须先转Unicode码,再转UTF-8。

后记

回到一开始工作遇到的编码转换乱码,就是因为一开始使用cp932进行了编码,一部分的表示的二进制数是shift-jis所对应的字符集里面不存在的,所以也就找不到对应的字符导致了乱码。unicode是比较后面才出现的字符集,各种历史原因导致以前的字符集还在使用没有迁移,例如嵌入式使用更小型的字符集更合适。

文档信息

Search

    Table of Contents