这个问题说起来其实挺简单的,但是很多刚接触的人因为这个问题而烦恼。其实就是对于计算机如何表示(存储)一个字符不理解。
它们的关系其实可以通过下图来表示:
1 | +-------->utf-8 |
我们知道,计算机只可以存储二进制的数据,所以,我们计算机如果需要存储字符(这里的字符包括英文字符、键盘上可见的字符、中文字符等等很多国家的字符),那么必须要有对应的二进制来表示(而二进制实际上我们可以人为的转化为一个数字)。
所以,我们要表示(存储)一个字符,就必须通过一个数字来表示,而unicode就用来做这件事情的。你可以理解为unicode编码实际上就是一张表,里面存储了所有数字->字符的关系,一个数字唯一的对应一个字符(反过来一个字符也唯一的对应一个数字)。
举个例子,汉字'严'对应的数字是4E25,这是十六进制(当然,你可以转换为十进制的你看起来顺眼的数字)。可以看出来要表示'严'这个字符,在计算机里面最少需要用两个字节来存储。为什么这里要用最少这个词呢?因为一个字符对应的unicode数字不一定就是它最终的存储形式。
因为计算机是不知道你表示'严'这个字符是用了两个字节。假设,我们在文件里面输入了如下字符串:
1 | a严 |
其中,第一个字符是英文字母'a',第二个字符是中文字符'严'。那么,这个字符串转化为unicode就是:
1 | 614E25 |
OK,这是十六机制表示的。其中,第一个字节61代表字符'a',第二和第三个字节一起代表字符'严'。
假设,我们直接以这种方式存储,这没问题对吧。但是,如果有一天,你拿到这串614E25,你知道它对应的字符串是什么吗?
是解析为61和4E25呢?还是614E和25呢?或者是61、4E、25呢?其实,我们都是无法得知的。所以,我们就需要一种规范来存储unicode。也就意味着,我们不可以简单的直接存储字符串对应的unicode串,而是需要规范化。
utf-8实际上就是unicode的一种规范。我们可以看看utf的全称:
1 | Unicode Transformation Format |
翻译过来就是:
1 | Unicode转换格式 |
以utf-8为例,unicode的存储格式如下:
1 | Unicode符号范围 | UTF-8编码方式 |
左边的是unicode,右边的是utf-8(也就是字符在内存中真正的存储形式)。
那么,这里的协议体现在哪里呢?如下:
1 | 1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。 |
我们还是以字符串:
1 | a严 |
为例。
这个字符串对应的unicode如下:
1 | 61 4E25 |
首先,我们需要把61这个unicode转换为utf-8的格式。因为61是在
1 | 0000 0000 ~ 0000 007F |
范围里面的。所以我们对应第一条规则,对应的utf-8为:
1 | 00000061 |
然后,我们把4E25这个unicode转换为utf-8的格式。因为4E25是在:
1 | 0000 0800 ~ 0000 FFFF |
范围里面的。所以我们对应第三条规则,对应的utf-8为:
1 | 11100100 10111000 10100101 |
所以,当我们在编辑器里面输入字符串:
1 | a严 |
会得到它的unicode:
1 | 614E25 |
然后,如果我们是通过utf-8保存的,那么会以如下utf-8格式存储:
1 | 00000061 11100100 10111000 10100101 |
所以,当我们下次打开这个文件的时候,我们只需要以utf-8的格式打开,就可以得到对应的字符串了。解析的过程如下:
1 | 00000061 11100100 10111000 10100101 |
首先读入一个字节:
1 | 00000061 |
发现,最左边的一位是0,所以,这是在规则:
1 | 0000 0000 ~ 0000 007F | 0xxxxxxx |
里面的,我们可以得到unicode:
1 | 61 |
因为unicode唯一的对应一个字符,所以我们可以得到字符'a'。
然后,我们继续读入下一个字节:
1 | 11100100 |
发现最左边的三位是三个1,这是在规则:
1 | 0000 0800 ~ 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
里面的,所以我们根据这条规则,从utf-8反解出它的unicode:
1 | 4E25 |
得到对应的字符'严'。
因此,我们就可以得到对应的字符串:
1 | a严 |
因为,utf-16、utf-32和utf-8有着不同的规则,所以,如果我们先以utf-8的格式保存文件,然后再以utf-16或者utf-32的格式打开,那么就可能会乱码。原因就是unicode对应的utf-*的规则不同,导致打开文件的时候,从utf-*反解出来的unicode错了,不是原来的那个unicode,所以得到的字符自然就是错的了。
希望通过这篇文章,可以让大家理解编码问题,以及为什么会出现乱码。