- Published on
字符编码指南
- Authors
- Name
- McDaddy(戣蓦)
关于CharSet你是否有以下疑问
字符集和字符编码是什么关系?
utf-8是什么东西?同理utf-16/32又是什么?
中文在我们的计算机里是怎么存储的?
为什么有时候展示会出现乱码?
我们的数据库和编程语言用的又是什么编码方式?
希望接下来的内容可以解答这些疑问
编码基本概念
字符
Character:就是一个单位的字形、类字形或符号。即各种文字和符号的总称。它可以是一个汉字一个英文字母,也可以是一个标点符号,甚至是一个emoji
字符集
Character Set:指多个字符的集合。各种字符集包含的字符内容肯定是不同的,我们熟悉的比如汉字GB2312、GBK,英文的比如ASCII,还有就是Unicode都是字符集
码点
Code Point:指字符在字符集中的唯一键。比如A
在ASCII表中的位置是65,那65就是A在ASCII这个字符集中的码点,一般来说码点都是连续的数字,简单理解就是一个字符在当前字符集里面的序号
字符编码
Character Encoding:指的是一种映射关系方式。它表示的是如何将一个字符用一种映射的方式存储在计算机中。它解决了几个问题
- 因为计算机的存储都是0101这样的形式,我们不知道读多少个字节或者位才是一个完整的字符,这里我们通过字符编码做到了统一的定义,即字符的读和写都遵守一个规则,类似钥匙和锁的关系,如果读写使用的是不同的编码方式,那就会出现乱码
- 确保字符集中的任何两个字符在内存中的表示都是不同的
总而言之,字符编码就是通过一个规则,把字符集中的某个字符,映射到内存结构里去,比如一个字符要存几个字节,同时在一种字符编码下每个字符在内存中的表示都必须是唯一的
ASCII
1960年代,美国指定的一套编码规则,一直沿用至今
它总共有128个字符,它的编码规则是
- 用一个字节来存储
- 第一位统一为0
- 后面7位就是编码表中码点数字的二进制
举个实际例子,我当前使用的字符编码是ASCII,此时我要存储字符A
,它在ASCII表中的码点是65,第一位为0,后面七位存编码后的内容,在这里它要存的就是65的二进制,即01000001
但是128个字符是明显无法满足真实世界的需求的。即使是在英语符号国家也无法得到满足,比如法语中的é,后来就由这个欧洲国家扩展出了一套扩展版的ASCII,称为EASCII(Extended ASCII),它就是利用了原先闲置的最高位,把字符数量扩展到了256个
但事实上即使扩展到258个字符,对于很多小语种国家来说还是不够的,它们的解决方法就是公用了这256个字符,即各玩各的
在法语编码中130代表é,而同样130在希伯来语中代表字母ג,在其他语言编码中又各有自己的字符。
而至于亚洲国家,很多都是象形文字,如汉字就有多达10w个字符,所以这个256个字符的方案就完全不可能满足
GB2312
在上世纪80年代,计算机开始在国内引入,一个难题就是计算机上无法展示中文,国家标准总局就发布了一个字符集称为GB2312,从1981年5月开始实施
它的思想是
- 它用两个字节来表示一个字符
- 完全兼容ASCII,所以从0到127这些码点的字符和ASCII完全一样
- 如果表示汉字,两个字节的范围都是0xA1
0xF7,换成十进制就是161247,所以能够组成(247 - 161) * (247 - 161)
差不多7千+的汉字(实际是收录了6763个汉字) - 在这其中还包括一些罗马希腊日文的字符
这样就基本解决了国人在计算机上使用汉字的问题了,但6763的汉字肯定是覆盖不到所有汉字的场景的,比如韡
字,就不在GB2312中
GBK
为了解决这个问题,微软推出了一个叫做GBK的字符集,其中的K就是拼音Kuo Zhan的缩写,它同样使用双字节编码,向下兼容了GB2312,最早应用于Windows 95简体中文版,它总共收录了20,902个汉字
虽然它的名气不小,但它并不是由国家权威发布的,但却被国家单位定义为指导性技术文件,所以使用的范围还是很大的
但即使这样还是只覆盖了所有汉字的五分之一而已,所以在2005年国家又推出了一个GB18030的字符集,里面收录了超过7w个汉字,具体的编码方式这里就不展开了
在历史的同一时间段,在我们的台湾、香港澳门等地区,也有中文使用的需要,但又没有使用大陆的标准,自身演进出了一个Big5的字符集,收录了大约13000个汉字
Unicode
在那个历史阶段,基本每个非英语字符语种国家都会搞一套自己的字符编码来显示自己国家的语言,但这样带来的结果就是你的文字通过网络或者其他传输介质到了我的电脑上,显示出来就是乱码,究其原因还是两边的字符编码不同,为了解决这个问题,Unicode应运而生
Unicode也称为万国码,是业界的一种标准,它可以让电脑可以体现超过数十种文字,目前Unicode已经包含了超过10w的字符
这里需要注意的是Unicode仅仅是字符集,而上面介绍的ASCII、GB2312、GBK等本身即是字符集同时又是字符编码,也就是说它只定义了里面包含了哪些字符,同时每个字符的码点是什么,但是具体是怎么在计算机里存储是没有定义的
UTF-8
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码定长码,它可以用来表示任何Unicode字符,且与ASCII兼容。
所谓可变长就是UTF-8使用一到四个字节位每个字符编码:
- ASCII中的0~127码点的字符,用一个字符表示
- 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码(Unicode范围由U+0080至U+07FF)。
- 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。
- 其他极少使用的Unicode辅助平面的字符使用四字节编码。
UTF-8 的编码规则很简单,只有两条:
- 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
- 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx x占7位
0000 0080-0000 07FF | 110xxxxx 10xxxxxx x占11位
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx x占16位
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx x占21位
我们以陈
字为例,看下怎么算出它的UTF-8编码
- 我们要查出
陈
字的Unicode,可以通过这个网址查看,得到结果U+9648
,就是十六进制的9648 - 我们把十六进制的9648转成二进制得到
1001011001001000
总共占了16位,我们要做的就是把这16位数从后往前填到上面的x中,发现符合三个字符的范围 - 结果得到一个
11101001 10011001 10001000
的二进制数字,这就是UTF-8在计算机中存储陈的样子
1110xxxx 10xxxxxx 10xxxxxx
1001 011001 001000
使用这个工具可以验证上面的结果
那UTF-16、UTF-32又是什么?
它们也是针对Unicode的编码方案。但它们都是固定长度的编码
UTF-16: 固定两个字节表示字符
UTF-32: 固定四个字节表示字符
事实上这两种编码在现实中基本用的很少,原因是我们客观世界主要存储的还是以英文内容居多,UTF-8因为是变长所以存储英文占用的空间就很小,而另外两种就可能要占更多的空间
扩展
为什么不建议在 MySQL 中使用 UTF-8?
因为MySQL 的“utf8”实际上不是真正的 UTF-8。
“在MySQL中,“utf8”编码只支持每个字符最多三个字节,而真正的 UTF-8 是每个字符最多四个字节。
这就导致可能存入的内容变成乱码。所以在RDS中我们需要把字符编码设置成utf8mb4
我们的编程语言用的是什么编码?
- Java 的
String
类型使用 UTF-16 编码,每个字符占用 2 字节。这是因为 Java 语言设计之初,人们认为 16 位足以表示所有可能的字符。然而,这是一个不正确的判断。后来 Unicode 规范扩展到了超过 16 位,所以 Java 中的字符现在可能由一对 16 位的值(称为“代理对”)表示。 - JavaScript 和 TypeScript 的字符串使用 UTF-16 编码的原因与 Java 类似。当 1995 年 Netscape 公司首次推出 JavaScript 语言时,Unicode 还处于发展早期,那时候使用 16 位的编码就足以表示所有的 Unicode 字符了。
- Go 语言的
string
类型在内部使用 UTF-8 编码。Go 语言还提供了rune
类型,它用于表示单个 Unicode 码点。
ANSI是什么鬼?
这个词相信很多人都见过,但很少人知道是什么。其实它是American National Standards Institute的缩写,但实际上跟美国没有太大关系,ANSI编码被称为本地编码,在不同的地区有不同的含义,在中文Windows系统里它就是GBK,在日文Windows里它就是Shift_JIS
什么CJK?
中日韩越统一表意文字(CJKV Unified Ideographs),指的是这四国语言中的汉字或仿汉字的集合,我们知道同一个汉字可能会在日语中也出现,写法是一模一样的,所以Unicode不会为两个字各自建立独立码点,而是两种语言用同一个码点来表示一个字符
https://www.unicode.org/charts/
https://www.qqxiuzi.cn/bianma/zifuji.php
为什么有些汉字就是打不出来?
比如说𮧵
字,用我们常用的输入法都是打不出来的,原因是我们常用的输入法使用的都是GB18030这样的字符集,很多生僻字就是打不出来的
为什么有时候编码没有问题还是显示不出来?
还是同𮧵
字,即使有时候编码是没问题的,显示起来看起来就像麻将里的白板,这里可能得原因是字体问题,除了黑体和宋体之外,很多字体都是没有提供完整的字符录入,如果使用一些美术字体去展示就会无法显示。比如有些手机机型可以显示而有些不能,这个只能通过字体的补丁来解决