MySQL 字符集
这是一个非常硬核且直击底层的技术问题!在变长编码(如 GBK 或 UTF-8)中,一串连续的二进制字节放在磁盘或内存里,计算机在读取时确实面临着“从哪里断句”的问题。
纠正一个小前提:我们在存储字符串时,是将字符串转成二进制数据(编码);而在读取时,才是将二进制数据转成字符串(解码)。
不同的变长字符集,在设计之初就利用了极其精妙的“前缀编码思想”和“高低位状态标记”来解决字符边界问题。下面我们以书中提到的 GBK 和 UTF-8 为例,看看它们是如何做到精准“断句”的:
1. GBK 字符集:靠“高位是否大于 127”来断句
GBK 是一种标准的变长编码:如果是英文,占 1 个字节;如果是汉字,占 2 个字节。
它的边界识别规则:
GBK 完美的利用了 ASCII 码的最高位。
-
ASCII 码的范围是
0 ~ 127(二进制:00000000到01111111),也就是说,所有标准英文和符号的单字节编码,最高位(最左边的一位)永远是0。 -
GBK 规定:属于汉字及中文符号的字符,双字节编码中的第一个字节(高位字节)的范围是
128 ~ 255(二进制:10000000到11111111),最高位永远是1。
计算机解码时的“脑回路”:
计算机在指针往后移动读取字节串时,采用如下状态机逻辑:
-
读取当前字节,看看它的数值:
-
如果在
0 ~ 127之间(最高位是 0),代表这是一个单字节的英文字符。直接解码,指针向后移动 1 位。 -
如果在
128 ~ 255之间(最高位是 1),代表这是一个中文的开头!它会强制把当前字节和紧随其后的下一个字节组合在一起,当成一个整体去解码。完成后,指针向后移动 2 位。
-
2. UTF-8 字符集:靠字节开头的“1 的个数”和固定前缀来断句
UTF-8 更加神奇,它可以支持 1 到 4 个字节的变长编码。一团乱麻一样的字节流,它不仅能精准分清边界,甚至哪怕文件中间损坏漏掉了一个字节,后面的字符依然能精准对齐,不会导致整篇乱码!
这是因为 UTF-8 在每个字节的开头都精心设计了“身份标签”:
| 字符长度 | 字节 1 的二进制前缀 | 字节 2 的前缀 | 字节 3 的前缀 | 字节 4 的前缀 |
|---|---|---|---|---|
| 1 字节(英文) | 0xxxxxxx |
- | - | - |
| 2 字节 | 110xxxxx |
10xxxxxx |
- | - |
| 3 字节(普通汉字) | 1110xxxx |
10xxxxxx |
10xxxxxx |
- |
| 4 字节(Emoji/生僻字) | 11110xxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
它的边界识别规则(看开头的 1):
当计算机开始读取一个新字符的第一个字节(我们称之为引导字节)时:
-
如果以
0开头:说明这个字符只有 1 个字节长。 -
如果以
110开头:数一下,有两个1,说明这个字符一共占 2 个字节。 -
如果以
1110开头:有三个1,说明这个字符一共占 3 个字节(比如书中的汉字'啊')。 -
如果以
11110开头:有四个1,说明这个字符一共占 4 个字节。
后面跟进的字节怎么识别?
你看上面表格,所有多字节字符的后续字节(非首字节),一律死死地固定以 10 开头。
这样做的好处是,计算机读取了引导字节后,就知道后面该打包几个字节了。比如读到 1110xxxx,它就知道“加上我自己,后面那两个以 10 开头的字节也都属于我,我们三个是一伙的!”
3. 回顾书中的乱码例子:边界被破坏的现场
书里举了一个完美的反面教材:把 UTF-8 编码的汉字 '我'(十六进制 0xE68891),错误地用 GBK 去解码。
我们来看看计算机在边界判断上是怎么被欺骗的:
-
真实数据(UTF-8):
0xE68891$\rightarrow$ 二进制为:11160110 10001000 10010001。- 如果是 UTF-8 去读:看到
1110开头,知道是 3 字节字符,把这 3 个字节打包成一句话,顺利解出'我'。
- 如果是 UTF-8 去读:看到
-
错误读取(GBK 强行介入):
-
计算机用 GBK 规则去读第一个字节
0xE6(十进制 230)。 -
GBK 边界判断:230 大于 127!计算机误以为“哦!这是一个汉字的开头,我要强行把后面一个字节也打包进来!”
-
于是它把
0xE6和0x88拼在一起去查 GBK 字典,强行解出了一个莫名其妙的汉字'鎴'。 -
接着往下读第三个字节
0x91(十进制 145),由于 145 依然大于 127,GBK 以为又是中文字头,试图去往后再读一个字节,结果发现后面没数据了(文件结束了),这就是半个字符,只能吐出一个问号?。
-
最终结果:原本好好的一个字符,因为边界判定规则错位,硬生生被拆成了“一个怪字 + 半个废字”,也就是我们看到的乱码。
总结
无论是 2 字节限制的 GBK,还是 4 字节天花板的 UTF-8,它们不靠额外的空格或分隔符,而是在数据本身的二进制最高位(前缀)里嵌入了“长度和边界信息”。这就是为什么存储变长字节时,计算机依然能“一字一顿”、精准断句的原因。