Python中文社区ID:python-chinaUnicode——UniversalCode为了独立解决语言的编码问题,人们提出了Unicode编码方案。这个方案简单粗暴:把世界上所有语言的字符组合成Unicode。UCS-2和UCS-4这两个Unicode方案分别可以使用2^16和2^32空间:在外星人造访地球之前,应该够用了。看看几个字符的Unicode编码码点(codepoint)是什么:ls='abABgong★☆'print([ord(l)forlinls])result:[97,98,65,66,24041,9733,9734].可以看出字母abAB的Unicode码位与其ASCII码位是一致的,所以当字符为字母时两者是兼容的,汉字Gong的码位为24041(0x5de9),即与之前的GB系列编码47534(0xb9ae)不同,所以Unicode与GB系列编码并不完全兼容:只有ASCII是部分兼容的。各国人民使用Unicode编码后,扩容和乱码的问题将不复存在:人类所有的语言字符都有一个统一的码位,我们在交流中书写的每一个数字码都有一个唯一的字符和他对应。Python中的chr函数返回Unicode码位对应的字符。>>>print([chr(i)foriin[123,957,24041]])['{','ν','gong']那我们能不能用强大的Unicode来编码呢?>>>ls='abAB宫★☆'>>>ls.encode('Unicode')Traceback(mostrecentcalllast):File"",line1,inLookupError:unknownencoding:Unicode未知编码Unicode!这是因为没有UnicodeCode这种编码形式,Unicode只是一个码位表,它只是建立字符和整数之间的映射。至于整数码点(codepoints)如何存储成字节,先存储高位和低位,有没有特殊标志,Unicode没有直接决定,而是留给具体的编码去考虑这些细节:UTF-32、UTF-16和UTF-8。UTF-32四字节单元UTF-32,顾名思义,是一种使用32位,即四个字节来存储一个字符的编码方案。>>>'aAGong'.encode('utf-32LE')b'a\x00\x00\x00A\x00\x00\x00\xe9\x5d\x00\x00'可见,所有字符都用四个字节来存储:除了Unicode代码点外,每个字节都不用\x00填充。这种方法简单明了,Unicode码位直接填充,无需转换。但是大量的\x00造成了极大的浪费。有没有办法解决这种浪费?可以用两位压缩吗?UTF-16用UTF-16编码时是两个字节。>>>'aAGong'.encode('utf-16LE')b'a\x00A\x00\xe9\x5d'两个字节对于大多数Unicode代码点来说已经足够了,如果不够,系统会自动用四位表示。这是系统实现,我们不需要关心。UTF-16编码的字节序列和字符仍然可以一一映射。UTF-16其实有两种编码方式,分别是上面例子中的UTF-16LE和下面的UTF-16BE。测试:>>>'aAgong'.encode('utf-16BE')b'\x00a\x00A\x5d\xe9'基本相同,只是高低字节位置颠倒了。LE和BE后缀表示小端和大端。这是计算机内部关于字节的MSB(大重量字节)是放在字节的开头还是结尾的实现细节。在《格列佛游记》中,小人国的公民为了吃鸡蛋,先吃大头还是小头。它们相互对立,形成了大端和小端两个军事对立集团,并多次相互开战。那么Unicode编码的极限是两个字节吗?UTF-8可变长度字节编码可以使用可变字节数来存储文本吗?如果存储英文文本,每个字符只用一个字节即可;对于汉字,扩大它。这进一步节省了存储空间。答案是肯定的,这是变长编码UTF-8。>>>'aA公'.encode('utf-8')b'aA\xe5\xb7\xa9'这是目前最短的字节序列,因为aA分别存储为一个字节。需要注意的是,在UTF-32和UTF-16中,Gong的字节序列为0x5de9,而在UTF-8中,字节序列变为0xe5b7a9。由此可见,UTF-8编码并不是简单地将Unicode码位直接存储为字节序列,而是进行了一些转换。这些转换确保英文以一位存储,而较大的字符(例如中文)以多个字节存储。那么它是如何转换的呢?这部分UTF-8编码转换规则过于详细,可以略过。UTF-8实现可变长度编码。为了在解码时区分可变长度有多长,需要在字节序列中使用特殊的模板。UTF-8编码遵循以下规则:0x00-0x7F之间的码点,兼容ASCII码,单个字节直接存入如下模板0*********0x80-0x7ff,使用两个字节存储,字节模板为110*****10******0x800-0xffff之间,使用三个字节存储,字节模板为1110****10******10******0x10000-0x1fffff,四个字节用于存储,字节模板为11110***10******10******10******用汉字表示为比如它的Unicode码位是0x6c49,它的二进制位是110110001001001,它位于第三行范围内,所以需要三个字节来存储,写出模板,1110****10******10******,使用二进制,从右往左填充,不足的部分补零,结果为111001101011000110001001,十六进制值为0xe60xb70x89,所以UTF-8字节序列格式为0Xe6b789。让我们从UTF-8编码转换的细节回到UTF三种编码的长度。UTF三种编码的长度以上三种编码方式,由于没有使用压缩率,文件长度也不同。下面程序比较三种不同编码在文本为汉字和英文内容时的长度:es='abcdefghij'cs='莫愁前路无知己,天下无不认识国王。'codes=['utf-32le','utf-16le','utf-8']print([len(es.encode(code))forcodeincodes])print([len(cs.encode(code))forcodeincodes])输出为[40,20,10][64,32,48]可以看出,对于英文,UTF-8比UTF-16和UTF-32编码更有优势;对于汉字来说,它最有优势反而是UTF16编码。这是因为在UTF-16编码中,大部分汉字是用2个字节来存储的,而UTF-8中的汉字则需要3个字节来存储。在日常生活中,出于最大兼容性的考虑,UTF-8的使用最为广泛。至此,我们已经从ASCII码发展到GB系列编码,再到Unicode和对应的UTF系列编码,拥有了覆盖所有编码、不乱码、压缩率高的字符编码体系。可以用吗?不!因为我们只对文本本身进行了编码,并没有记录具体使用的编码:当我们发送一个文件时,除非我们告诉对方,否则对方不知道应该用什么编码打开。解决这个问题,我们留到下一篇文章分析。总结Unicode统一了世界上所有语言的字符。在Unicode的几种编码形式中;UTF-32很简单,但很浪费。UTF-16以两个字节为单位存储,节省空间。UTF-8使用一个字节直接存储,是效率和空间的平衡。