原创作品:首发u3v3, 转载请保留

地址:https://www.u3v3.com/ar/1276

作者ID:Yi_Zhi_Yu

首发日期:2017.3.7

Python学习群:278529278 (欢迎交流)


前言

虽然我们一直在编程, 但很多时候遇到编码和字符集相关的内容就一脸懵逼, utf-8, gbk, ascii, unicode, 用的时候也是。 今天我们就来撸一撸这个所谓的编码到底是个什么鬼

以下源于个人对字符集合编码相关的理解, 如有错误, 还请指出

背景

我们知道在计算机中, 数据无论什么内容, 最终存储的格式都是二进制的, 但是, 人是不能直接阅读二进制的, 必须将其转换为人类看的懂得字符, 我们才能理解

那计算机是怎么将二进制与人类可读的字符进行转换的呢?

我的答案是: 依照字符集, 按照某种编码规范对二进制进行编码/解码

这里就引入了几个概念

字符集

字符集在我的理解看来, 就相当于一本词典, 这本词典包含了二进制编码和对应的字符之间的映射关系,

比如 ascii 码表中, 1000001(十六进制41或者十进制65)表示字符A, 这个 ascii 码表 就是一个字符集

比如 unicode 字符集中, 100111101100000(十六进制4f60)表示中文字符unicode也是一个字符集

编码/解码

按照码表, 按照某种规范对二进制进行字符转换的过程,就是解码,将字符转换为计算机可识别的二进制的过程, 就是编码,

编码规范

二进制存储数据是以字节为单位的, 在数据传输或者其他的转换处理时, 计算机将二进制按照字节进行切分.

而对应几个字节表示什么字符, 这个就没有统一的规范了, 比如单字节表示哪个字符, 或者两个字节表示哪个字符,甚至三字节更多等等, 定义按照某种字符集如何切分的字节并将字节转换字符的规范就是编码规范

比如将 \c41 转换为A(二进制的65) 的过程就是一个解码的过程,其遵照ascii编码规范, 而依赖这个规范, 我们又能将 A 编码成计算机可读的 \c41(二进制的65)

从这里可以看出, 编码/解码是需要依赖字符集和编码规范的


理解了上面几个概念, 我们再来区分unicode, gbk, utf-8, ascii 是个什么鬼

ascii

ascii 这个词, 我的理解是既包含了 ascii 字符集,也就是 ascii码表, 又包含了 ascii编码规范, 即单字节<=>单字符

在早期, 计算机是由欧美人发明并普遍使用的,计算机需要表达的字符就只有 26 个字母以及一些标点等字符,ascii 码表 就包含了二进制和这些字符之间的映射关系, 然后通过 ascii编码规范,将二进制与字符相互转换进行传输和展示

ascii编码单字节<=>单字符的转换过程

比如:

# 二进制的十进制表示方式 => 字符 映射关系
65 <=> A
66 <=> B
...
90 <=> Z

gbk

ascii码表中仅包含了最多256(实际上一半都没用到)个英文使用的字符集, 但当其他国家的人使用计算机时, 需要将二进制转换成对应国家认识的字符啊。

所以有些国家也推出了一些字符集,定义了二进制与对应国家的字符的映射关系, 以及如何转换.

gbk 就是我们国家推出的基于gbk码表的编码规范,

其规定:

字符有一字节和双字节编码,00–7F范围内是第一个字节,和ASCII保持一致,

双字节表示中文字符

比如:

中文的, 用gbk的规范进行编码就是

\xc4\xe3 <=> 

unicode

虽然使用gbk的字符集和规范可以识别英文字符和大部分的中文字符, 但这并不包括其他国家的字符, 比如韩文, 日文, 俄文等等.

在其他国家的字符集中, \xc4\xe3可能表示的就不见得是中文的

在这种情况下, unicode 应运而生,

unicode 是一个巨大的字符集, 其中包含了世界上大部分国家的字符和其对应的二进制表示,

无论是英文/中文/韩文/日文/俄文等等, 都能在这个字符集里找到对应的字符编码, 从而进行转换

比如

中文的, 在unicode编码里就是

u'\u4f60' <=> 

unicode 存储的范围最大4个字节, 能表示的字符数量可达 2^32, 足以包含人类可用的所有字符

utf-8

unicode 使得计算机统一了字符集, 但unicode表示字符从单字节最多能到四字节, 那计算机如何切分一段128字节的字符, 将对应长度的几个字节转换成一个字符呢

utf-8 就是这么一种编码规范, 该规范实现了unicode 字符集下, 变长的 字节<=>字符的转换, 其他的还有utf-16, utf-32等方式, 不过很少用, 所以这里不做说明了

utf-8 的编码规范定义:

  • 如果是单字节的字符, 其utf-8编码也是单字节的, 以1开头, 剩下的补齐对应的unicode编码

比如, A对应的unicode 是1000001, 其对应的utf-8编码就是11000001 加粗部分就是A对应的unicode编码

  • 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。(这部分解释摘自 @阮一峰的博客)
Unicode符号范围         | UTF-8编码方式
(十六进制)              | (二进制)
--------------------+---------------------------------------------
0000 0000 ~~ 0000 007F | 0xxxxxxx
0000 0080 ~~ 0000 07FF | 110xxxxx 10xxxxxx
0000 0800 ~~ 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 ~~ 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

这里我们还是以中文的为例

中文的对应的unicode编码是 4f60(1001111 01100000), 范围处在 0000 0800 ~~ 0000 FFFF(0800~~FFFF)这个区间, 所以对应的utf-8是三字节, 即1110xxxx 10xxxxxx 10xxxxxx 这种表达方式,

我们将1001111 01100000 从低到高补充x, 不够补充的x0填充即可

所以结果是 11100100 10111101 10100000, 注意加粗的部分, 就是从低位开始填充的unicode编码对应的二进制位

所以, 得到的 utf-8编码就是e4bda0

计算机可以根据遇到的每个字节前面的1的位数, 判断是切分几个字节作为一个字符识别, 从而转换为可读的字符

乱码问题

我们有时打开文件看到的文件内容是乱码, 这就是以为该文件保存时的字符编码与我们打开时的编码方式不一致导致的,

比如, 文件保存时, 使用的是utf-8编码, 但我们打开时, 使用的gbk编码, gbk 会做定长的解码, 即双字节解码, 原本6个字节在utf-8中可能表示2个中文字符, 而被gbk 解析成了3个字符, 而这三个字符就很可能是我们看到的乱码内容


总结

综上, 可以得出, unicode 属于字符集的概念, ascii, gbk 一般属于编码规范的概念, 但我这里也认为包含了字符集的概念, utf-8 就是单纯的编码规范的概念

当然, 以上提出的概念都是为了能让大家更容易的做区别, 而且均是个人的理解, 可能不规范, 重要的是希望能让各位理解字符编码的概念和过程, 如果这样目的达到了, 这篇总结就有价值了

如果各位有更好的理解和意见, 请在评论中指出, 不胜感激

right