其实这是个老生常谈的问题了,相信你们在第一次遇到Unicode编码问题时,都会在网上搜索一通,
找到几个解释,虽然有点杂乱,但仍是感受本身明白了些什么,而后就继续忙别的事情.
而我之因此就这个问题专门写一篇文章,缘由是前两天在与公司一位有十几年工做经验的JAVA程序员对接
API时, 我问他返回的汉字是什么编码的, 而他回答说"直接返回unicode". 一个如此有经验的老程序员
对这种基本问题都不甚清楚, 所以我以为仍是有必要好好说一下这个问题的.python
在介绍他们之间的区别时, 咱们先讲下什么是Unicode. 简单来讲,Unicode是一个字符集(character set),
和ASCII同样, 其做用是用一系列数字来表示字符(character), 这些数字有时也称为码点(code points).
在PC刚出来的时候,使用英文的几位先驱认为计算机须要表示的字符很少,26个英文字母加几个回车换行等
特殊符号,总共一百个字符顶天了,因而就有了ASCII. ASCII码的大小为1个字节,定义了128个字符,
分别表示为0-127. 好比字符'A'的码点为65,回车符'\n'的码点为10, 以下所示:程序员
>>> ord('A') 65 >>> ord('0') 48 >>> ord('\n') 10
固然, 后来人们发现, 世界上的字符远远不止128个, 所以就须要一个新的字符集能表示世上全部的字符,
包括一个英文字符,一个汉字字符,一个象形文字等. 这个字符集就是Unicode. Unicode前向兼容了ASCII,
最多能够表示2^21(大概200万)个字符,已经足够囊括当今全部国家的文字, 以下所示:编码
>>> u'ソ' u'\u30bd' >>> u'龍' u'\u9f8d' >>> u'A' u'A'
目前unicode字符集表示完全部字符后还有剩余, 这些暂时用不到的部分一般用占位符FFFD
表示.spa
有了字符集, 咱们如今能够用任意数字来表示现实中的字符了. 但字符要保存在计算机中,必需要先通过编码.
有人问, 数字直接保存在内存里不就好了吗? 可是用多少个字节表示一个数字,以及每一个字节的范围这都是须要
预先约定的,这种约定就叫编码. 假如咱们有四个数字,1,2,3,4要保存在计算机里, 若是约定了utf-8编码,
那么在内存中的表示则以下:设计
00000001 00000010 00000011 00000100
其余的编码规则有utf-16,gb2312,gbk等,具体的编码规则不在本文的范围内,想要深刻了解的能够在网上查阅相关的文档.
所以,咱们能够看到,若是不按照约定的规则来解码,就颇有可能没法还原出原来的数据,也就是咱们常常遇到的"乱码".
下面以几个例子来简单说明:code
>>> u'你好' u'\u4f60\u597d' >>> u'你好'.encode('utf8') '\xe4\xbd\xa0\xe5\xa5\xbd' >>> u'你好'.encode('gbk') '\xc4\xe3\xba\xc3' >>> u'你好'.encode('utf8').decode('gbk') u'\u6d63\u72b2\u30bd' >>> print u'你好'.encode('utf8').decode('gbk') 浣犲ソ
如上面的代码所示, "你好"两个汉字字符的unicode分别为4f60和597d, utf-8编码后占6个字节, 而gbk编码后占4个字节.
若是用utf8编码后错误地用gbk来解码, 就会获得3个unicode码点,分别表示字符浣
,犲
和ソ
;而若是用gbk编码后
错误地用utf8来解码, 则在解码第二个字符时没法凑够3个字节, 所以会获得未知的结果, 甚至会由于内存越界访问引发程序异常.内存
注: 本文的python代码示例是在Linux Terminal下运行的, 所以默认为utf-8编码, 若是你是在Windows cmd里运行,
则一般默认GBK编码, 所以乱码会在不一样地方出现:)utf-8
知道字符编解码的用法以后,咱们就能够解释一下常见的一些乱码由来了, 好比在Windows下,未初始化的栈会初始化为0xcc,
未初始化的堆内存会初始化为0xcd, 能够看到前者为'烫'的gbk编码,然后者正好为'屯'的gbk编码, 以下所示:unicode
>>> u'烫' u'\u70eb' >>> u'烫'.encode('gbk') '\xcc\xcc' >>> u'屯' u'\u5c6f' >>> u'屯'.encode('gbk') '\xcd\xcd'
前面也说过, unicode暂时没用到码点会用占位符FFFD来表示, 若是这个占位符被错误解析, 就会被看成有意义的内容了:文档
>>> u'\uFFFD'.encode('utf8') '\xef\xbf\xbd' >>> u'锟斤拷'.encode('gbk') '\xef\xbf\xbd\xef\xbf\xbd' >>> print (u'\uFFFD'.encode('utf8')*2).decode('gbk') 锟斤拷
能够看到,汉字"锟斤铐"(Unicode)的gbk编码分别为\xef\xbf, \xbd\xef和\xbf\xbd, 正好是unicode码FFFD的utf8编码
的叠加, 所以若是平时遇到多个utf8编码的Unicode占位符且不巧用了gbk的方式解码,那就会看到熟悉的锟斤铐了.
在Windows的Notepad.exe中, 保存文件的格式能够看到有以下几种:
可刚刚不是说Unicode只是字符集吗, 为何上面显示能够保存为Unicode"编码"? 好吧, 其实这是Windows在命名上一个操蛋的
地方. 由于Windows内部使用UTF-16小端(UTF-16LE)做为默认编码,而且认为这就是Unicode的标准编码格式. 在Windows的世界中,
存在着ANSI字符串(在当前系统代码页中, 不可拓展),以及Unicode字符串(内部以UTF16-LE编码保存). 所以notepad里所说的
Unicode大端,其实就是UTF16-BE.
这其实也不怪Windows, 由于这是在Unicode出现的早期设计的, 那时咱们还没意识到UCS-2的不足, 并且UTF-8尚未被发明出来.
这也是为何Windows对UTF8的支持如此之差的缘由之一吧.
说了这么多, 如今让咱们回到一开始的问题, 若是有人问你"Unicode,GBK和UTF-8有什么区别?", 我想你应该知道该怎么回答了吧: Unicode是 一种字符集, 而GBK和UTF-8都是编码, 所以Unicode和后二者不是一类事物, 是没法进行对比的.