字符集和编码
在计算机内部,所有信息都是由一串位来表示,代表的数据对象取决于它所处的上下文,可能是数字、字符串或其他。当为字符串时,相信很多人遇到过乱码问题,产生乱码的根本原因和字符集及其编码有关。
- 字符集(Character set):各种文字和符号的总称,如ASCII字符集、Unicode字符集。
- 字符编码(Character encoding):指定位模式与字符符号的映射关系,如
0x61表示字符'a'
。
ASCII
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是英文字符的一种编码方案。ASCII 字符集有两类字符:控制字符和可打印字符。
标准ASCII码使用1字节(8-bit)中的 7 位表示,总共 2^7=128 种字符(0x00~0x7F),最高位为0。其中前32个代码和最后一个为控制字符;0x20~0x7E 为可打印字符,代表英文字母、数字、标点符号以及一些杂项符号,共95个。
ASCII码已经能够满足现代英语的信息交换,但它们不能处理大部分欧洲国家的语言,因为他们还使用其他拉丁字母的字符(如带音标的),此外不同国家用到的字符也不同,为了解决这个问题,ISO创建了ISO 2022
标准,规范了7位和8位编码的多个字符集中的字符,以及大字符集的表示,后来被应用于创建Latin-1,ISO 8859-1
标准。
ISO 8859
ISO 8859全称为ISO/IEC 8859,是一系列8-bit编码
字符集的标准,它通过扩展ASCII码,利用 8-bit 中的第8位添加了另96个可打印字符的位置,其编码区间是 0xA0~0xFF。ISO 8859定义了15个字符集,其中常见的是ISO 8859-1,西欧语言字符集
。
这里不得不提一下ISO-8859-1
,它是IANA(互联网号码分配机构)根据ISO/IEC 8859-1标准制定的8-bit编码
字符集,别名latin1,编码范围是0x00~0xFF,各区间字符情况如下:
- 0x00~0x7F,依据ASCII字符集
- 0x80~0x9F,ISO/IEC 2022中的C1控制字符
- 0xA0~0xFF,依据ISO 8859-1,西欧语言字符集
ISO-8859-x系列编码其实就是ISO 2022标准中各组件的特定组合。
GB2312/GBK
单字节对于CJK字符来说显然不够用,ISO/IEC 2022标准在ISO 646标准(其前身就是ASCII)的基础上,规范了大字符集的表示。7-bit编码除了空格和33个控制字符外,总共能定义94个图形字符,也就是说2个7-bit编码可表示94x94个字符,三字节能表示更多。
GB2312
就是我国根据ISO/IEC 2022标准,制定的简体中文编码标准字符集。GB2312中的字符就排列在94x94的方阵中,总共有8836个码位,行称为区,列称为位,利用区号和位号可以确定一个字符,称为字符的区位码(码点),这里的码点使用的是十进制。
- 01-09,包括标点符号和其他特殊字符
- 16-55,一级汉字,按拼音排列。(3755个字符)
- 56-87,二级汉字,按部首和笔画排列。(3008个字符)
区 10-15 和 88-94 未分配。
GB2312字符集通常使用1字节或2字节的8位EUC-CN(一个多字节字符编码)编码,以便兼容ASCII码,其他汉字符号使用2-Byte
表示。编码规则:第一个字节称高位字节,范围是0xA1~0xF7(01-87区号加上0xA0);第二个字节称低位字节,范围是0xA1~0xFE(01-94位号加上0xA0)。比如“创”的码点是2020(0x1414),那么编码就是0xB4B4。之所以要加上0xA0,我觉得是为了区分ASCII编码。
GBK
是GB2312字符集的扩展,扩展了GB2312未分配的码点,放弃了ISO 2022规定的控制字符块,扩展了字节的范围,第一个字节从A1~FE扩展到了81~FE(94+32=126),第二个字节40~FE(191),总共24,066个位置。
Unicode
Unicode 给每个字符提供了一个唯一的数字,称作码点(code points)
,通常使用U+十六进制数
来引用码点,比如U+0041
表示字符 ‘A’。Unicode 定义了1,114,112个码点的代码空间,范围从 U+0000 到 U+10FFFF,代码空间又分为17个平面(0-16),每个平面有65,536个码点。其中0号平面叫做基本多语言平面(Basic Multilingual Plane, BMP),BMP范围是U+0000~U+FFFF,使用4个hex数表示码点。BMP之外的代码点,使用5或6个hex数表示。
Unicode可以通过不同的编码方案来实现,最常用的是UTF-8
和UTF-16
。
UTF-16
UTF-16 以16-bit
为代码单元(code units,2-Byte)对Unicode码点编码,BMP中的码点编码与UTF-16码元相等,比如码点U+2020,UTF-16编码为0x2020。其他补充平面使用代理对(surrogate pairs),即两个码元表示一个码点。
在BMP中,Unicode标准保留U+D800~U+DFFF这部分码点用于高和低位代理的UTF-16编码,其中U+D800~U+DBFF为高位代理,U+DC00~U+DFFF为低位代理。其他补充平面编码规则为:
- 码点减去0x10000,保留0x000000..0x0FFFFF范围内20位数
- 用高10位bit加上0xD800给出第一个16位代码单元或高位代码
- 用低10位bit加上0xDC00给出第二个16位代码单元或低位代码
计算机中大多以字节为单位,那么一个16位码元的序列,就需要解决字节序的问题。UTF-16允许在实际编码值之前加一个字节序标记 (Byte Order Mark ,BOM)
,值为0xFEFF
表示big-endian
,值为0xFFFE
表示little-endian
。Unicode 默认是 big-endian。
举个例子,求码点U+10393的编码:
- 0x10393-0x10000=0x393,那么高10bit为0x0,低10bit为0x393
- 则高位代码为0x0+0xD800=0xD800,低位代码为0x393+0xDC00=0xDF93
- 按照大端序最终UTF-16的编码为
FEFFD800DF93
UTF-8
UTF-8 是一种可变长度的编码方案,以8-bit
为码元,是互联网广泛使用的一种Unicode实现方式,因为它字节少流量小。UTF-8使用1~4个字节
对Unicode代码空间中的有效码点编码。其编码规则是,前128个字符(US-ASCII)使用一个字节编码,与ASCII相对应;接下来1920个字符使用两个字节编码;BMP其余字符需要三个字节,其中包含了大多数中文;其他平面中的字符使用四个字节。
下表是UTF-8编码规则,其中x
由码点的位替换:
字节|码位| 码点范围 | 编码方式
----+-------------------------+---------------------------------------
1 | 7 | U+0000 ~ U+007F | 0xxxxxxx
2 | 11 | U+0080 ~ U+07FF | 110xxxxx 10xxxxxx
3 | 16 | U+0800 ~ U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx
4 | 21 | U+10000 ~ U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
UTF-8使用8-bit代码单元,避免了字节序的问题。求U+10393的UTF-8编码:
- 0x10393 > 0x10000据上表,使用4字节编码
- 将0x10393转成二进制0001 0000 0011 1001 0011,从右到右依次替换编码中的x
- 替换结果为11110000 10010000 10001110 10010011,即 F0908E93
FAQ
1. Unicode和UTF-8是什么关系?
Unicode是字符集,而UTF-8是Unicode的一种编码方案。
2. UTF-8的BOM?
这个是微软的锅,它给UTF-8加上的,一般还是不要加BOM了。
3. 乱码出现的原因?
一般是使用了错误的字符集。避免乱码的最好方法就是统一编码(最好都是UTF-8),或者是知道字符的原始编码方式。