字符集与编码

编码

  ascii编码是针对公共字符的编码,阿拉伯数字、英文字母,还有一些特殊字符;而如GB2312是针对汉字的编码,使用两个字节表示一个汉字

Acsii

  Ascii:是基于拉丁字母的一套电脑编码系统,主要用来显示现代英语;

  Ascii字符集:包括控制字符(回车键、退格、换行键等);可显示字符(英文大小写字符、阿拉伯数字和西文符号)。一开始是128字符,共7位,后来为了表示更多欧洲常用字符,对其进行了扩展,使用了8位,共256个字符;虽然解决了欧洲的问题,但是对于其他有些语言没有办法;

  Ascii编码:将ascii字符集转换为二进制的规则;

Unicode

  世界上存在着很多编码标准,同一个二进制数,可能被不同编码标准解释成不同符号,那么如果全世界拥有一种统一的编码标准就好了;然后就有了Unicode,它的规模很大;需要注意,它只是一个字符集,只规定了符号的二进制代码,却没有规定这个二进制如何存储;即该需要多少字节,那么这就相当不好,因为他没有一个统一的存储的标准;而比如ascii都规定了一个字节存储;

  如汉字的Unicode是十六进制4E25,二进制共15位,那么表示他至少需要2个字节;那么表示其他符号呢,可能需要3、4个字节,可能更多;

  那么如何区别Unicode和ASCII,计算机怎么知道三个字节位一个符号,而不是分别表示三个符号;第二个问题,英文字符表示用一个字节就够了,如果Unicode规定统一用3个或4个字节,那么每个英文字符前都有2个到3个字节是0,这是极大的浪费。

  Unicode可以通过不同的字符编码实现。Unicode标准定义了UTF-8UTF-16UTF-32,并且正在使用其他几种编码。最常用的编码是UTF-16 的前身UTF-8,UTF-16和UCS -2(不完全支持Unicode)。GB18030在中国已标准化,并且完全实现了Unicode,而不是正式的Unicode标准。

Unicode定义了两种映射方法:Unicode转换格式(UTF)编码和通用编码字符集(UCS)编码

Utf-8

  UTF-8 是在互联网上使用最广的一种 Unicode 的实现方式。;这是一种变长编码;

  utf-8的编码实现如下,即utf-8的物理存储与Unicode序号的转换关系,utf-8编码为边长字节。最小编码单位(code unit)为一个字节,每个字节的前1-3个bit为描述性部分,后面为实际序号部分(序号即Unicode字符库中的序号)。

  • 如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分(7个bit)代表在Unicode中的序号。
  • 如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(5个bit)加上后一个字节的除10外的部分(6个bit)代表在Unicode中的序号。且第二个字节以10开头
  • 如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。110之后的所有部分(5个bit)加上后两个字节的除10外的部分(12个bit)代表在Unicode中的序号。且第二、第三个字节以10开头
  • 如果一个字节以10开头,那么代表当前字节为多字节字符的第二个字节。10之后的所有部分(6个bit)和之前的部分一同组成在Unicode中的序号。

  这里和ip地址的规划、变长操作码的指令字结构很相似;

1
2
3
4
5
6
7
Unicode符号范围     |        UTF-8编码方式
(十六进制) | (二进制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

  这里把阮一峰大佬博客里的utf-8编码拿了过来;

编码转换

  对于程序员来说,要实现两个编码(A与B)之间的转换可以这样做:A->二进制->B;

乱码

  乱码的出现是因为编码和解码的时候使用了不同或者不兼容的字符集。对应到真实生活中,就好比是一个英国人为了表示祝福在纸上写了bless(编码过程)。而一个法国人拿到了这张纸,由于在法语中bless表示受伤的意思,所以认为他想表达的是受伤(解码过程)。

  当读取文件时,计算机会猜测应该使用哪种编码方式来对二进制进行编码,如果猜对了那么就会得到原来的字符串,如果猜错了就会产生乱码;

要从乱码字符中反解出原来的正确文字需要对各个字符集编码规则有较为深刻的掌握。但是原理很简单,这里用最常见的UTF-8被错误用GBK展示时的乱码为例,来说明具体反解和识别过程。

第1步 编码

假设我们在页面上看到寰堝睂这样的乱码,而又得知我们的浏览器当前使用GBK编码。那么第一步我们就能先通过GBK把乱码编码成二进制表达式。当然查表编码效率很低,我们也可以用以下SQL语句直接通过MySQL客户端来做编码工作:

1
2
3
4
5
6
7
mysql [localhost] {msandbox} > select hex(convert('寰堝睂' using gbk));
+-------------------------------------+
| hex(convert('寰堝睂' using gbk)) |
+-------------------------------------+
| E5BE88E5B18C |
+-------------------------------------+
1 row in set (0.01 sec)

第2步 识别

现在我们得到了解码后的二进制字符串E5BE88E5B18C。然后我们将它按字节拆开。

Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6
E5 BE 88 E5 B1 8C

然后套用之前UTF-8编码介绍章节中总结出的规律,就不难发现这6个字节的数据符合UTF-8编码规则。如果整个数据流都符合这个规则的话,我们就能大胆假设乱码之前的编码字符集是UTF-8

第3步 解码

然后我们就能拿着E5BE88E5B18C用UTF-8解码,查看乱码前的文字了。当然我们可以不查表直接通过SQL获得结果:

1
2
3
4
5
6
7
mysql [localhost] {msandbox} ((none)) > select convert(0xE5BE88E5B18C using utf8);
+------------------------------------+
| convert(0xE5BE88E5B18C using utf8) |
+------------------------------------+
| 很屌 |
+------------------------------------+
1 row in set (0.00 sec)

Reference

  1. 阮一峰的网络日志:字符编码笔记:ASCII,Unicode 和 UTF-8
  2. 你需要知道的字符串编码