跳到主要内容

字符编码

基本概念

在计算机中表示文字,图片,符号等内容都需要进行编码,因为它们最终都是以二进制形式存储在计算机中。下面是一些概念:

  • 字符(Chracter):这个很容易理解,就是字母、数字、运算符号、标点符号和其他符号,是组成文本的最小单位

  • 字符集(Chracter Set):就是字符的集合嘛,它一般是分国家,语言或者地区的

  • 编码字符集(Coded Character Set):为每个字符分配一个唯一的整数编号,这些整数的集合就是编码字符集

  • 码点(Code point):在字符集中任意一个整数值都叫码点,例如 Unicode 码点形式就是 U+XXXX

  • 码空间(Code space):代码点整数的范围

  • 码单元(Code Unit):是码点的最小二进制序列表示形式;一个码点由一系列代码单元表示,而一个码点需要的码单元数取决于编码,例如 UTF-8 中,代码点可以映射到一个,两个,三个或四个代码单元的序列

字符集

ASCII

ASCII,发音艾斯 ki,是American Standard Code for Information Interchange(美国标准信息交换码)的简写,是电子通信的字符编码标准。大多数现代字符编码方案都基于 ASCII,并支持了更多的字符。

ASCII 码的标准制定是由美国标准协会 ASA(也就是现在的 ANSI,美国国家标准协会,类似于中国国家标准化管理委员会)手底下的一个 X3.2 小组委员会来完成,第一版标准是在 1963 年完成并发布,当时只包含大写字母和数字,在 1967 年经过重大修订,添加了小写字母和一些打印机的控制字符,什么回车啊,退格啊等等,至此就包含了所有在英文中可能用到的 128 个字符;这 128 个字符里面 95 个编码字符是可打印的:这些字符包括数字 0 到 9,小写字母 a 到 z,大写字母 A 到 Z 和标点符号;还有 33 种用在电传打字机里面,里面的大部分都过时了,但是部分也被现在的键盘采用了,例如回车,换行,制表符。

为了最小化数据传输的成本,X3.2 小组委员会决定使用 7 位二进制进行编码, 如下表所示,这些字符其实就是从十进制的 0 到 127,0 是 NULL,A 是 1000001,10 进制就是 65,那么 B 十进制就是 66,就是依次往下数的。

控制字符

二进制十进制十六进制缩写Unicode 表示法脱出字符 表示法名称/意义
0000 0000000NUL^@空字符Null
0000 0001101SOH^A标题开始
0000 0010202STX^B本文开始
0000 0011303ETX^C本文结束
0000 0100404EOT^D传输结束
0000 0101505ENQ^E请求
0000 0110606ACK^F确认回应
0000 0111707BEL^G响铃
0000 1000808BS^H退格
0000 1001909HT^I水平定位符号
0000 1010100ALF^J换行键
0000 1011110BVT^K垂直定位符号
0000 1100120CFF^L换页键
0000 1101130DCR^MCR (字符)
0000 1110140ESO^N取消变换(Shift out)
0000 1111150FSI^O启用变换(Shift in)
0001 00001610DLE^P跳出数据通讯
0001 00011711DC1^Q设备控制一(XON 激活软件速度控制
0001 00101812DC2^R设备控制
0001 00111913DC3^S设备控制三(XOFF 停用软件速度控制
0001 01002014DC4^T设备控制
0001 01012115NAK^U确认失败回应
0001 01102216SYN^V同步用暂停
0001 01112317ETB^W区块传输结束
0001 10002418CAN^X取消
0001 10012519EM^Y连线介质中断
0001 1010261ASUB^Z替换
0001 1011271BESC^[退出键
0001 1100281CFS^\ 文件分割符
0001 1101291DGS^]组群分隔符
0001 1110301ERS^^记录分隔符
0001 1111311FUS^_单元分隔符

可输出字符

二进制十进制十六进制图形
0010 00003220(space)
0010 00013321!
0010 00103422"
0010 00113523#
0010 01003624$
0010 01013725%
0010 01103826&
0010 01113927'
0010 10004028(
0010 10014129)
0010 1010422A*
0010 1011432B+
0010 1100442C,
0010 1101452D-
0010 1110462E.
0010 1111472F/
0011 000048300
0011 000149311
0011 001050322
0011 001151333
0011 010052344
0011 010153355
0011 011054366
0011 011155377
0011 100056388
0011 100157399
0011 1010583A:
0011 1011593B;
0011 1100603C<
0011 1101613D=
0011 1110623E>
0011 1111633F?
二进制十进制十六进制图形
0100 00006440@
0100 00016541A
0100 00106642B
0100 00116743C
0100 01006844D
0100 01016945E
0100 01107046F
0100 01117147G
0100 10007248H
0100 10017349I
0100 1010744AJ
0100 1011754BK
0100 1100764CL
0100 1101774DM
0100 1110784EN
0100 1111794FO
0101 00008050P
0101 00018151Q
0101 00108252R
0101 00118353S
0101 01008454T
0101 01018555U
0101 01108656V
0101 01118757W
0101 10008858X
0101 10018959Y
0101 1010905AZ
0101 1011915B[
0101 1100925C\
0101 1101935D]
0101 1110945E^
0101 1111955F_
二进制十进制十六进制图形
0110 00009660`
0110 00019761a
0110 00109862b
0110 00119963c
0110 010010064d
0110 010110165e
0110 011010266f
0110 011110367g
0110 100010468h
0110 100110569i
0110 10101066Aj
0110 10111076Bk
0110 11001086Cl
0110 11011096Dm
0110 11101106En
0110 11111116Fo
0111 000011270p
0111 000111371q
0111 001011472r
0111 001111573s
0111 010011674t
0111 010111775u
0111 011011876v
0111 011111977w
0111 100012078x
0111 100112179y
0111 10101227Az
0111 10111237B{
0111 11001247C|
0111 11011257D}
0111 11101267E~

Unicode

Unicode 字符集查询

参考 —— Unicode 及编码方式概述

参考 —— 关于 Unicode 的常见问题

Unicode 是由 Unicode 联盟(该联盟是一个非盈利组织,负责协调 Unicode 的发展,成员包括一些科技公司巨头,如苹果,微软,谷歌等,还有一些国家)发布的字符编码标准,该标准中定义了一个全球统一的通用字符集——Unicode 字符集。不像 ASCII,Unicode 旨在旨在收集全球所有的字符,使得所有国家所有语言之间都可以通用一套编码规则,这样在互联网全球化的发展进程中降低信息传输的成本和难度。Unicode 对应的国际标准是 ISO / IEC 10646 : 2020,它们之间共享字符库。

而 Unicode 字符集是兼容 ASCII 字符集的,前 128 个字符的码点和 ASCII 字符集的码点(十进制整数值)是完全一样,例如大写字母 A 在 ASCII 字符表中显示是 65,而 Unicode 码点值是U+0041,这个十六进制形式转十进制就是 65。

只是 Unicode 就是表示码点的形式是 16 进制形式U+0000U+10FFFF,从U+0000U+007F也就对应 ASCII 字符表的 0~127

image-20200723145615794

在 Unicode 标准中,字符是使用U+p+XXXX形式表示的,p 表示平面,它占据两个十六进制数字,从0010,对应二进制也就是0001000000000000,也就是从 0 到 16 共 17 个平面;后四位十六进制是在每个平面内叠加的编码值,每个平面内都能编码0000FFFF2^16这么多个字符,所以理论上 Unicode 一共可以编码 17*2^16 = 1,114,112 个字符,但是从 U + D800 到 U + DFFF 的代码点,用于在UTF-16 中编码代理对,由标准保留下来,所以剩下的编码个数就是1,112,064基本的多语言平面(英文为 Basic Multilingual Plane,简称 BMP)又称平面 0,收集了使用最广泛的字符,代码点从 U+0000 到 U+FFFF;其中兼容了 ASCII 字符集,从 U+0000 到 U+007F 范围内(十进制为 0~127)都是 ASCII 的字符

image-20200706213707998

这里可能就开始有点迷糊了,照理说 Unicode 不是已经给了每个字符的表示了吗,为什么还分什么 UTF-8,UTF-16 呢?首先,从概念上来看,Unicode 是系统的概念,它并不是一项具体的编码算法,它内部包含了编码字符集,字符编码方案等众多内容;其次,Unicode 指定了字符的码点形式,使用 U+XXXXX 的十六进制形式去表示每个字符的整数编号,而计算机中处理数据的方式是二进制,也就是说在 Unicode 码点的基础上,我们还要对码点进行二进制转换;从 U+0000 到最高 U+10FFFF,随着数字越来越大,转换成二进制序列的位数可以从一位二进制0到最多 24 位二进制那么多,所以我们还需要确定最后到底用几位二进制数来表示码点。

字符编码

信息转换

字符编码是给人看的,但是计算机只能识别01二进制数,所以需要进行编码转换。

ASCII 编码

image-20200722120846119

随着电传打字机的普及,越来越多国家根据自己国家的字符拓展了 ASCII 编码;几乎每个国家都需要经过改编的 ASCII 版本,因为 ASCII 仅适合美国和其他一些国家/地区的需求。随着 8 位,16 位,32 位等计算机的发展,ASCII 也被拓展到了 8 位二进制,又添加进了 128 个字符,这期间还出现了 ISO / IEC 8859 标准,以及在此标准上由微软拓展的 Windows-1252 编码字符集

image-20200706182540520

UTF-8

UTF-8 最初是 1993 年 1 月 25 日至 29 日在圣地亚哥USENIX会议上正式提出,UTF-8 是可变字节长度的编码方式,允许使用 1 到 4 个字节,也就是最低 8 位二进制,最高 32 位二进制编码每个码点。

UTF-8 编码的方式如下:

  • U+0000U+007F (十进制为 0~127)之间的码点,为了兼容 ASCII 的一字节编码,也使用单字节编码,即这部分使用 UTF-8 得到的编码值和 ASCII 是一样的,并且都以二进制0开头
  • 剩余的 Unicode 码点,如果使用 UTF-8 编码的第一个字节开头有多少个二进制1就表示使用的是多少个字节编码,剩余字节统一使用10开头;掌握这个规律后基本能轻易的从 Unicode 码点得出 UTF-8 编码的基本形式:
    • 处于U+0080U+07FF之间的字符,第一个字节必定以两个1开头,所以就是 2 个字节编码,即 16 位二进制形式110xxxxx10xxxxxx
    • 处于U+0800U+FFFF之间的字符,第一个字节必定以三个1开头,即 3 个字节编码,即 24 位二进制形式1110xxxx10xxxxxx10xxxxxx
    • 超过U+FFFF的字符,第一个字节必定以四个1开头,都使用 4 个字节编码11110xxx10xxxxxx10xxxxxx10xxxxxx

image-20200707174601988

以汉字为例,Unicode 码点是U+6211,属于U+0800U+FFFF之间,所以使用 3 个字节编码1110xxxx10xxxxxx10xxxxxx的形式,从的最后一个二进制数开始依次将每个二进制数填入上述形式中的空位x,填不满的位置补 0,即得到U+6211的 UTF-8 编码形式。

`U+6211`            =>          110 0010 0001 0001

//依次填入
1110xxxx 10xxxxxx 10xxxxxx
110 001000 010001

//得到
11100110 10001000 10010001

//转16进制
1110 0110 1000 1000 1001 0001
E 6 8 8 9 1

//UTF-8
E68891

UTF-16

了解了 UTF-8,基本能猜到 UTF-16 的码元是 16,可以使用 1 到 2 个 16 位二进制编码。

  • 从 U+0000 到 U+D7FF,U+E000 到 U+FFFF,也就是上文提到的基本多语言平面中的码点,使用单个 16 位二进制数来表示;所以 UTF-16 不兼容 ASCII 编码
  • 从 U+010000 到 U+10FFFF 的码点,使用 2 个 16 位二进制表示,这部分的算法比较复杂,有兴趣再研究一下

UCS

UCS,Universal Character Set,通用字符集,是国际标准化组织 ISO 定义的包含世界上所有语言字符的字符集,对应的国际标准号是 ISO10646,该标准和 Unicode 标准一起维护。

ISO 开始编写通用字符集是在 1989 年,1990 年发布了第一个版本 ISO10646,但是由一些科技公司组成的 Unicode 联盟以复杂性等理由拒绝 ISO 标准,并提出自己的 Unicode 标准,使用 16 位二进制表示每个字符,于是 ISO 和 Unicode 联盟统一了标准,使用 UCS-2 实现 Unicode 标准,UCS-2 使用定长的 16 位二进制数字来表示字符,但是这显然不够,于是经过修订,加入了可以使用 32 位二进制编码的 UTF-16,此外 ISO 还提出了 UCS-4 编码,对应于 Unicode 中的 UTF-32 编码。在 1991 年到 1995 年之间都是 Unicode1 的版本,使用 16 位二进制编码,1996 年 7 月开始,Unicode2.0 版本发布,支持了 32 位二进制编码。UCS-2 可以说是 Unicode2.0 版本之前的实现,从 2.0 版本开始称为 UTF-16

我们来看 JavaScript 是什么时候发明的,1995 年 12 月,而 UTF-16 是在 1996 年才发版,所以JavaScript 一直使用的是 UCS-2 编码字符,这也就是为什么有些书称 JavaScript 是使用 UTF-16 编码的字符集,因为 Unicode 标准不建议再使用 UCS-2 称呼 UTF-16。而 UCS-2 的缺点是很明显的,只能支持 16 位二进制的编码,对应 Unicode 码点从 U+0000 到 U+FFFF。高于这个范围的码点,也就是 32 位二进制编码的字符,JS 都不认识,它只能按照 16 位编码把这些字符拆开,这也就是为什么有些单个字符的length在 JS 里显示是 2,我们可以随便找一个大于 U+FFFF 的码点测试一下。

//𝑒,数学常数,自然对数lnX的底数,码点U+1D452
console.log("𝑒".length); // 2

并且 JS 中字符串的操作 charAtcharCodeAt等方法针对的也都是 UTF16 编码。从 ES6 开始,增强了对 Unicode 字符的表示,允许使用\u....的形式通过 Unicode 码点表示字符

console.log("\u1D452".length);              //和上面𝑒的结果一样