在乔纳森·斯威夫特的著名讽刺小说《格列夫游记》中,小人国内部分裂成 Big-endian 和 Little-endian 两派,区别在于一派要求从鸡蛋的大头把鸡蛋打破,另一派要求从鸡蛋的小头把鸡蛋打破。
然后忘了这个故事,咱们开始吧。
Charles 同学这周又踩了个坑,数据插入 MySQL 时报错:
1366 Incorrect string value: '\xF0\x90\x8D\x83...' for column 'content' at row 1
按惯例搜一下,据说是因为 mysql 用的 utf8 不支持 emoji,需要修改配置文件,将字符集改成 utf8mb4:
[mysqld]
character-set-server = utf8mb4
stackoverflow.com/questions/10957238
但是 Charles 已经加上了这个配置,仍然报错。
实际上,MySQL 还有另外一个配置,用于指定客户端和服务器之间连接时使用的字符集:
[mysqld]
character-set-client-handshake = utf8mb4
当然,也可以在 MySQL Client 中指定,具体需要参考 client 的文档,或者简单粗暴地在连接成功以后执行(但不推荐):
SET NAMES utf8mb4;
那么,什么是 utf8mb4 ?和 utf8 有啥区别呢?
根据 MySQL 的 manual:
The utfmb4 character set has these characteristics:
- Supports BMP and supplementary characters.
- Requires a maximum of four bytes per multibyte character.
https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8mb4.html
(文档中 utf8mb4 打错了,我是原样复制的)
翻译过来就是,utf8mb4 支持 BMP ( Unicode Basic Multilingual Plane )和补充字符,每个字符最多 4 字节(这里 “mb4” 大概就是 multi byte 4 的简写了)。
冷知识:Unicode 编码一共有 17 个 "Plane"( 0~16 ),其中 Plane 0 就是 BMP,包含绝大多数常用字符,比如希腊、希伯来、阿拉伯、CJK ( Chinese-Japanese-Korean )字符等。Plane 1~16 被称为 "supplementary planes",包含不常用的其他字符,例如 emoji 和某些特殊的 CJK 字符。所以目前 Unicode 字符的最大编码为 0x10FFFF 。
至于 utf8,MySQL 文档里也有说明:
utf8 is an alias for the utf8mb3 character set.
Note
The utf8mb3 character set is deprecated and will be removed in a future MySQL release. Please use utf8mb4 instead
https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8.html
简单说就是挂羊头卖狗肉了,看到的是 utf8,实际用的是 utf8mb3
utf8mb3 的文档就不贴了(懒),和 ut8mb4 的区别就在于最多只支持 3 个字节,因此不支持 Unicode 的补充字符集。
也就是说,MySQL 里的 utf8,实际上是一个阉割版的 utf8 。
MySQL 从 5.5.3 才开始支持完整版的 utf8 ( utf8mb4 ),并且后续计划移除 utf8mb3,utf8 未来在 mysql 中也会变成 utf8mb4 的别名,所以以后默认都使用 utf8mb4 就对了。
话说回来,MySQL 为什么会有这种奇怪的设定呢?
其实最初是从性能上考虑的,这个精简版的 utf8 在运行的时候可以更快一点点。
要知道 MySQL 已经是一个 24 岁的老项目了,在 1995 年诞生时,Intel 才只推出了 Pentium Pro,对比现在的 CPU,性能可以说是非常差了。
冷知识:差到什么程度呢?举个例子,早期的 Windows Beta 版,桌面右下角时间是可以显示秒数的,但由于当时硬件的性能问题,微软在发布 Windows 95 之前就移除了该功能,直到 Windows 7 ( 2009 年)才允许通过修改注册表开启。
那么真正的 utf8 长什么样呢?
在查文档之前,不妨先动手创建一个文档看一下
$ echo '0Aa 你好' > utf-8.txt
$ file utf-8.txt
utf-8.txt: UTF-8 Unicode text
$ xxd utf-8.txt #用 16 进制的方式查看
0000: 3041 61e4 bda0 e5a5 bd0a 0Aa.......
可以看到,开头"0Aa" 对应 3 个字节 0x30 、0x41 、0x61 (十进制 48 、65 、97,大写 A < 小写 a 就是这么来的)。
最后一个字符 0x0a 是 echo 默认输出的换行。
冷知识:可以加上 -n 参数让 echo 不输出换行符。换行符在不同 OS 下不同,在 Linux/Unix 下是 "\n" ( 0x0a ),在 Windows 下是 "\r\n"( 0x0d 0x0a ),在早期 Mac 下是 "\r" ( 0x0d ),从 Mac OS 10.0 ( 2001 )开始也和 Unix 一样用 "\n" 了。在 C/Python 等语言下,fopen/open 默认使用“文本模式”打开文件,读取时会统一转换成 "\n",写入时将"\n"转换为按 os 的默认值。还有一些其他场景可能需要注意,例如 http 协议中 header 使用 "\r\n" 换行。
中间的 "e4bda0e5a5bd" 这 6 个字节对应的就是 “你好” 了,每个字符 3 个字节。
那到底如何确定一个 utf8 字符是几个字节呢?
这里贴一个 wikipedia 的表格 Number
字节数 | 比特数 | Unicode 区间 开始~结束 | 字节 1~4
1 7 U+0000 U+00007F 0xxxxxxx
2 11 U+0080 U+0007FF 110xxxxx 10xxxxxx
3 16 U+0800 U+00FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 21 U+10000 U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
简单解释一下:
前面展示了 ASCII 字符和中文,咱们顺便再看看 emoji 的 utf-8 编码长什么样:
$ echo -n 😀 > 1.txt
$ xxd 1.txt
0000: f09f 9880 ....
根据上面的表格,我们可以算出,这个 GRIN FACE (露齿笑)的 Unicode 码点是 0x1F600,在 Unicode 的 Plain 1 中,因此 utf-8 编码需要 4 个字节。
上文提到了 Unicode 和 utf-8 这两个名词,但是很多同学其实没搞明白他俩的区别是啥。
一般我们提到 Unicode 时指的是字符集( Character Set ),其中包含了一系列字符,并为每一个字符指定了码点( Code Point,即该字符的编号)。
Unicode 标准里包含了很多编码规范,utf-8 是其中一种,指定了一个 Unicode 字符的码点应该如何存储,例如 ASCII 用一个字节,超过一个字节的根据需要的 bit 数量,分别存储到多个字节中。
除了 utf-8 之外,Unicode 还有多种不同的编码规范,例如
然鹅 utf-8 几乎统治了互联网,超过 93%的网页是用 UTF-8 编码的,以至于 IETF 要求所有网络协议都需要标明内容的编码,并且必须支持 UTF-8 。
至于原因么,还记得开头的那个故事吗? utf-8 避免了上述编码中的字节序( big endian 、little endian )的问题。
当然这只是一个原因,我认为更重要的是,utf-8 保持了对 ascii 的兼容,路径依赖的强大惯性,会导致上述 4 种编码在实际推广中带来很高的迁移成本(按理应该在这里讲讲马屁股宽度的故事,不过跑题太远了)。
utf-8 在保持后向兼容的前提下,能支持所有 Unicode 字符,相比 ucs4 还能节省大量存储空间、数据传输量,因此统治了互联网,也就在情理之中了。
除了 Unicode 之外,还有很多其他字符集,例如最经典的 ASCII,由于字符少,其编码规范也相当简单。
在中国,比较常见的字符集还有 GB2312 ( 1980 年)、GBK ( 1993 年)、GB18030 ( 2000 年),这些标准都规定了对应的编码规范,所以这些名字既可以表示字符集,也可以表示编码规范。
其中 GB2312 只包含 7445 个字符,其中汉字 6763 个(只包含常用汉字,很多生僻字都不支持),编码规范也兼容 ASCII 。GBK ( GB13000 )兼容 GB2312,添加了更多字符,GB18030 是进一步的补充。
冷知识:我们可以使用 iconv 命令行工具来修改文件的字符编码
$ iconv -f gb18030 -t utf-8 < gb18030.txt
0Aa 你好
也可以在 vim 中这么干
:set fileencoding=gb18030
此外,使用 windows 的同学可能还见到过一个奇怪的代号 "cp936"(在上述 iconv 命令、vim 中都可以使用),这是微软对 GB2312 的实现,在 Win95 以后实际上也支持了大部分 GBK 字符。
~ 投递链接 ~
后端开发(上海) https://job.toutiao.com/s/sBAvKe
后端开发(北京) https://job.toutiao.com/s/sBMyxk
广告策略研发(上海) https://job.toutiao.com/s/sBDMAK
其他地区、职能线 https://job.toutiao.com/s/sB9Jqk
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.