前言
编解码问题常常困扰,实际上理解的关键就是明白2点:
1.电脑只知道二进制不识别中英文等
2.最初将我们的语言转变为电脑识别的二进制办法是ASCII码,它是为英语设计的,无法做到适配英语外的语言,所以出现了各种编解码机制
从一张图开始编解码讲解:
Unicode:Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。
因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),0 - 255被用来表示大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122。
如果要表示中文,显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。
类似的,日文和韩文等其他语言也有这个问题。为了统一所有文字的编码,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填为0就可以。
需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
比如,汉字严的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。
这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的
它们造成的结果是:1)出现了 Unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 Unicode。2)Unicode 在很长一段时间内无法推广,直到互联网的出现。
GBK: 汉字国标扩展码,基本上采用了原来GB2312-80所有的汉字及码位,并涵盖了原Unicode中所有的汉字20902,总共收录了883个符号, 21003个汉字及提供了1894个造字码位。 Microsoft简体版中文Windows 95就是以GBK为内码,又由于GBK同时也涵盖了Unicode所有CJK汉字,所以也可以和Unicode做一一对应。
GB码,全称是GB2312-80《信息交换用汉字编码字符集 基本集》,1980年发布,是中文信息处理的国家标准,在大陆及海外使用简体中文的地区(如新加坡等)是强制使用的唯一中文编码。P-Windows3.2和苹果OS就是以GB2312为基本汉字编码, Windows 95/98则以GBK为基本汉字编码、但兼容支持GB2312。GB码共收录6763个简体汉字、682个符号,其中汉字部分:一级字3755,以拼音排序,二级字3008,以偏旁排序。该标准的制定和应用为规范、推动中文信息化进程起了很大作用。
GBK编码是中国大陆制订的、等同于UCS的新的中文编码扩展国家标准。GBK工作小组于1995年10月,同年12月完成GBK规范。该编码标准兼容GB2312,共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一库。
Utf-8:
如果UNICODE字符由2个字节表示,则编码成UTF-8很可能需要3个字节。而如果UNICODE字符由4个字节表示,则编码成UTF-8可能需要6个字节。用4个或6个字节去编码一个UNICODE字符可能太多了,但很少会遇到那样的UNICODE字符。
UTF-8编码规则:如果只有一个字节则其最高二进制位为0,这使得utf-8可以与ASCII兼容,是其巨大的优势;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的字节数,其余各字节均以10开头。UTF-8转换表表示如下:
Unicode/UCS-4 | bit数 | UTF-8 | byte数 | 备注 |
---|---|---|---|---|
0000 ~007F | 0~7 | 0XXXXXXX | 1 | NA |
0080 ~07FF | 8~11 | 110XXXXX 10XXXXXX |
2 | NA |
0800 ~FFFF | 12~16 | 1110XXXX 10XXXXXX 10XXXXXX |
3 | 基本定义范围:0~FFFF |
10000~1FFFFF | 17~21 | 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX |
4 | Unicode6.1定义范围:0~10 FFFF |
200000 ~3FF FFFF | 22~26 | 111110XX 10XXXXXX 10XX XXXX 10XXXXXX 10XXXXXX |
5 | 说明:此非unicode编码范围,属于UCS-4 编码早期的规范UTF-8可以到达6字节序列,可以覆盖到31位元(通用字符集原来的极限)。 |
4000000~7FFFFFFF | 27~31 | 1111110X 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX |
6 |
尽管如此,2003年11月UTF-8 被 RFC 3629 重新规范,只能使用原来Unicode定义的区域, U+0000到U+10FFFF。 根据规范,这些字节值将无法出现在合法 UTF-8序列中 |
实际表示ASCII字符的UNICODE字符,将会编码成1个字节,并且UTF-8表示与ASCII字符表示是一样的。所有其他的UNICODE字符转化成UTF-8将需要至少2个字节。每个字节由一个换码序列开始。第一个字节由唯一的换码序列,由n位连续的1加一位0组成, 首字节连续的1的个数表示字符编码所需的字节数。
Unicode转换为UTF-8时,可以将Unicode二进制从低位往高位取出二进制数字,每次取6位,如上述的二进制就可以分别取出为如下示例所示的格式,前面按格式填补,不足8位用0填补。
注:Unicode转换为UTF-8需要的字节数可以根据这个规则计算:如果Unicode小于0X80(Ascii字符),则转换后为1个字节。否则转换后的字节数为Unicode二进制位数减1再除以5。
示例
UNICODE uCA(1100 1010) 编码成UTF-8将需要2个字节:
uCA -> C3 8A, 过程如下:
uCA(1100 1010)处于0080 ~07FF之间,从上文中的转换表可知对其编码需要2bytes,即两个字节,其对 应 UTF-8格式为: 110X XXXX10XX XXXX。从此格式中可以看到,对其编码还需要11位,而uCA(1100 1010)仅有8位,这时需要在其二进制数前补0凑成11位: 000 1100 1010, 依次填入110X XXXX 10XX XXXX的空位中, 即得 1100 0011 1000 1010(C38A)。
同理,UNICODE uF03F (1111 0000 0011 1111) 编码成UTF-8将需要3个字节:
u F03F -> EF 80 BF,对应格式为:1110XXXX10XX XXXX10XX XXXX,编码还需要16位,将1111 0000 0011 1111(F03F)依次填入,可得 1110 1111 1000 0000 1011 1111(EF 80 BF)。
Unicode 16进制 | Unicode 2进制 | bit数 | UTF-8 2进制 | UTF-8 16进制 |
---|---|---|---|---|
CA | 11001010 | 8 | 1100001110001010 | C3 8A |
F03F | 1111000000111111 | 16 | 111011111000 000010111111 | EF 80 BF |
python代码示例
# coding=utf-8
def code_transfer(_str, transfer_mode, encoding=True):
if encoding:
new_str = _str.encode(transfer_mode)
print "Encoding %(_str)s to %(str)s by %(transfer_mode)s" % {"_str": repr(_str), "str": repr(new_str), "transfer_mode": transfer_mode}
else:
new_str = _str.decode(transfer_mode)
print "Decoding %(_str)s to %(str)s by %(transfer_mode)s" % {"_str": repr(_str), "str": repr(new_str), "transfer_mode": transfer_mode}
# encode
code_transfer(u'\u4e2d\u56fd', "utf-8", encoding=True)
code_transfer(u'\u4e2d\u56fd', "gb2312", encoding=True)
code_transfer(u'\u4e2d\u56fd', "gbk", encoding=True)
code_transfer(u'俄罗斯', "gbk", encoding=True)
# decode
code_transfer('俄罗斯', "utf-8", encoding=False)
code_transfer('\xd6\xd0\xb9\xfa', "gb2312", encoding=False)
code_transfer('\xd6\xd0\xb9\xfa', "gbk", encoding=False)
#
code_transfer('ssss', "utf-8", encoding=False)
code_transfer('ssss', "gbk", encoding=True)
结果示例(IDE默认UTF-8)
Encoding u'\u4e2d\u56fd' to '\xe4\xb8\xad\xe5\x9b\xbd' by utf-8
Encoding u'\u4e2d\u56fd' to '\xd6\xd0\xb9\xfa' by gb2312
Encoding u'\u4e2d\u56fd' to '\xd6\xd0\xb9\xfa' by gbk
Encoding u'\u4fc4\u7f57\u65af' to '\xb6\xed\xc2\xde\xcb\xb9' by gbk
Decoding '\xe4\xbf\x84\xe7\xbd\x97\xe6\x96\xaf' to u'\u4fc4\u7f57\u65af' by utf-8
Decoding '\xd6\xd0\xb9\xfa' to u'\u4e2d\u56fd' by gb2312
Decoding '\xd6\xd0\xb9\xfa' to u'\u4e2d\u56fd' by gbk
Decoding 'ssss' to u'ssss' by utf-8
Encoding 'ssss' to 'ssss' by gbk
总结
1.ASCII编码只能支持英文和其他一些字符无法支持中文及其它语言,unicode为此而生
2.unicode只是符合和字符之间转换的标准,并没有规定存储方式,并不是一种直接将字符和二进制之间进行编码的方式,编码方式主要是以utf-8为主的一系列编码方式,utf-8是解决了unicode的两个缺点(英文字母的多字节、计算机区别不了unicode和ASCII码)
(ps:解决的版本通过变长字节和与ASCII码一致的1字节内与ASCII码统一)
3.字节显示成中文或其他语言是由字符集来完成,代码里面的注释、字符串若是写成中文,IDE会将其翻译成unicode编码,而显示给人看的时候会成为规定编码格式字符。