字符:各种文字和符号的总称,包括各个国家的文字,标点符号,图形符号,数字等
字符集:字符集是多个符号的集合,每个字符集包含的字符个数不同
字符编码:字符集只是规定了有哪些字符,而最终决定采用哪些字符,每个字符用多少字节表示等问题,是由编码来决定的。因为计算机要准确的处理各种字符集文字,就需要对字符进行编码,以便于计算机的识别和存储。
ANSI在中国大陆称为GBK(以前是GB2312),最常用的是GBK和UTF-8无BOM编码格式。UTF-8、UCS-2 Big Endian(Unicode 大端)、USC-2 Little Endian(Unicode 小端)为有BOM头
BOM头:文本文件中开始的几个并不表示任何字符的字节,用二进制编辑器展示如下
UTF8的BOM头为0xEF 0xBB 0xBF
Unicode大端模式为0xFE 0xFF
Unicode小端模式为0xFF 0xFE
ASCII码
计算机只能识别数字,本质是通过二进制来表示十进制的所有数字,以此来换算数据进行计算。
如果使用数字来表示文本,就需要将数字和文本一一对应,且所有计算机都必须同一标准,否则就会造成同一段数字在不同计算机上显示的字符不同。至此,美国ANSI指定了一个标准,规定了常用字符的集合以及每个字符对应的编号,这就是ASCII字符集的诞生
当时的编解码系统:查表
编码步骤:字符→查表→对应的编码→存储设备
-
在ASCII字符集表中依次找到字符对应的字节
-
然后将对应的字节写入存储设备
解码步骤:
-
在ASCII字符编码表中依次找到字节编码对应的字符
-
然后将对应的字符输出到输出设备。
-
0~31及127(共 33 个):是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)
-
32~126(共 95 个):是字符(32 是空格),其中 48~57 为 0 到 9 十个阿拉伯数字。
-
65~90 :为 26 个大写英文字母,97~122 号为 26 个小写英文字母,其余为一些标点符号、运算符号等
-
- 后 128 个称为扩展 ASCII 码:许多基于 x86 的系统都支持使用扩展(或“高”)ASCII。扩展ASCII 码允许将每个字符的第 8 位用于确定附加的 128 个特殊符号字符、外来语字母和图形符号。
OEM字符集
一个字节能够表示的数字有256个,但是ASCII字符集只表示了128个(0x00~0x7F),但是当时对于OEM没有明确的规定,导致各种计算机的后128位表示的字符也各种各样,这就导致了人们无法跨机器交流文档。
OEM就是对ASCII码的扩展,诞生的各种OEM字符集最终生成了MBCS多字节字符集
MBCS多字节字符集
ASCII和OEM都是单字节编码,即一个字节翻译一个字符,对于大部分使用拉丁语系得国家,都足够了,但是对于大部分亚洲国家,256个字符远远不够,例如中国。
既要和ASCII保持兼容,又要能够支持自己国家的字符,这就出现了多字节编码方式,相应得字符集就称为多字节字符集(Muilti-Bytes Charecter Set)
最常见双字节字符集:
-
中文字符集GB2312:包含了所有简体字符以及一部分其他字符
-
中文扩展字符集GBK:在GB2312的基础上,添加了繁体字符和其他非简体字符
windows系统采用936代码页对GBK字符集进行编解码
-
在解析字节流的时候,如果遇到字节最高位为0,就用936代码页中的第一张码表进行解码(和单字节字符集解码方式一样)
-
如果遇到字节最高位为1,就表示需要两个字节才能对应一个字符
举个例子:
GB2312编码写如下内容:我叫ABC
11001110 11010010 10111101 11010000 01000001 01000002 01000003
全角和半角:
全角占用两个标准字符(两个字节)的位置,半角为一个标准字符(字节)
三大标准
ANSI标准、国家标准、ISO标准
ANSI编码一般指代平台的默认编码,例如英文系统使用的就是ISO标准的ISO-8859-1,中文系统是GBK,,统称为ANSI编码。
国家标准一般是各个国家根据自身的实际情况规定的编码格式,例如:中国的GBK、GB2312和GB18030
ISO标准字符编码由ISO组织整理
Unicode字符集
根据上述的编码标准和编码字符集,已经能够满足在不同机器上查阅不同语言的文档
但是也存在问题:如果一份文档中含有不同国家不同语言的字符,我们无法在一份文档中显示所有字符
-
Unicode就是全人类达成共识而生成的一个巨大的字符集,并为每个字符进行同一编号,分配唯一的字符码
-
Unicode前128位和ASCII码一致,用于兼容ASCII码,Unicode的总范围是
\x0
到\x10FFFF
共1,114,112个码位 -
2048个用于编码代理(UTF-16),66个非字符码位(例如BOM),137,468个预留给私人使用,最终剩余974,530用于普通字符分配
-
码位的最大值为10FFFF,对应的二进制有21位(1 0000 1111 1111 1111 1111),刚好以 2 16 2^{16} 216可以分为17个层面
-
其中第0个层面已经包含了世界上所有用到的字符,其他层面一些表示一些远古时期的文字,还有一些用于留作扩展,目前的Unicode字符集中尚有大量字符空间未使用。
平面编号 | 码位范围(十六进制) | 名称简写 | 名称 |
---|---|---|---|
Plane 0 | 0000–FFFF | BMP | 基础多语言平面(Basic Multilingual Plane) |
Plane 1 | 10000–1FFFF | SMP | 补充多语言平面(Supplementary Multilingual Plane) |
Plane 2 | 20000–2FFFF | SIP | 补充表意语言平面(Supplementary Ideographic Plane) |
Plane 3 | 30000–3FFFF | TIP | 第三表意语言平面(Tertiary Ideographic Plane) |
Planes 4–13 | 40000–DFFFF | - (未分配) | - (未分配) |
Plane 14 | E0000–EFFFF | SSP | 补充特殊用途平面(Supplementary Special-purpose Plane) |
Planes 15–16 | F0000–10FFFF | SPUA-A/B | 补充私有使用区平面(Supplementary Private Use Area planes) |
什么是私有平面?用于给个人做编码扩展,Unicode不指定字符编码,例如游戏中的一个字符代表一种操作,就是用的私有平面 。
上述的是以平面进行分类,在Unicode中还有一个概念:在逻辑上属于一类的字符称为Block
block | 码位范围(十六进制) | 类型 |
---|---|---|
C0 Controls and Basic Latin | \x0000-\x007F | ASCII码的128个字符 |
CJK Unified Ideographs | \x4E00-\x9FFC | 包含大部分的中日韩文字 |
Halfwidth and Fullwidth Forms | \xFF00-\xFFEF | 用于英文字母/数字/日文/个别符号等一些字符的全角-半角相互转换 |
Miscellaneous Symbols and Pictographs | \x1F300-\x1F5FF | 包含大部分emoji表情 |
Supplemental Symbols and Pictographs | \x1F900-\x1F9FF | 包含大部分emoji表情 |
General Punctuation | \x2000-\x206F | 包含一些符号以及一些特殊的分隔符、连接符、空格符等 |
编码系统的变化
在Unicode出现之前,所有的字符集都是和具体的编码方案绑定的(字符集≈编码方式),这样的方式通常只需要简单的查表就可以实现。
Unicode在最初是无法使用的,是一个十六进制的,计算机只能识别二进制数据
怎么才能区分Unicode和ASCII
计算机怎么才知道两个字节表示一个字符,而不是分别表示两个符号呢
如果使用GBK这种双字节编码方式,最高位用0或1表示一个字节或两个字节,就会少了很多值无法表示
这样就诞生了UTF-8、UTF-16、UTF-32
UTF-8
UTF-8是一种变长的编码方式,码位大于\xFFFF
的字符,使用4字节存储,小于等于\xFFFF
大于\x07FF
的使用3字节,小于等于\x07FF
大于\x007F
的使用2字节,小于等于\x007F
使用1字节。
UTF-8直接兼容ASCII码
在UTF8中,
-
如果字节序列以0开头,代表当前字节本身表示了一个字符。
-
如果为10开头,则代表当前字节为多字节字符中的一个字节。
-
如果当前字符以11开头,则前面1的个数,代表当前字符所使用的字节数,2个1代表使用两个字节表示一个字符,3个1代表使用3个字节表示一个字符。
范围 | 码位(二进制) | 第1个字节 | 第2个字节 | 第3个字节 | 第4个字节 |
---|---|---|---|---|---|
\x0000 … \x007F(7位) | 00000000 0xxxxxxx | 0xxxxxxx | - | - | - |
\x0080 … \x07FF(11位) | 00000yyy yyxxxxxx | 110yyyyy | 10xxxxxx | - | - |
\x0800 … \xFFFF | zzzzyyyy yyxxxxxx | 1110zzzz | 10yyyyyy | 10xxxxxx | - |
\x10000 … \x10FFFF | 000uuuuu zzzzyyyy yyxxxxxx | 11110uuu | 10uuzzzz | 10yyyyyy | 10xxxxxx |
UTF-16
变长编码格式,按平面区分,位于第一平面中的字符(\x0000
…\xD7FF
和\xE000
…\xFFFF
),使用16位(2字节)存储,使用和码位相同的值。位于其他平面的字符(\x10000
…\x10FFFF
),通过高位和低位代理使用32位(4字节)表示。
UTF-32
是一种定长编码格式,使用32位(4字节)表示Unicode中的一个码位。由于Unicode的码位实际只用了21位,所以多余部分前导0。例如字符小写字母a,对应码位为\x61
,存储的字节序列为:\x00000061
因为各个系统之间的字节顺序不同,所以在传输和交换Unicode文本时,要告诉对方当前是以什么顺序保存的,从而接收方才能有效的进行解析。
字节序列标记(Byte order mark,简写BOM),特指\xFEFF
字符。在文本的开头,添加\xFEFF
字符,用于标识当前文本的字节顺序。
-
对于UTF8编码格式,该字符会被保存为
\xEFBBBF
-
对于UTF16 BE编码格式,该字符会被保存为
\xFEFF
-
对于UTF16 LE编码格式,该字符会被保存为
\xFFFE
-
对于UTF32 BE编码格式,该字符会被保存为
\x0000FEFF
-
对于UTF32 LE编码格式,该字符会被保存为
\xFFFE0000
解析程序通过判断BOM即可确定接下来的文本所使用的编码格式以及字节顺序
当使用UTF8格式保存文本时,Unicode标准建议,如果原文本没有BOM,则不要添加BOM。因为:
-
UTF8是单字节存储的,不存在字节顺序问题。
-
解析器会默认使用UTF8解析文本。
-
因为ASCII和UTF8是一一对应的,如果不添加BOM,则ASCII和Unicode可以相互兼容,如果加上了BOM,就打破了相互兼容。
-
而对于UTF16和UTF32,要添加BOM,不然在解析的出的文本可能就是乱码,因为解析器在对字节顺序的推算上,并不能保证完全可靠。
BOM可以省略,不是必须的,因为:
-
在某些场景下已经预设了编码格式或字节顺序,例如W3C的HTML5规范中,如果指定charset为utf-8,则会默认按照utf-8解析,而如果文件流指定了BOM,则会优先使用BOM指定的编码格式和字节顺序。
-
当BOM被省略时,大部分解析器都会对文本流进行推算,推算出编码格式和字节顺序,但是这个推算并不是绝对可靠的。
字节顺序标记(Byte order mark),指预定义的,放置在文本流开头的,一段特殊的字节序列,用于标记当前文本使用的哪种编码格式(UTF32/UTF16/UTF8)。具体规则如下:
编码格式 | 文本流开头的字节序列 |
---|---|
UTF-8 | EF BB BF |
UTF-16 (BE) | FE FF |
UTF-16 (LE) | FF FE |
UTF-32 (BE) | 00 00 FE FF |
UTF-32 (LE) | FF FE 00 00 |
例如Windows的记事本应用,将文本保存为UTF8格式时,会在文本内容的开头添加\xEF
,\xBB
,\BF
3个字节。记事本应用在读取一个文本文件的时候,发现前三个字节为\xEF
,\xBB
,\BF
,则认为接下来的字节流通过UTF8形式解析。
字节顺序(endianness),这里特指当保存一个数字类型数据时,存储的字节序列的顺序。分为大端序(big-endian,简写BE)和小端序(little-endian,简写LE)。
假设当前要将一个16位的整型数字\x0A0B
指向内存地址\x100。
对于大端序的CPU,随着内存地址的增加,认为其存储的值的重要性是递减的,所以大端序的CPU会在\x100的位置上存储\x0A
,在\x101的位置上存储\x0B
。
对于小端序的CPU,随着内存地址的增加,认为其存储的值的重要性是递增的,所以小端序的CPU会在\x100的位置上存储\x0B
,在\x101的位置上存储\x0A
。
所以反过来,假设现在在内存中,地址\x100的地方存储了\xAA
,在\x101的地方存储了\xBB
,假设有一个int16变量指向\x100,对于大端序CPU会认为该变量的值为\xAABB
,对于小端序CPU会认为该变量的值为\xBBAA
。
常见的字符编码方式
常见 CharSet 有:GBK、GB2312、US-ASCII、ISO-8859-1、UTF-8、UTF-16BE、UTF-16LE、UTF-16