彻底弄懂 Unicode 编码

前言

为什么要有编码?

大家需要明确的是在计算机里所有的数据都是字节的形式存储、处理的。我们需要这些字节来表示计算机里的信息。但是这些字节本身又是没有任何意义的,所以我们需要对这些字节赋予实际的意义。所以才会制定各种编码标准。

编码模型

首先需要明确的是存在两种编码模型

A:简单字符集

在这种编码模型里,一个字符集定义了这个字符集里包含什么字符,同时把每个字符如何对应成计算机里的比特也进行了定义。例如 ASCII,在 ASCII 里直接定义了 A -> 0100 0001。也就是 ASCII 直接完成了现代编码模型的前三步工作。

B:现代编码模型

在现代编码模型里要知道一个字符如何映射成计算机里比特,需要经过如下几个步骤:

知道一个系统需要支持哪些字符,这些字符的集合被称为字符表(Character repertoire)

给字符表里的抽象字符编上一个数字,也就是字符集合到一个整数集合的映射。这种映射称为编码字符集(CCS:Coded Character Set),unicode 是属于这一层的概念,跟计算机里的什么进制啊没有任何关系,它是完全数学的抽象的。

将 CCS 里字符对应的整数转换成有限长度的比特值,便于以后计算机使用一定长度的二进制形式表示该整数。这个对应关系被称为字符编码表(CEF:Character Encoding Form)UTF-8, UTF-16 都属于这层。

对于 CEF 得到的比特值具体如何在计算机中进行存储,传输。因为存在大端小端的问题,这就会跟具体的操作系统相关了。这种解决方案称为字符编码方案(CES:Character Encoding Scheme)。

平常我们所说的编码都在第三步的时候完成了,都没有涉及到 CES。所以 CES 并不在本文的讨论范围之内。

现在也许有人会想为什么要有现代的编码模型?为什么在现在的编码模型要拆分出这么多概念?直接像原始的编码模型直接都规定好所有的信息不行吗?这些问题在下文的编码发展史中都会有所阐述。

ASCII

我们知道在计算机中,所有的信息最终都表示为一个二进制的字符串,每一个二进制位有 0 和 1 两种状态,通过不同的排列组合,使用 0 和 1 就可以表示世界上所有的东西。

而 1 字节对应 8 位二进制数,每位二进制数有 0、1 两种状态,因此 1 字节可以组合出 256 种状态。如果这 256 中状态每一个都对应一个符号,就能通过 1 字节的数据表示 256 个字符。美国人于是就制定了一套编码(其实就是个字典),描述英语中的字符和这 8 位二进制数的对应关系,这被称为 ASCII 码。

ASCII 码一共定义了 128 个字符,包括英文字母 A-Z,a-z,数字 0-9,一些标点符号和控制符号等。这 128 个字符只使用了 8 位二进制数中的后面 7 位,最前面的一位统一规定为 0。

GB2312

英语用 128 个字符来编码完全是足够的,但是用来表示其他语言,128 个字符是远远不够的。于是,一些欧洲的国家就决定,将 ASCII 码中闲置的最高位利用起来,这样一来就能表示 256 个字符。但是,这里又有了一个问题,那就是不同的国家的字符集可能不同,就算它们都能用 256 个字符表示全,但是同一个码点(也就是 8 位二进制数)表示的字符可能可能不同。例如,144 在阿拉伯人的 ASCII 码中是 گ,而在俄罗斯的 ASCII 码中是 ђ。

因此,ASCII 码的问题在于尽管所有人都在 0 - 127 号字符上达成了一致,但对于 128 - 255 号字符上却有很多种不同的解释。与此同时,亚洲语言有更多的字符需要被存储,一个字节已经不够用了。

但是这难不倒智慧的中国人民,我们不客气地把那些 127 号之后的奇异符号们直接取消掉, 规定:

一个小于 127 的字符的意义与原来相同,但两个大于 127 的字符连在一起时,就表示一个汉字;

前面的一个字节(他称之为高字节)从 0xA1 用到 0xF7,后面一个字节(低字节)从 0xA1 到 0xFE;

这样我们就可以组合出大约 7000 多个简体汉字了。

在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的 全角字符。

而原来在 127 号以下的那些就叫 半角字符 了。

中国人民看到这样很不错,于是就把这种汉字方案叫做 GB2312。GB2312 是对 ASCII 的中文扩展。

GBK

但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。

后来还是不够用,于是干脆不再要求低字节一定是 127 号之后的内码,只要第一个字节是大于 127 就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近 20000 个新的汉字(包括繁体字)和符号。

GB18030 / DBCS

后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。

中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 DBCS。

Double Byte Charecter Set:双字节字符集。

在 DBCS 系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于 127 的,那么就认为一个双字节字符集里的字符出现了。

因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码。

Unicode

最终,美国人意识到他们应该提出一种标准方案来展示世界上所有语言中的所有字符,出于这个目的,Unicode 诞生了。

Unicode 源于一个很简单的想法:将全世界所有的字符包含在一个集合里,计算机只要支持这一个字符集,就能显示所有的字符,再也不会有乱码了。

它从 0 开始,为每个符号指定一个编号,这叫做”码点”(code point)。比如,码点 0 的符号就是 null(表示所有二进制位都是 0)。

U+0000 = null

上式中,U+表示紧跟在后面的十六进制数是 Unicode 的码点。

这么多符号,Unicode 不是一次性定义的,而是分区定义。每个区可以存放 65536 个(2^16)字符,称为一个平面(plane)。目前,一共有 17 个平面,也就是说,整个 Unicode 字符集的大小现在是 2^21。

最前面的 65536 个字符位,称为基本平面(缩写 BMP),它的码点范围是从 0 一直到 2^16-1,写成 16 进制就是从 U+0000 到 U+FFFF。所有最常见的字符都放在这个平面,这是 Unicode 最先定义和公布的一个平面。

剩下的字符都放在辅助平面(缩写 SMP),码点范围从 U+010000 一直到 U+10FFFF。

Unicode 只规定了每个字符的码点,到底用什么样的字节序表示这个码点,就涉及到编码方法。

Unicode 编码方案

之前提到,Unicode 没有规定字符对应的二进制码如何存储。以汉字“汉”为例,它的 Unicode 码点是 0x6c49,对应的二进制数是 110110001001001,二进制数有 15 位,这也就说明了它至少需要 2 个字节来表示。可以想象,在 Unicode 字典中往后的字符可能就需要 3 个字节或者 4 个字节,甚至更多字节来表示了。

这就导致了一些问题,计算机怎么知道你这个 2 个字节表示的是一个字符,而不是分别表示两个字符呢?这里我们可能会想到,那就取个最大的,假如 Unicode 中最大的字符用 4 字节就可以表示了,那么我们就将所有的字符都用 4 个字节来表示,不够的就往前面补 0。这样确实可以解决编码问题,但是却造成了空间的极大浪费,如果是一个英文文档,那文件大小就大出了 3 倍,这显然是无法接受的。

于是,为了较好的解决 Unicode 的编码问题, UTF-8 和 UTF-16 两种当前比较流行的编码方式诞生了。当然还有一个 UTF-32 的编码方式,也就是上述那种定长编码,字符统一使用 4 个字节,虽然看似方便,但是却不如另外两种编码方式使用广泛。

UTF-8

UTF-8 是一个非常惊艳的编码方式,漂亮的实现了对 ASCII 码的向后兼容,以保证 Unicode 可以被大众接受。

UTF-8 是目前互联网上使用最广泛的一种 Unicode 编码方式,它的最大特点就是可变长。它可以使用 1 - 4 个字节表示一个字符,根据字符的不同变换长度。编码规则如下:

对于单个字节的字符,第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。因此,对于英文中的 0 - 127 号字符,与 ASCII 码完全相同。这意味着 ASCII 码那个年代的文档用 UTF-8 编码打开完全没有问题。

对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为 0,剩余的 N - 1 个字节的前两位都设位 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充。

编码规则如下:

Unicode 十六进制码点范围UTF-8 二进制0000 0000 - 0000 007F0xxxxxxx0000 0080 - 0000 07FF110xxxxx 10xxxxxx0000 0800 - 0000 FFFF1110xxxx 10xxxxxx 10xxxxxx0001 0000 - 0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

根据上面编码规则对照表,进行 UTF-8 编码和解码就简单多了。下面以汉字“汉”为利,具体说明如何进行 UTF-8 编码和解码。

“汉”的 Unicode 码点是 0x6c49(110 1100 0100 1001),通过上面的对照表可以发现,0x0000 6c49 位于第三行的范围,那么得出其格式为 1110xxxx 10xxxxxx 10xxxxxx。接着,从“汉”的二进制数最后一位开始,从后向前依次填充对应格式中的 x,多出的 x 用 0 补上。这样,就得到了“汉”的 UTF-8 编码为 11100110 10110001 10001001,转换成十六进制就是 0xE6 0xB7 0x89。

解码的过程也十分简单:如果一个字节的第一位是 0 ,则说明这个字节对应一个字符;如果一个字节的第一位 1,那么连续有多少个 1,就表示该字符占用多少个字节。

UTF-16

Windows 内核、Java、Objective-C (Foundation)、JavaScript 中都会将字符的基本单元定为两个字节的数据类型,也就是我们在 C / C++ 中遇到的 wchar_t 类型或 Java 中的 char 类型等等,这些类型占内存两个字节,因为 Unicode 中常用的字符都处于 0x0 - 0xFFFF 的范围之内,因此两个字节几乎可以覆盖大部分的常用字符。

UTF-16 编码介于 UTF-32 与 UTF-8 之间,同时结合了定长和变长两种编码方法的特点。它的编码规则很简单:基本平面的字符占用 2 个字节,辅助平面的字符占用 4 个字节。也就是说,UTF-16 的编码长度要么是 2 个字节(U+0000 到 U+FFFF),要么是 4 个字节(U+010000 到 U+10FFFF)。那么问题来了,当我们遇到两个字节时,到底是把这两个字节当作一个字符还是与后面的两个字节一起当作一个字符呢?

这里有一个很巧妙的地方,在基本平面内,从 U+D800 到 U+DFFF 是一个空段,即这些码点不对应任何字符。因此,这个空段可以用来映射辅助平面的字符。

辅助平面的字符位共有 2^20 个,因此表示这些字符至少需要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800 到 U+DBFF(空间大小 2^10),称为高位(H),后 10 位映射在 U+DC00 到 U+DFFF(空间大小 2^10),称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。

因此,当我们遇到两个字节,发现它的码点在 U+D800 到 U+DBFF 之间,就可以断定,紧跟在后面的两个字节的码点,应该在 U+DC00 到 U+DFFF 之间,这四个字节必须放在一起解读。

接下来,以汉字”

计算机基础知识之Unicode-彻底弄懂 Unicode 编码相关推荐

  1. 计算机数据传输和信号传输,职称计算机基础知识第1章:数据传输的编码和调制技术...

    数据通信的技术基础 在数据通信中,要将数据从一个节点传送到另一个节点,必须将数据转换为信号 数据通信的编码技术 数字数据的编码方式有三种,不归零编码.曼彻斯特编码和差分曼彻斯特编码 1.不归零编码 N ...

  2. 计算机的基础知识---位(bit)、字节(byte)、编码(Encoding)和大小端

    一.基础知识简介 1.1.位(bit) 位(bit):是计算机的最小数据存储单位(即:由二进制数字0和1表示)可用小写的b表示: 现代计算机存储和处理的信息以二值信号表示.这些微不足道的二进制数字,或 ...

  3. python-第一章 计算机基础知识

    一.学习链接介绍 本文档是学习尚硅谷的python课程而写,在此感谢尚硅谷老师无私的视频贡献!视频地址:https://www.bilibili.com/video/BV1Xo4y1d7Uc/? 1. ...

  4. 大学生计算机基础 英语,大学生计算机基础知识练习答案(国外英语资料).doc

    大学生计算机基础知识练习答案(国外英语资料) 翁蜘子沽千算县妈萤霜援仅沿镇派鉴桑吨珍敬忽没佛河吧税馁儡郸浩让媳午宠留寻臀摇丧听计椰彤粒妊硫满囚纯乃涡传且粒纹专交车泥鳞鸡调咙剃欲涌殴胳荤诺糖憾怕谋颐可吁 ...

  5. 穿越 java | 快速入门篇 - 第1节 计算机基础知识

    主题:计算机基础知识 开发环境 更多干货 定义 作用 组成元件 CPU 内存 cpu里的高速缓存 BIOS软件(基础输入输出系统) CMOS芯片 机械硬盘 组成 数据存取过程 文件编码 ASCII G ...

  6. 计算机硬件和软件基础知识,计算机基础知识(硬件和软件)

    计算机基础知识(硬件和软件) 一. 教学目标 [知识目标]通过本章节的学习,可以了解到计算机系统基本组成.计算机常见硬件的基础知识及计算机的基本工作原理. [技能目标]对于本章节所涉及的计算机基础知识 ...

  7. 计算机知识太多了,计算机基础知识对程序员来说有多重要?

    原标题:计算机基础知识对程序员来说有多重要? 科班和培训生同比于自学者的优势就在于这些计算机专业的核心课程(数据结构与算法这种不管科班培训都要学的不算):离散数学.编译原理.计算机组成原理.操作与系统 ...

  8. 别再学习框架了,看看这些让你起飞的计算机基础知识

    这些基础知识,就像我们的内功,如果在未来想要走的更远,这些内功是必须要修炼的.框架千变万化,而这些通用的底层知识,却是几乎不变的,了解了这些知识,可以帮助我们更快着学习一门知识,更加懂得计算机的运行机 ...

  9. 计算机键盘输入法基础知识,教程计算机基础知识-:认识输入法

    教程计算机基础知识-:认识输入法 认识输入法 在任务栏的右边有一个小键盘图标,这就是输入法,默认输入的是英文字母,点击可以选择汉字输入法: 1.切换输入法 1)瞄准任务栏上的小键盘点左键,在出来的菜单 ...

最新文章

  1. 从微盟36小时故障,谈谈数据安全这点事
  2. MySQL高效分页解决方案集
  3. 天翼云从业认证课后习题(3.5云安全产品)
  4. vue中refs的使用
  5. C++关于引用的注意事项 总结知识点
  6. iOS UIWebView URL拦截
  7. qlv视频转换器免费版_迅捷视频转换器无法转换腾讯视频怎么办?亲测操作快速转换...
  8. 经典测试用例--水杯测试
  9. 关于php聊天室的实现方法,PHP聊天室简单实现
  10. CCRC信息安全服务资质--风险评估申请
  11. 二手手机验机教程(不拆机)
  12. 有孚网络CEO安柯:IT部门趋于轻量化 “平台+服务”或将成为主流
  13. 中英文停止词表(stopword)
  14. NOIP2021 T3 方差
  15. 项目进度经常延误,该怎么破?
  16. ES 与关系型数据库的对比
  17. 电磁感应、电感与变压器
  18. 解决小程序自定义导航栏和右边胶囊高度一致
  19. 【渝粤题库】国家开放大学2021春2412基础写作题目
  20. JDK里的“SPI”是什么意思

热门文章

  1. Zimbra邮件服务器利用XXE漏洞与SSRF完成对目标的文件上传与远程代码执行
  2. mac for intel 使用个人账号免费使用vmware
  3. mqttnet+emqx实现消息订阅与发布
  4. 第八届蓝桥杯单片机省赛----程序题
  5. 思维导图 XMind 8 Update 8 Pro for Mac 中文破解版
  6. RSA加密算法,从实际应用到基础原理(一)
  7. C#中Emial发送方法
  8. 隆云通水质ORP传感器
  9. 农产品溯源和农业数字化转型如何同步进行?
  10. EasyRecovery14全新个人版安装包Windows