详解字符编码与 Unicode

news/2024/7/3 2:37:19

🚀 优质资源分享 🚀

学习路线指引(点击解锁)知识定位人群定位
🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

人类交流使用 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。


这些概念间的关系如下:

因此,我们说 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)10b00b0zzz zzzz0b0zzz zzzz
8 到 11 位 (0x80 - 0x07FF)2第一个 0b110,剩下的 0b100b0yyy yyzz zzzz0b110y yyyy 10zz zzzz
12 到 16 位 (0x0800 - 0xFFFF)3第一个 0b1110,剩下的 0b100bxxxx yyyy yyzz zzzz0b1110 xxxx 10yy yyyy 10zz zzzz
17 到 21 位 (0x10000 - 10FFFF)4第一个 0b11110,剩下的 0b100b000w wwxx xxxx yyyy yyzz zzzz0b1111 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)12
8 到 11 位 (0x0080 - 0x07FF)22
12 到 16 位 (0x0800 - 0xFFFF)32
17 到 21 位 (0x10000 - 10FFFF)44

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

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


相关文章:

  • Unicode 标准化
  • Unicode 与编程语言
  • 字节顺序标记
  • 字节序
  • Unicode 与 UCS

http://lihuaxi.xjx100.cn/news/18503.html

相关文章

Linux简单命令之服务控制和安全开关

Linux简单命令之服务控制和安全开关 服务控制安全开关 一、服务控制 systemctl : 系统控制器,用来管理Linux系统的开关机/服务资源运行状态 直接执行 systemctl 列出可以管理的系统资源,包括各种系统服务 控制当前服务的运行状态 systemctl start|stop…

【计算机网络】Web服务器的配置

目录 课题描述 需求分析 2.1 WEB服务器基本构架 2.1.1 WEB服务器和浏览器 2.2 HTTP协议 2.2.1 HTTP简介 2.2.2 HTTP工作原理 2.3 FTP协议 2.3.1 FTP简介 2.3.2 FTP工作原理 2.4 ISS服务作用 概要设计 3.1 ISS服务器的安装与配置 3.2 详细设计 结果分析 4.1 We…

Windows11 VMware上安装适用于编译Android12源代码的Ubuntu虚拟机

一、软件环境 首先下载 VMWare-Workstation下载地址 安装包(自行选择版本下载和安装)其次准备好 Ubuntu 18.04 (Bionic Beaver) 的系统镜像(Google推荐的版本) VMware-workstation-full-16.2.4-20089737.exe(文件615…

Android Studio升级Gradle7.4之后Hilt报错的解决

目录升级Android Studio和Gradle版本Hilt版本升级编译通过,但是运行报错 SAVED_STATE_REGISTRY_OWNER_KEY总结升级Android Studio和Gradle版本 升级到 Android Studio Dolphin | 2021.3.1 版本之后,gradle版本的推荐设置是7.4,于是便一同升级…

在Kafka生产实践中又出问题了

1、背景 最近在折腾Kafka日志集群,由于公司部署的应用不断增加,日志采集程序将采集到的日志发送到Kafka集群时出现了较大延迟,总的TPS始终上不去,为了不影响业务团队通过日志排查问题,采取了先解决问题,再…

【回眸】HighTec编译文件烧录及串口调试

前言 上周烧录完毕后没有串口调试,这周再走一遍流程,编译下载烧录后串口调试。 首先 File→import→General→Existing Project into Workspace导入已经存在的源代码 打开工程目录1ToolEnv→0Build→1Config→ConfigTricoreGnuc→ConfigGnuc.mk&#…

Shell-基础(二):Shell变量、Shell运算符、Shell条件判断、Shell流程控制、函数

三、Shell变量 3.1 系统变量 $HOME、$PWD、$SHELL、$USER等 查看系统变量的值 [rootlocalhost ~]$ echo $HOME /root [rootlocalhost ~]$ 3.2 自定义变量 3.2.1 基本语法 定义变量:变量值撤销变量:unset 变量声明静态变量:readonly变量&…

《c++ Primer Plus 第6版》读书笔记(5)

第10章 对象和类 本章内容包括: 过程性编程和面向对象编程类概念如何定义和实现类共有类访问和私有类访问类的数据成员类方法(类函数成员)创建和实用类对象类的构造函数和析构函数const成员函数this指针创建对象数组类作用域抽象数据类型OOP&…