人类交流使用 ABC 等字符,但计算机只认识 01。因此,就需要将人类的字符,转换成计算机认识的二进制编码。这个过程就是字符编码。

ASCII

最简单、常用的字符编码就是 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码),它将美国人最常用的 26 个英文字符的大小写和常用的标点符号,编码成 0127 的数字。例如 A 映射成 65 (0x41),这样计算机中就可以用 0100 0001 这组二进制数据,来表示字母 A 了。

ASCII 编码的字符可以分成两类:

  • 控制字符:0 - 31127 (0x00 - 0x1F0x7F)
  • 可显示字符:32 - 126 (0x20 - 0x7E)

具体字符表可以参考:ASCII - 维基百科,自由的百科全书

Unicode

ASCII 只编码了美国常用的 128 个字符。显然不足以满足世界上这么多国家、这么多语言的字符使用。于是各个国家和地区,就都开始对自己需要的字符设计其他编码方案。例如,中国有自己的 GB2312,不够用了之后又扩展了 GBK,还是不够用,又有了 GB18030。欧洲有一系列的 ISO-8859 编码。这样各国人民就都可以在计算机上处理自己的语言文字了。

但每种编码方案,都只考虑了自己用到的字符,没办法跨服交流。如果一篇文档里,同时使用了多种语言的字符,总不能分别指定哪个字符使用了那种编码方式。

如果能统一给世界上的所有字符分配编码,就可以解决跨服交流的问题了,Unicode 就是来干这个事情的。

Unicode 统一编码了世界上大部分的字符,例如将 A 编码成 0x00A1,将 编码成 0x4E2D,将 α 编码成 0x03B1。这样,中国人、美国人、欧洲人,就可以使用同一种编码方式交流了。

一个 Unicode 字符可以使用 U+ 和 4 到 6 个十六进制数字来表示。例如 U+0041 表示字符 AU+4E2D 表示字符 U+03B1 表示字符 α

Unicode 最初编码的范围是 0x00000xFFFF,也就是两个字节,最多 65536 (2^16) 个字符。但随着编码的字符越来越多,两个字节的编码空间已经不够用,因此又引入了 16 个辅助平面,每个辅助平面同样最多包含 65536 个字符。原来的编码范围称为基本平面,也叫第 0 平面。

各平面的字符范围和名称如下表:

平面 字符范围 名称
0 号平面 U+0000 - U+FFFF 基本多文种平面 (Basic Multilingual Plane, BMP)
1 号平面 U+10000 - U+1FFFF 多文种补充平面 (Supplementary Multilingual Plane, SMP)
2 号平面 U+20000 - U+2FFFF 表意文字补充平面 (Supplementary Ideographic Plane, SIP)
3 号平面 U+30000 - U+3FFFF 表意文字第三平面 (Tertiary Ideographic Plane, TIP)
14 号平面 U+E0000 - U+EFFFF 特别用途补充平面
15 号平面 U+F0000 - U+FFFFF 保留作为私人使用区(A 区)(Private Use Area-A, PUA-A)
16 号平面 U+100000 - U+10FFFF 保留作为私人使用区(B 区)(Private Use Area-B, PUA-B)

每个平面内还会进一步划分成不同的区段。每个平面和区段具体说明参考 Unicode字符平面映射 - 维基百科,自由的百科全书;汉字相关的区段说明参考 中日韩统一表意文字 - 维基百科,自由的百科全书。Unicode 所有字符按平面和区段查找,可以参考 Roadmaps to Unicode;按区域和语言查找可以参考 Unicode Character Code Charts

字符编码的基本概念

“字符编码”是一个模糊、笼统的概念,为了进一步说明字符编码的过程,需要将其拆解为一些更加明确的概念:

字符 (Character)

人类使用的字符。例如:

  • A
  • 等。

编码字符集 (Coded Character Set, CCS)

把一些字符的集合 (Character Set) 中的每个字符 (Character),映射成一个编号或坐标。例如:

  • 在 ASCII 中,把 A 编号为 65 (0x41);
  • 在 Unicode 中,把 编号为 0x4E2D
  • 在 GB2312 中,把 映射到第 54 区第 0 位。

这个映射的编号或坐标,叫做 Code Point。

Unicode 就是一个 CCS。

字符编码表 (Character Encoding Form, CEF)

把 Code Point 转换成特定长度的整型值的序列。这个特定长度的整型值叫做 Code Unit。例如:

  • 在 ASCII 中,0x41 这个 Code Point 会被转换成 0x41 这个 Code Unit;
  • 在 UTF-8 中,0x4E2D 这个 Code Point 会被转换成 0xE4 B8 AD 这三个 Code Unit 的序列。

我们常用的 UTF-8、UTF-16 等,就是 CEF。

字符编码方案 (Character Encoding Scheme, CES)

把 Code Unit 序列转换成字节序列(也就是最终编码后的二进制数据,供计算机使用)。例如 :

  • 0x0041 这个 Code Unit,使用大端序会转换成 0x00 41 两个字节;
  • 使用小端序会转换成 0x41 00 两个字节。

UTF-16 BE、UTF-32 LE 等,就是 CES。


这些概念间的关系如下:

@startuml

hide empty description

state Character
state CodePoint
state CodeUnits
state Bytes Character : A
CodePoint : 0x41
CodeUnits : 0x0041
Bytes : 0x41 0x00 Character -right-> CodePoint : CCS (Unicode)
CodePoint -right-> CodeUnits : CEF (UTF-16)
CodeUnits -right-> Bytes : CES (UTF-16 LE) @enduml

因此,我们说 ASCII 是“字符编码”时,“字符编码”指的是上面从 Character 到字节数组的整个过程。因为 ASCII 足够简单,中间的 Code Point 到 Code Unit,再到字节数组,都是一样的,没必要拆开说。

而我们说 Unicode 是“字符编码”时,“字符编码”其实指的仅是上面的 CCS 部分。

同理,ASCII、Unicode、UTF-8、UTF-16、UTF-16 LE,都可以笼统的叫做“字符编码”,但每个“字符编码”表示的含义都是不同的。可能是 CCS、CEF、CES,也可能是整个过程。

Unicode 转换格式

Unicode 只是把字符映射成了 Code Point (字符编码表,CCS)。将 Code Point 转换成 Code Unit 序列(字符编码表,CEF),再最终将 Code Unit 序列转换成字节序列(字符编码方案,CES),有多种不同的实现方式。这些实现方式叫做 Unicode 转换格式 (Unicode Transformation Format, UTF)。主要包括:

  • UTF-32
  • UTF-16
  • UTF-8

UTF-32

UTF-32 将每个 Unicode Code Point 转换成 1 个 32 位长的 Code Unit。

UTF-32 是固定长度的编码方案,每个 Code Unit 的值就是其 Code Point 的值。例如 0x00 00 00 41 这个 Code Unit,就表示了 0x0041 这个 Code Point。

UTF-32 的一个 Code Unit,需要转换成 4 个字节的序列。因此,有大端序 (UTF-32 BE) 和小端序 (UTF-32 LE) 两种转换方式。

例如 0x00 00 00 41 这个 Code Unit,使用 UTF-32 BE 最终会编码为 0x00 00 00 41;使用 UTF-32 LE 最终会编码为 0x41 00 00 00

UTF-16

UTF-16 将每个 Unicode Code Point 转换成 1 到 2 个 16 位长的 Code Unit。

对于基本平面的 Code Point(0x00000xFFFF),每个 Code Point 转换成 1 个 Code Unit,Code Unit 的值就是其对应 Code Point 的值。例如 0x0041 这个 Code Unit,就表示了 0x0041 这个 Code Point。

对于辅助平面的 Code Point(0x0100000x10FFFF),每个 Code Point 转换成 2 个 Code Unit 的序列。如果还是直接使用 Code Point 数值转换成 Code Unit,就有可能和基本平面的编码重叠。例如 U+010041 如果转换成 0x00010x0041 这两个 Code Unit,解码的时候没办法知道这是 U+010041 一个字符,还是 U+0001U+0041 两个字符。

为了让辅助平面编码的两个 Code Unit,都不与基本平面编码的 Code Unit 重叠,就需要利用基本平面中一个特殊的区段了。基本平面中规定了从 0xD8000xDFFF 之间的区段,是永久保留不映射任何字符的。UTF-16 将辅助平面的 Code Point,编码成一对在这个范围内的 Code Unit,叫做代理对。这样解码的时候,如果解析到某个 Code Unit 在 0xD8000xDFFF 范围内,就知道他不是基本平面的 Code Unit,而是要两个 Code Unit 组合在一起去表示 Code Point。

具体转换方式是:

  1. 将辅助平面的 Code Point 的值 (0x010000 - 0x10FFFF),减去 0x010000,得到 0x000000xFFFFF 范围内的一个数值,也就是最多 20 个比特位的数值
  2. 将前 10 位的值(范围在 0x00000x03FF),加上 0xD800,得到范围在 0xD8000xDBFF 的一个值,作为第一个 Code Unit,称作高位代理或前导代理
  3. 将后 10 位的值(范围在 0x00000x03FF),加上 0xDC00,得到范围在 0xDC000xDFFF 的一个只,作为第二个 Code Unit,称作低位代理或后尾代理

基本平面中的 0xD800 - 0xDBFF0xDC00 - 0xDFFF 这两个区段,也分别叫做 UTF-16 高半区 (High-half zone of UTF-16) 和 UTF-16 低半区 (Low-half zone of UTF-16)。

UTF-16 的一个 Code Unit,需要转换成 2 个字节的序列。因此,有大端序 (UTF-16 BE) 和小端序 (UTF-16 LE) 两种转换方式。

例如 0x0041 这个 Code Unit,使用 UTF-16 BE 最终会编码为 0x0041;使用 UTF-16 LE 最终会编码为 0x4100

UTF-8

UTF-8 将每个 Unicode Code Point 转换成 1 到 4 个 8 位长的 Code Unit。

UTF-8 是不定长的编码方案,使用前缀来标识 Code Unit 序列的长度。解码时,根据前缀,就知道该将哪几个 Code Unit 组合在一起解析成一个 Code Point 了。

具体编码方式是:

Code Point 范围 Code Unit 个数 每个 Code Unit 前缀 示例 Code Point 示例 Code Unit 序列
7 位以内 (0 - 0xEF) 1 0b0 0b0zzz zzzz 0b0zzz zzzz
8 到 11 位 (0x80 - 0x07FF) 2 第一个 0b110,剩下的 0b10 0b0yyy yyzz zzzz 0b110y yyyy 10zz zzzz
12 到 16 位 (0x0800 - 0xFFFF) 3 第一个 0b1110,剩下的 0b10 0bxxxx yyyy yyzz zzzz 0b1110 xxxx 10yy yyyy 10zz zzzz
17 到 21 位 (0x10000 - 10FFFF) 4 第一个 0b11110,剩下的 0b10 0b000w wwxx xxxx yyyy yyzz zzzz 0b1111 0www 10xx xxxx 10yy yyyy 10zz zzzz

解码时,拿到每个 Code Unit 的前缀,就知道这是对应第几个 Code Unit:

  • 前缀是 0b0,说明这个 Code Point 是一个 Code Unit 组成
  • 前缀是 0b110,说明这个 Code Point 是两个 Code Unit 组成,后面还会有 1 个 0b10 前缀的 Code Unit
  • 前缀是 0b1110,说明这个 Code Point 是三个 Code Unit 组成,后面还会有 2 个 0b10 前缀的 Code Unit
  • 前缀是 0b11110,说明这个 Code Point 是四个 Code Unit 组成,后面还会有 3 个 0b10 前缀的 Code Unit

UTF-8 的一个 Code Unit,刚好转换成 1 个字节,因此不需要考虑字节序。

参考上表,对于 ASCII 范围内的字符,使用 ASCII 和 UTF-8 编码的结果是一样的。所以 UTF-8 是 ASCII 的超集,使用 ASCII 编码的字节流也可以使用 UTF-8 解码。

UTF-8 与 UTF-16 对比

Code Point 范围 UTF-8 编码长度 UTF-16 编码长度
7 位以内 (0x00 - 0xEF) 1 2
8 到 11 位 (0x0080 - 0x07FF) 2 2
12 到 16 位 (0x0800 - 0xFFFF) 3 2
17 到 21 位 (0x10000 - 10FFFF) 4 4

可以看出只有在 0x000xEF 范围的字符,UTF-8 编码比 UTF-16 短;而在 0x0800 - 0xFFFF 范围内,UTF-8 编码是比 UTF-16 长的。

而中文主要在 0x4E000x9FFF,如果写一篇文档,全都是中文,一个英文字母和符号都没有。那使用 UTF-8 编码,可能比 UTF-16 编码还要多占用一半的空间。


相关文章:

详解字符编码与 Unicode的更多相关文章

  1. UTF-8、GB2312、GBK编码格式详解和编码示例

    UTF-8.GB2312.GBK编码格式详解 参考文章 UTF-8 使用1~4个字节对每个字符进行编码 128个ASCII字符字需要一个字节编码 带有附加符号的拉丁文.希腊文.西里尔字母.亚美尼亚语. ...

  2. 刨根究底字符编码之八——Unicode编码方案概述

    Unicode编码方案概述   1. 前面讲过,随着计算机发展到世界各地,于是各个国家和地区各自为政,搞出了很多既兼容ASCII但又互相不兼容的各种编码方案.这样一来同一个二进制编码就有可能被解释成不 ...

  3. 关于JAVA字符编码:Unicode,ISO-8859-1,GBK,UTF-8编码及相互转换

    我们最初学习计算机的时候,都学过ASCII编码. 但是为了表示各种各样的语言,在计算机技术的发展过程中,逐渐出现了很多不同标准的编码格式, 重要的有Unicode.UTF.ISO-8859-1和中国人 ...

  4. 字符编码:Unicode和UTF-8之间的关系

    Unicode和UTF-8之间的关系 1. ASCII码 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串.每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256 ...

  5. python中字符编码及unicode和utf-8区别

    ascii和unicode是字符集,utf-8是编码集 字符集:为每一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point) 编码规则:将「码位」转换为字节序列的规则(编码/ ...

  6. 详解Base64编码和解码

    Base64是最常用的编码之一,比如开发中用于传递参数.现代浏览器中的<img />标签直接通过Base64字符串来渲染图片以及用于邮件中等等.Base64编码在RFC2045中定义,它被 ...

  7. JavaScript:详解 Base64 编码和解码

    Base64是最常用的编码之一,比如开发中用于传递参数.现代浏览器中的<img />标签直接通过Base64字符串来渲染图片以及用于邮件中等等.Base64编码在RFC2045中定义,它被 ...

  8. 字符编码 ASCII,Unicode和UTF-8的关系

    转自:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143166410626 ...

  9. 字符编码 ASCII unicode UTF-8

    字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(b ...

随机推荐

  1. java反序列化漏洞专项

    背景条件:java的框架,java的应用程序,使用序列化,反序列化操作非常多.在漏洞挖掘中,代码审计中,安全研究中,反序列化漏洞是个重点项,不可忽视.尤其是java应用程序的rce,10个里面有7个是 ...

  2. centos7 yum error yum doesn't have enough cached data

    1.vi /etc/resolv.conf,添加下面一行 nameserver 114.114.114.114 修改完成后service network restart进行重启,试一下yum upda ...

  3. APISpace 全球快递物流查询API接口 免费好用

    前言   随着我国电子商务的迅猛发展,物流行业也开始突飞猛进,人们的日常生活越来越离不开快递服务,查快递.寄快递的需求越来越大,随之而来,常用快递接口的需求也越来越大. 全国快递查询接口,支持各大快递 ...

  4. Winsock Server Code

    以下代码来自:https://msdn.microsoft.com/en-us/library/windows/desktop/ms737593(v=vs.85).aspx #undef UNICOD ...

  5. 事务_基本演示和事务_默认自动提交&手动提交

    事务的基本介绍 概念: 如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败 操作: 开启事务:start transaction; 回滚:rollback; 提交:co ...

  6. 清北学堂 2020 国庆J2考前综合强化 Day5

    目录 1. 题目 T1 a 题目描述 Sol T2 b 题目描述 Sol T3 c 题目描述 Sol T4 d 题目描述 Sol 2. 算法 - 贪心 & 数学 1. 贪心 2. 数学 2.1 ...

  7. 人工智能不过尔尔,基于Python3深度学习库Keras/TensorFlow打造属于自己的聊天机器人(ChatRobot)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_178 聊天机器人(ChatRobot)的概念我们并不陌生,也许你曾经在百无聊赖之下和Siri打情骂俏过,亦或是闲暇之余与小爱同学谈 ...

  8. 163_技巧_Power BI 一键批量建立自定义字段参数

    163_技巧_Power BI 一键批量建立自定义字段参数 一.背景 在 2022 年 5 月开始,Power BI 新增了一个非常有用的功能字段参数.再也不用写一串的 SWITCH 了.字段参数的效 ...

  9. ToString()格式化输出

    C 货币 2.5.ToString("C") ¥2.50 D 十进制数 25.ToString("D5") 00025 E 科学型 25000.ToString ...

  10. playwright录制脚本

    我喜欢Playwright! 这是微软开源的一款非常强大的自动化工具,再过几年,他很有可能取代Selenium在浏览器自动化的通知地位.使用过一段时间,我没有找到很好的中文资料可以参考,导致很多问题无 ...