我不输出好文,我只是好文的搬运工。十分钟搞清字符集和字符编码带你走进 Unicode 和 UTF-8 的前世今生。总而言之,Unicode 是标准,UTF-8 是实现。
MySQL 过招 Emoji
通常 MySQL 的默认字符集都会配置为 UTF-8,只支持单字符不超过 3 bytes 的存储1。常见的 Emoji 的 Unicode 编码值 code point 位于 \u1F601 -- \u1F64F
区间,以 \u1F300
『🌀』举例,它的二进制有 17 位,无法用三字节的 UTF-8 编码表示2。如果通过 JDBC 将此字符尝试插入 MySQL 记录中,会得到以下异常
1
|
|
Java 过招 Emoji
Java 存储字符的单元是 char
,那么问题来了,16 位的 char
如何 handle 17 位的 Unicode 呢?
喜闻乐见,Java 的单个 char
确实无法 handle 🌀。那么 ⚡ 为什么可以通过编译?相信聪明如你,一定可以查到 ⚡ 的 Unicode 是 \u26A1
,Tada ~~ 所以 ⚡ 插入数据库中也是妥妥的。
速入 Java’s Unicode Notation 科普一下 Java 对 Unicode 的支持实现,配合通俗易懂版更赞3。原来 Java 存储单个字符的最小单元并不一定是单个 char
,而是根据 code point 的范围确定。如果字符 c
的 code point 二进制表示不超过 16 位那么单个 char
可直接表示4;否则要进行以下处理,以 🌀 举例
- 把
\u1F300
的二进制表示0001 11110011 00000000
,其高 10 位与低 10 位拆开备用 - 高位偏移量 W1 :
0xD7C0
与低位偏移量 W2 :0xDC00
分别与其高低 10 位相加处理,得到\uD83C
与\uDF00
5 - 两个字符拼接
\uD83C\uDF00
得到 🌀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
合体
Client 发送 Emoji 到后台,Java 却无法通过 JDBC 将其存储到 MySQL。如果我们遵循某个特定的转换规则,存储前转义一次,读取时再逆转义一次,那不就妥妥妥的了。你当然可以自己写,但是懒逼如我,更倾向于找现货。
引用
- 十分钟搞清字符集和字符编码
- Java’s Unicode Notation
- Get unicode value of a character
- Get character of a unicode value
- Full Emoji Catalog
- emoji-java
-
MySQL5.5 版本以后有对 4 bytes 单字符的原生支持,字符集配置为 utf8mb4↩
-
请看十分钟系列的实现细节,三字节的 UTF-8 编码上限是 16 位『4 + 6 + 6』,
11111 001100 000000
对应的 UTF-8 编码为11110000 10011111 10001100 10000000
,即 \xF0…,MySQL 经常会冒出这种异常↩ -
勘误,通俗版中所说的 W1 :
0xD800
不对,应是0xD7C0
,已在评论中反映给原作者↩ -
比如『⚡』↩
-
Unicode 预留了替代区域
\uD800 -- \uDFFF
作为辅助,此区间的单个字符没有实际表示意义↩