文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

用 Python 理清编码问题:Unicode万国码

2024-12-03 06:44

关注

Unicode——万国码

为解决语言各自为政的编码,人们提出了Unicode编码方案,这个方案简单粗暴:把世界上所有语言字符统一编码。Unicode的两种方案UCS-2和UCS-4,可使用空间分别达到2^16和2^32个:外星人到访地球之前,应该是够用的。

我们看几个字符的Unicode编码码位(code point)是怎样的: 

  1. ls = 'abAB巩★☆' 
  2. print([ord(l) for l in ls]) 

结果:[97, 98, 65, 66, 24041, 9733, 9734]。可见,字母abAB的Unicode码位和其ASCII码位一致,所以字符为字母时两者兼容,而汉字巩的码位为24041(0x5de9),与之前的GB系列编码47534(0xb9ae)不同,所以Unicode和GB系列编码之间是不完全兼容的:只有ASCII部分兼容。

所有国家的人都使用Unicode编码之后,扩展、乱码问题都不复存在:所有人类语言字符都有了一个统一的编码码位,沟通中我们写出的每个数字编码,都有唯一的字符与他对应。Python中chr函数返回Unicode码位对应的字符。 

  1. >>> print([chr(i) for i in [123,957,24041]]) 
  2. ['{''ν''巩'

那么我们可以使用强大的Unicode进行编码了么?

  1. >>> ls = 'abAB巩★☆' 
  2. >>> ls.encode('Unicode'
  3. Traceback (most recent call last): 
  4.   File "", line 1, in  LookupError: unknown encoding: Unicode 

未知编码Unicode!这是因为,并不存在Unicode码这种编码形式,Unicode只是一个码位表,它只是建立了字符和整数之间的映射。至于整数码位(code point)如何存储成字节,先存高位低位,有没有特殊标志,Unicode并不直接决定,而是交给具体编码来考虑这些细节:UTF-32,UTF-16和UTF-8。

UTF-32 四字节为单位

UTF-32,顾名思义,是用32位,也就是四个字节来存储一个字符的编码方案。 

  1. >>> 'aA巩'.encode('utf-32LE'
  2. b'a\x00\x00\x00A\x00\x00\x00\xe9\x5d\x00\x00' 

可见,所有的字符,都使用了四个字节来存储:每个字节除了Unicode码位之外,不足用\x00来填充。此法简单明了,Unicode码位不用转换,直接填充。但大量的\x00造成了极大的浪费。有没有办法解决这种浪费了?压缩下用两位行不行?

UTF-16 两字节为单位

当用UTF-16来编码时。 

  1. >>> 'aA巩'.encode('utf-16LE'
  2. b'a\x00A\x00\xe9\x5d' 

两个字节对绝大多数Unicode码位来说是够用的,不够用的话系统自动用四位表示。这是系统实现,我们无需关心。UTF-16编码后的字节序列和字符,依然能够一一对应起来。UTF-16其实有两种编码方法,分别为上例的UTF-16LE和如下的UTF-16BE,测试: 

  1. >>> 'aA巩'.encode('utf-16BE'
  2. b'\x00a\x00A\x5d\xe9' 

两者基本一样,只是高低字节位置发生了颠倒。LE和BE后缀,表示小字节序(little endian)和大字节序(big endian)。这是计算机内部关于字节的MSB(大权重字节)放在字节的开头还是结尾的具体实现细节。

《格列佛游记》中,小人国国民为吃鸡蛋先吃大头或小头,针锋相对,组成了两个军事对立集团big endians和little endians,相互间多次发动战争。

那么两个字节就是Unicode编码的极限了么?

UTF-8 变长字节编码

能不能用可变数目的字节来存储文本呢?如果存储的是英文文本的话,每个字符只用一个字节就可以;汉字的话,再进行扩展。如此来进一步节省存储空间。答案是可以的,这就是可变长度编码UTF-8。 

  1. >>> 'aA巩'.encode('utf-8'
  2. b'aA\xe5\xb7\xa9' 

这是目前最短的字节序列,因为aA分别存储成了一个字节。需要注意的是,UTF-32和UTF-16中,巩的字节序列是0x5de9,但在UTF-8中,字节序列变成了0xe5b7a9。这说明UTF-8编码不是简单地把Unicode码位直接存储进字节序列中,而是进行了某些转换。这些转换,保证了英文用一位存储,汉语等较大字符多字节存储。那么是如何转换的呢?

UTF-8 编码转换规则

本部分过于细节,可略过。UTF-8实现了可变长度的编码,为解码时区分可变长度究竟多长,需要在字节序列里使用特殊模板。UTF-8编码遵循以下规则:

以汉字巩为例,其Unicode码位为0x6c49,二进制位110 1100 0100 1001。位于第三行范围,所以需要三个字节来存储,写出模板,1110 **** 10** **** 10** ****,使用二进制,从右向左填充,不足部分补零,可得结果1110 0110 1011 0001 1000 1001,十六进制为0xe6 0xb7 0x89,所以巩编码为UTF-8的字节序列形式为0Xe6b789。让我们从UTF-8编码转换细节中,回到UTF三种编码的长度问题上来。

UTF三种编码后的长度

以上三种编码方式,由于压缩率不用,导致文件长度也不同,以下程序比较当文本为汉字和英语内容时,三种不同编码的长度: 

  1. es = 'abcdefghij' 
  2. cs = '莫愁前路无知己,天下谁人不识君。' 
  3.  
  4. codes = ['utf-32le','utf-16le','utf-8'
  5.  
  6. print([len(es.encode(code)) for code in codes]) 
  7. print([len(cs.encode(code)) for code in codes]) 

输出为 [40, 20, 10] [64, 32, 48] 可见,对于英文来说,UTF-8比UTF-16和UTF-32编码都要有优势;对汉字来说,最有优势的反而是UTF16编码。这是因为UTF-16编码中,大部分汉字采用2Byte存储,而UTF-8中汉字需要三个字节存储。在日常生活中,因为考虑到最大兼容性,UTF-8使用的最为广泛。至此,我们从ASCII码到GB系列编码,再到Unicode和相应的UTF系列编码,一路进化,拥有了一个包罗万码,不会乱码和有较高压缩率的字符编码系统。可以使用了么?没有!因为我们只是编码了文本自身,并没有记载具体用了那个编码:当我们发送一份文件后,除非告诉对方,否则对方不知道应该该用什么编码打开它。解决这个问题,我们留待下篇文章分析。

总结

 

 

来源:Python中文社区内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯