MySQL 字符集

这是一个非常硬核且直击底层的技术问题!在变长编码(如 GBKUTF-8)中,一串连续的二进制字节放在磁盘或内存里,计算机在读取时确实面临着“从哪里断句”的问题。

纠正一个小前提:我们在存储字符串时,是将字符串转成二进制数据(编码);而在读取时,才是将二进制数据转成字符串(解码)

不同的变长字符集,在设计之初就利用了极其精妙的“前缀编码思想”“高低位状态标记”来解决字符边界问题。下面我们以书中提到的 GBKUTF-8 为例,看看它们是如何做到精准“断句”的:


1. GBK 字符集:靠“高位是否大于 127”来断句

GBK 是一种标准的变长编码:如果是英文,占 1 个字节;如果是汉字,占 2 个字节。

它的边界识别规则:

GBK 完美的利用了 ASCII 码的最高位

  • ASCII 码的范围0 ~ 127(二进制:0000000001111111),也就是说,所有标准英文和符号的单字节编码,最高位(最左边的一位)永远是 0

  • GBK 规定:属于汉字及中文符号的字符,双字节编码中的第一个字节(高位字节)的范围是 128 ~ 255(二进制:1000000011111111),最高位永远是 1

计算机解码时的“脑回路”:

计算机在指针往后移动读取字节串时,采用如下状态机逻辑:

  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 去解码。

我们来看看计算机在边界判断上是怎么被欺骗的:

  1. 真实数据(UTF-8)0xE68891 $\rightarrow$ 二进制为:11160110 10001000 10010001

    • 如果是 UTF-8 去读:看到 1110 开头,知道是 3 字节字符,把这 3 个字节打包成一句话,顺利解出 '我'
  2. 错误读取(GBK 强行介入)

    • 计算机用 GBK 规则去读第一个字节 0xE6(十进制 230)。

    • GBK 边界判断:230 大于 127!计算机误以为“哦!这是一个汉字的开头,我要强行把后面一个字节也打包进来!”

    • 于是它把 0xE60x88 拼在一起去查 GBK 字典,强行解出了一个莫名其妙的汉字 '鎴'

    • 接着往下读第三个字节 0x91(十进制 145),由于 145 依然大于 127,GBK 以为又是中文字头,试图去往后再读一个字节,结果发现后面没数据了(文件结束了),这就是半个字符,只能吐出一个问号 ?

最终结果:原本好好的一个字符,因为边界判定规则错位,硬生生被拆成了“一个怪字 + 半个废字”,也就是我们看到的乱码。

总结

无论是 2 字节限制的 GBK,还是 4 字节天花板的 UTF-8,它们不靠额外的空格或分隔符,而是在数据本身的二进制最高位(前缀)里嵌入了“长度和边界信息”。这就是为什么存储变长字节时,计算机依然能“一字一顿”、精准断句的原因。