前言
继上文发表之后, 结合评论意见并亲自验证最终发现是编码的问题, 但是对于字符编码还是有点不解, 于是乎, 有了本文, 我们来学习字符编码, 在学习的过程中, 我发现对于 MySQL 中 JSON 类型的编码导致数据中文出现乱码还有可深挖之处, 接下来我们来分析一下, 若有错误之处, 还请批评指出.
字符编码
评论中指出任何不在基本多文本平面的 Unicode 字符, 都无法使用 MySQL 的 utf8 字符集存储, 包括 Emoji 表情 (Emoji 是一种特殊的 Unicode 编码, 常见于 iOS 和 Android 手机上) 和很多不常用的汉字, 以及任何新增的 Unicode 字符等等 (utf8 的缺点), 然而啥是多文本平面, 详情维基百科《 https://en.wikipedia.org/wiki/Plane_(Unicode) 》. 首先我们了解下什么是 Unicode,Unicode 是通用字符集, 它是一种标准, 该标准在一处定义了编写在计算机上使用的大多数活动语言所需的所有字符, 它的目标是成为并且在很大程度上已经是已编码的所有其他字符集的超集. 在计算机或网络中的文本我们通过字符组成, 字符代表字母, 标点符号或其他符号. 不同的组织收集了不同的字符集并为其创建了编码 - 一个字符集可能仅覆盖基于拉丁语的西欧语言(不包括保加利亚或希腊等欧盟国家), 另一个可能覆盖特定的远东语言(例如(例如日语), 其他语言可能是以特殊方式设计的, 代表世界上某处其他语言的众多语言之一. 但是我们并不能保证应用程序将支持所有编码, 也不能保证给定的编码将满足我们代表给定语言的所有需求, 另外, 通常不可能在同一网页或数据库中组合不同的编码, 因此使用 "传统" 编码方法来支持多语言页面通常非常困难, 所以 Unicode 协会提供了一个大的, 单字节字符集, 旨在包括所有需要的世界上任何书写系统, 包括古老的脚本(如楔形文字, 哥特式和埃及的象形文字) 的字符, 所以统一字符编码, 将其作为 web 和操作系统体系结构的基础, 并且得到所有主要 Web 浏览器和应用程序的支持. 当前的 Unicode 字符分为 17 组编排, 每组被称之为一个平面 (Plane), 所以将字符划分为 0-16 号的平面, 而每平面拥有 65536(即 216) 个代码点即范围区间在 0x000 到 0xFFFF 之间, 而 0 号平面就是基本多语言平面(BMP:Basic Mutiingual Plane). 在基本多文本平面上针对每一种文字或者其补充或者其扩展都给出了一个编码范围, 比如拉丁文[0000-007F] , 拉丁文 - 补充[0080-00FF] 等等. 说了这么多, 我们只需要记住一点即可: 在 Unicode 字符集中前 65536 个代码点构成了基本多语言平面简称 BMP,BMP 中包含了大多常用的字符, 另外 Unicode 字符集还包含了一百万个其他代码点的位置空间, 我们称之为补充字符. 我们需要区分字符集, 编码字符集和编码的概念, 字符集或字符串包含可能用于特定目的的字符集, 它是支持计算机上的西欧语言所需的字符集, 与计算机完全无关, 而编码字符集是一组用于该唯一的号码被分配给每个字符的字符, 有时候我们将编码字符集也可称作为代码页, 编码字符集的单位称为代码点, 代码点值表示字符在编码字符集中的位置. 例如, Unicode 编码字符集中字母 á 的代码点为十进制 225, 十六进制表示法为 0xE1. 而字符编码反映编码字符集被映射到用于在计算机操纵字节的方式. 一个字符集可以有多种编码, 许多字符编码标准, 例如 ISO 8859 系列中定义的标准, 都为给定字符使用单个字节, 并且编码是对编码字符集中字符标量位置的直接映射. 例如, ISO 8859-1 编码字符集中的字母 A 在第 65 个字符位置(从零开始), 并且使用值为 65 的字节进行编码并以此在计算机中表示, 对于 ISO 8859-1 而言, 这将永远不会再改变, 但是, 对于 Unicode, 事情并没有如此简单, 尽管 Unicode 编码字符集中字母 á 的代码点始终为 225(十进制), 但在 UTF-8 中, 它在计算机中由两个字节表示, 换句话说, 在此字符的编码字符集值和编码值之间不是简单的一对一映射, 另外, 在 Unicode 中, 针对同一字符可以有多种编码的方式. 例如, 字母 á 可以用一种编码形式的两个字节表示, 而用另一种编码形式的四个字节表示. 可以与 Unicode 一起使用的编码形式称为 UTF-8,UTF-16 和 UTF-32.
UTF-8 使用 1 个字节表示 ASCII 集中的字符, 使用 2 个字节表示其他几个字母块中的字符, 使用 3 个字节表示 BMP 的其余部分, 补充字符使用 4 个字节. UTF-16 对 BMP 中的任何字符使用 2 个字节, 对补充字符使用 4 个字节. UTF-32 对所有字符使用 4 个字节. 基本多语言平面对应代码点存储的是常用字符, 上述针对不同字符在其对应代码点, 然后计算出该字符的 16 进制的字符串, 举个栗子, 将[好] 字进行 UTF-8 编码看看该字符的字节值和字节数, 如下:
- var bytes = Encoding.UTF8.GetBytes("好");
- var hexString = BitConverter.ToString(bytes);
到此我们大概了解完了字符编码, 接下来我们再次回到上一节的问题, 上一节将我姓名作为 JSON 存储到数据库中去, 但是最终获取数据时, 将出现乱码, 因为其表编码为 utf8, 最终将表编码修改为 utf8mb4 才好使, 为啥 utf8 就不行呢? 通过上述对 utf8 的定义最多可以有 4 个字节, 支持补充字符, 所以 MySQL 根本就没有实现标准的 utf8 编码, 换句话说只是部分实现了 utf8 编码, MySQL 中的 utf8 又名为 utf8mb3, 也就是一个字符最多可通过 3 个字节表示且包含 BMP 字符, 而不包含补充字符. 所以无论是我的姓还是名虽然是 3 个字节, 但是并非常用 BMP 字符导致. 但是针对列类型为 JSON 类型, 事实是对于获取中文真的会乱码吗? 上文我用到的 MySQL 版本为 5.7+, 如下:
接下来我们利用 MySQL 8.0 再来进行测试发现不会乱码, 创建类和表配置编码如下:
- public class t1
- {
- public int id { get; set; }
- public string jdoc { get; set; }
- }
- static void Main(string[] args)
- {
- SetDialect(Dialect.MySQL);
- var con = new MySqlConnection(@"Server=localhost;Database=user;Uid=root;Pwd=root;");
- var id = con.Insert(new t1() { jdoc = JsonConvert.SerializeObject(new { Data = "汪鹏" }) });
- var result = con.QueryFirstOrDefault<t1>("select * from t1 where id = @id", new { id });
- Console.ReadKey();
- }
随着移动端的兴起, 有了表情的出现, 所以从 MySQL 5.5.3 开始, 引入 utf8mb4 字符集每个字符最多可使用 4 个字节, 支持补充字符, 对于 BMP 字符, utf8 [utf8mb3]和 utf8mb4 具有相同的存储特征: 相同的代码值, 相同的编码, 相同的长度, 对于补充字符, utf8 [utf8mb3]根本无法存储该字符, 而 utf8mb4 需要 4 个字节来存储它, 由于 utf8 [utf8mb3]根本无法存储字符, 因此在 utf8 [utf8mb3]列中没有任何补充字符. 接下来我们在针对 JSON 类型配置为 utf8 编码的情况下, 我们来插入表情, 此时会发现也是可以的. 我们是可以获取对应字符的字节数, 比如如下哭笑不得的表情为 4 个字节:
var emotion = Encoding.UTF8.GetByteCount("");
其实针对 JSON 类型获取数据乱码的情况早就有人提出过相关 bug, 详见地址《 https://bugs.mysql.com/bug.php?id=81677 》, 不过官方一直没有任何回复, 至少通过上述测试出来的结果对于 utf8 存储表情也可以, 到底具体情况咋回事, 我们还是看看 8.0 版本以对 utf8 编码描述为准, 详情请见《》, 对于 utf8 编码的描述依然还是最多可存储 3 个字节, 如下:
别忘记, 还有注意: utf8[utf8mb3]字符集已被弃用, 并会在将来的 MySQL 版本中移除, 请改用 utf8mb4, 尽管 utf8 当前是 utf8mb3 的别名, 但在某些时候 utf8 将成为对 utf8mb4 的引用, 为避免对 utf8 的含义含糊不清, 请考虑为字符集引用显式指定 utf8mb4 而不是 utf8. 所以到此我们已明了, 针对 8.0 版本中的 utf8 编码虽说最多可支持 3 个字节, 但是, 会将 utf8 成为 utf8mb4 的引用, 如此就不难理解为何上述将表配置为 utf8 编码时, 对于 JSON 类型的不在常用 BMP 字符进行数据存储和表情皆没问题.
总结
通过此文对 utf8 编码的 JSON 类型的数据出现中文乱码的问题的学习才算告一段落, 原来版本问题使得 utf8 存在对 utf8mb4 编码的引用, 知其然, 知其所以然, 嗯, 大概是这么个道理. 发表博客的好处就在这里, 没有批评和指正, 哪来的更进一步呢.
来源: https://www.cnblogs.com/CreateMyself/p/12587426.html