什么是字符集在介绍字符集之前,先了解一下为什么会有字符集。我们在电脑屏幕上看到的是物化的文字,但实际存储在电脑存储介质中的是二进制比特流。那么两者之间的转换规则就需要一个统一的标准。否则,我们的U盘插入老板的电脑,文件就会乱码;.于是为了实现转换标准,出现了各种字符集标准。简单的说,字符集规定了某个文本对应的二进制数的存储方式(编码)与一串二进制值代表的文本(解码)之间的转换关系。那么为什么会有这么多的字符集标准呢?这个问题其实很容易回答。问问自己,为什么我们在英国买到的插头不能用?为什么显示器同时有DVI、VGA、HDMI、DP那么多接口?许多规范和标准在最初制定时,并没有意识到这将是未来全球适用的规范,或者为了组织本身的利益,他们希望与现有标准有本质的区别。结果,有这么多的标准具有相同的效果但彼此不兼容。说了这么多,我们来看一个实际的例子。下面是dick这个词在各种编码下的十六进制和二进制编码结果。你有鸡巴的感觉吗?字符集十六进制编码对应的二进制数据UTF-80xE5B18C111001011011000110001100UTF-160x5C4C1011100010011000GBK0x8CC51000110011000101字符编码名称中的真实集合是什么生活中,一个字符集就是某种语言的名称。例如:英文、中文、日文。对于一个字符集,要对一个字符进行正确的编码和转码,需要三个关键要素:字符库、编码字符集和字符编码形式。字体表是一个相当于所有可读或可显示字符的数据库,字体表决定了整个字符集所能表示的所有字符的范围。编码字符集,即用一个编码值码点来表示一个字符在字体中的位置。字符编码,会对字符集与实际存储值的转换关系进行编码。一般来说,代码点的值直接存储为编码值。例如A在ASCII表中排在第65位,A编码后的值为01000001,即65转十进制后的二进制结果。看到这里,很多读者可能会有和我开头一样的疑问:字形表和编码字符集好像缺一不可,既然字形表中的每个字符都有自己的序号,直接用序号作为存储的内容就好了。何必把序列号通过字符编码转换成另一种存储格式呢?其实原因比较好理解:统一字库的目的是为了覆盖世界上所有的字符,但是在实际使用过程中会发现实际使用的字符比例很低与整个字体表相比。比如中文地区的程序几乎不需要日文字符,而在一些英语国家,即使是简单的ASCII字库表也能满足基本需求。而如果每个字符在字体表中都存储序号,则每个字符需要3个字节(这里以Unicode字体为例),那么对于原本只使用一个字符的ASCII编码的英语国家来说,显然需要额外增加一个成本(存储量的三倍)。说得直接一点,同一个硬盘可以存储1500篇ASCII文章,但是3字节的Unicode序列号只能存储500篇文章。于是就有了UTF-8这样的变长编码。在UTF-8编码中,原本只需要一个字节的ASCII字符仍然只占用一个字节。而像中文、日文这样复杂的字符只需要2到3个字节就可以存储。UTF-8和Unicode的关系看完上面两个概念的解释,说明UTF-8和Unicode的关系就比较简单了。Unicode就是上面提到的编码字符集,而UTF-8就是字符编码,也就是Unicode规则字体的一种实现形式。随着互联网的发展,对同一字体集的要求越来越迫切,Unicode标准自然而然地出现了。它几乎涵盖了每个国家语言中可能出现的所有符号和字符,并对其进行编号。请参阅:维基百科上的Unicode。Unicode编号从0000到10FFFF分为16个Plane,每个Plane有65536个字符。UTF-8只实现了第一个Plane。可见,虽然UTF-8是当今最被广泛接受的字符集编码,但它并没有涵盖整个Unicode字体库,这也导致它在某些场景下无法使用。难以处理特殊字符(如下所述)。UTF-8编码简介为了更好的理解背后的实际应用,这里简单介绍一下UTF-8的编码实现方法。即UTF-8的物理存储与Unicode序号的转换关系。UTF-8编码为可变长度编码。最小的代码单元(codeunit)是一个字节。一个字节的前1-3位是描述部分,后面是实际的序号部分。如果一个字节的第一位为0,则表示当前字符为单字节字符,占用一个字节的空间。0之后的所有部分(7位)代表Unicode中的序号。如果一个字节以110开头,则表示当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(7位)表示Unicode中的序号。而第二个字节以10开头,如果一个字节以1110开头,则表示当前字符是一个三字节字符,占用2个字节的空间。110之后的所有部分(7位)表示Unicode中的序号。而第二个和第三个字节以10开头。如果一个字节以10开头,则表示当前字节是一个多字节字符的第二个字节。10之后的所有部分(6位)表示Unicode中的序列号。每个字节的具体特征可以看下表,其中x代表序号部分,每个字节中的所有x部分拼接在一起就形成了Unicode字体中的序号。Byte1Byte2Byte30xxxxxxx110xxxxx10xxxxxx1110xxxx10xxxxxx10xxxxxx下面看三个UTF-8编码从一个字节到三个字节的例子:UTF-8编码的Unicode字体序列号的二进制UTF-8编码后的编码$0024010000100010010024¢00a200010100010001000100010001000100010001011101110AC001012010读者不难的E2s从上面到上面。简单介绍得出以下规则:3字节的UTF-8十六进制编码必须以E开头,2字节的UTF-8十六进制编码必须以E开头C或D开头的1字节UTF-8十六进制码必须以小于8的数字开头,为什么会出现乱码?:不同或不兼容的字符集被用于编码和解码。对应到现实生活中,就像英国人在纸上写下bless来表达祝福(编码过程)。而一个法国人拿到了这张纸,因为祝福在法语中是伤害的意思,所以他认为他想表达的是伤害(解码过程)。这是现实生活中的乱码情况。在计算机科学中,用UTF-8编码的字符用GBK解码。由于两个字符集的字形表不同,所以同一个汉字在两个字符表中的位置也不一样,最终会出现乱码。我们来看一个例子:假设我们使用UTF-8编码来存储孟孟这两个字符,会有如下转换:字符UTF-8编码的十六进制E5BE88孟E5B18C所以我们得到一串值如E5BE88E5B18C。在显示的时候,我们使用GBK解码来显示,通过查表可以得到如下信息:二字节的十六进制值GBK解码对应的字符E5BEhuan88E5炎B18C睂解码后得到huan炎睂所以A错结果,更重要的是,连字符的数量发生了变化。如何识别原本要表达的乱码要从乱码中破译出原来正确的文字,需要更深入地掌握各个字符集的编码规则。但原理很简单。这里以最常见的UTF-8在GBK中显示错误时出现的乱码为例,来说明具体的反解和识别过程。Step1编码假设我们在页面上看到了乱码之类的乱码,我们知道我们的浏览器目前使用的是GBK编码。那么在第一步中,我们可以将乱码通过GBK编码成二进制表达式。当然,查表的编码效率很低。我们也可以通过MySQL客户端直接使用如下SQL语句来进行编码工作:mysql[localhost]{msandbox}>selecthex(convert('环红睂'usinggbk));+-----------------------------------+|hex(convert('Huancup'usinggbk))|+-----------------------------------------+|E5BE88E5B18C|+--------------------------------------+1rowinset(0.01sec)step2recognition现在我们得到解码后的二进制字符串E5BE88E5B18C。然后我们逐字节解压。Byte1Byte2Byte3Byte4Byte5Byte6E5BE88E5B18C那么套用之前UTF-8编码介绍章节总结的规则,不难发现这6个字节的数据是符合UTF-8编码规则的.如果整个数据流都符合这个规则,我们可以大胆假设乱码前的编码字符集是UTF-8。Step3解码然后我们可以用UTF-8解码E5BE88E5B18C,查看乱码前的文字。当然我们也可以不查表直接通过SQL获取结果:mysql[localhost]{msandbox}((none))>selectconvert(0xE5BE88E5B18Cusingutf8);+---------------------------------+|转换(0xE5BE88E5B18Cusingutf8)|+-------------------------------------+|很屌|+------------------------------------+1rowinset(0.00sec)常见问题处理的Emoji所谓Emoji,就是位于Unicode的\u1F601-\u1F64F段的一个字符。这显然超出了目前常用的UTF-8字符集的编码范围\u0000-\uFFFF。Emoji表情随着IOS的普及和微信的支持越来越普遍。下面介绍几个常见的Emoji:那么Emoji角色对我们平时的开发和维护有什么影响呢?最常见的问题是当他存储在MySQL数据库中时。一般来说,MySQL数据库默认的字符集会配置为UTF-8(三个字节),5.5之后才支持utf8mb4,很少有DBA会主动把系统默认字符集改成utf8mb4。那么问题来了。当我们将需要4字节UTF-8编码的字符存入数据库时??,会报错:ERROR1366:Incorrectstringvalue:'\xF0\x9D\x8C\x86'forcolumn。如果仔细阅读上面的解释,那么这个错误就不难理解了。我们试图将一串Bytes插入到一列中,而这串Bytes的第一个字节是\xF0,这意味着它是一个四字节的UTF-8编码。但是当MySQL表列字符集配置为UTF-8时,无法存储这样的字符,所以会报错。那么我们如何解决这种情况呢?有两种方法:将MySQL升级到5.6或更高版本,将表字符集切换为utf8mb4。第二种方法是在将内容存入数据库之前先做一个过滤,将Emoji字符替换成特殊的文字编码,然后存入数据库。之后在从数据库中获取或者在前端展示的时候,将这个特殊的文字编码转化为Emoji展示。在第二种方法中,我们假设用-*-1F601-*-来替换4字节的Emoji,那么具体的python代码实现可以参考Stackoverflow上的答案。
