这个问题说起来其实挺简单的,但是很多刚接触的人因为这个问题而烦恼。其实就是对于计算机如何表示(存储)一个字符不理解。
它们的关系其实可以通过下图来表示:
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
,所以得到的字符自然就是错的了。
希望通过这篇文章,可以让大家理解编码问题,以及为什么会出现乱码。