ASCII、Unicode、utf-8、utf-16、utf-32

理解ASCII、Unicode、utf-8、utf-16、utf-32

 

编码与解码

在计算机中,信息是由 0和1组成的二进制 进行传递的,将我们看到的字符转化为二进制数字的过程就是编码,反之将二进制数字转换为字符的过程是解码。

字符集

ASCII和Unicode分别是两种不同的编码方式,通过某一种编码方式进行编码所组成的集合称为字符集。如,字符通过Unicode编码所组成集合称为Unicode字符集。

字符编码

对于一个字符集来说要正确编码转码一个字符需要三个关键元素:字库表(character repertoire)、编码字符集(coded character set)、字符编码(character encoding form)。

字库表,决定了整个字符集能够展现表示的所有字符的范围。

编码字符集,即用一个编码值来表示一个字符在字库中的位置。如Unicode。

字符编码,将编码字符集和实际存储数值之间的转换关系。一般会将编码字符集的值作为编码后的值直接存储。如UTF-8。

看到这里,可能很多读者都会有和我当初一样的疑问:字库表编码字符集看来是必不可少的,那既然字库表中的每一个字符都有一个自己的序号,直接把序号作为存储内容就好了。为什么还要多此一举通过 字符编码把序号转换成另外一种存储格式呢?其实原因也比较容易理解:统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。例如中文地区的程序几乎不会需要日语字符,而一些英语国家甚至简单的ASCII字库表就能满足基本需求。而如果把每个字符都用字库表中的序号来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。算的直接一些,同样一块硬盘,用ASCII可以存1500篇文章,而用3字节Unicode序号存储只能存500篇。于是就出现了UTF-8这样的变长编码。在UTF-8编码中原本只需要一个字节的ASCII字符,仍然只占一个字节。而像中文及日语这样的复杂字符就需要2个到3个字节来存储。

ASCII

由于计算机是美国人发明的,并没有考虑其他国家的字符,最早的编码方式便是ASCII。0 - 255被用来表示大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码。

Unicode

Unicode编码定义了这个世界上几乎所有字符,而且Unicode还兼容了很多老版本的编码规范,例如刚刚讲过的 ASCII码。Unicode 编码 发展到今天 扩展到了 21 位。详见http://en.wikipedia.org/wiki/Unicode

Unicode也为了每个字符发了一张身份证,这张“身份证”上有一串唯一的数字ID确定了这个字符。这串数字在整个计算机的世界具有唯一性,Unicode给这串数字ID起了个名字叫 码点码点 经过映射后得到的二进制串的转换格式单位称之为 码元码点就是一串二进制数,码元 就是切分这个二进制数的方法。

Unicode的编号从 0000开始一直到 10FFFF共分为16个Plane,每个Plane中有65536个字符(正好填充2个字节,16位)。0 号平面叫做基本多文种平面( BMP, Basic Multilingual Plane ),涵盖了几乎所有你能遇到的字符,除了 emoji(emoji位于1号平面 ),其它平面叫做补充平面,大多是空的。

UTF

Unicode转换格式(Unicode Transformation Formats,即UTF),是为了解决码点在计算机中存储方式而设计的。

UTF-32

UTF-32是最好理解的一个了。UTF-32也就是说它的码元是32位,每32位去读一下码点,而码点是Unicode给字符的编码,前面也说了,最长才21位,因此每一个 UTF-32 值都可以直接表示对应的码点。

UTF-16

UTF-16的码元是16位的,也就是说每16位去读一下码点,获取码点的前16位数字,直到读取完成。由于BMP 几乎包括了所有常见字符,UTF-16 一般需要 UTF-32大约 一半的空间。至于其它平面里很少使用的码点都是用两个 16 位的码元来编码的。

UTF-8

UTF-8 使用一到四个字节来编码一个码点。从 0 到 127 的这些码点直接映射成 1 个字节(对于只包含这个范围字符的文本来说,这一点使得 UTF-8 和 ASCII 完全相同)。接下来的 1,920 个码点映射成 2 个字节,在 BMP 里所有剩下的码点需要 3 个字节。Unicode 的其他平面里的码点则需要 4 个字节。UTF-8 是基于 8 位的码元的。UTF-8 是基于 8 位的码元的,因此它并不需要关心字节顺序(因为字节就是8位的呀,其它UTF-16和UTF-32在不同的机器编译环境下需要考虑字节的顺序问题)

UTF-8的编码实现方法

UTF-8编码为变长编码。最小编码单位为一个字节。一个字节的前1-3个bit为描述性部分,后面为实际序号部分。

  • 如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分代表在Unicode中的序号。

  • 如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分代表在Unicode中的序号。且第二个字节以10开头

  • 如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。110之后的所有部分代表在Unicode中的序号。且第二、第三个字节以10开头

  • 如果一个字节以10开头,那么代表当前字节为多字节字符的第二个字节。10之后的所有部分代表在Unicode中的序号。

具体每个字节的特征可见下表,其中 x代表序号部分,把各个字节中的所有 x部分拼接在一起就组成了在Unicode字库中的序号

 

Byte1Byte2Byte3
0xxx xxxx    
110x xxxx 10xx xxxx  
1110 xxxx 10xx xxxx 10xx xxxx

可以得出这样一个规律:

  • 3个字节的UTF-8十六进制编码一定是以 E开头的

  • 2个字节的UTF-8十六进制编码一定是以 CD开头的

  • 1个字节的UTF-8十六进制编码一定是以比 8小的数字开头的

Emoji问题

前面也提到Emoji位于1号平面,是需要四个字节来编码的,Emoji在Unicode中位于 \u1F601- \u1F64F区段。这个显然超过了目前常用的UTF-8字符集的编码范围 \u0000- \uFFFF。Emoji表情随着IOS的普及和微信的支持越来越常见。下面就是几个常见的Emoji:

😂😄😀

那么Emoji字符表情会对我们平时的开发运维带来什么影响呢?最常见的问题就在于将他存入MySQL数据库的时候。一般来说MySQL数据库的默认字符集都会配置成UTF-8(三字节),而utf8mb4在5.5以后才被支持,也很少会有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显示。

 

posted @ 2019-10-25 20:04  dpj999  阅读(446)  评论(0编辑  收藏  举报