2KB: 文档乱码怎么办? 字符编码,字符集的故事
浏览网页的时候,有时候网页会是一堆乱码,或者下载文档的时候,会发现文件名是乱码。
乱码要么是文档损坏了,软件不能正确显示。这时候我们需要重新下载或者寻求难度高的恢复操作。而有时文档乱码仅仅是因为软件(浏览器)不知道文档的编码方式,也没有猜对文档的编码方式,所以不能正确解析对应的二进制数据。
什么是文档的编码方式呢?
首先文档是由一堆0和1组成的数据的集合,如何将这些0和1显示成字符就是文档的编码方式。其实文档的编码方式是口语的叫法,实际上将0和1转换成(可以认识的)字符或者将字符转成0和1存储包含两个过程,如图1.
第一个过程是将0和1转变成数字,比如将0010100转变成24。将0和1转变成数字(或者反过来,将数字转变成0和1)的方式(算法),叫做字符编码方案(character encoding scheme)。
你可能会问将0和1转变成数字的方式不是唯一的,0010100按照二进制的计算结果不就是24吗?它还可以表示成其他数字吗?实际上是的,数字是死的,对死的东西的解释却是活的。比如对于没学过二进制的人来说,0010100就是一万零一百前面加了两个零,比如对于某些编程语言,0在开头表示八进制,0010100就是对应着十进制的4160(调出Python的界面输入int('0010100', 8)算的☺)。常见的字符编码方案有UTF-8,UTF-16,。
而第二个过程是将数字映射到特定的字符上。将数字映射到特定字符(或者将字符映射成特定的数字)使用的映射集合叫做编码后的字符集(coded character set)。比如常见的ASCII码,如图2,将0到127映射成相应的字符,(图片来源于charstable.com);
以及UCS(Universal Character Set, 统一字符集),听名字就是很厉害的编码后字符集。不同的编码后的字符集决定了将数字映射为哪一个字符的问题,比如0010100在ASCII码的映射下就变成了$符号;在不兼容ASCII的编码后字符集映射下就是另外的字符,因为它们把24映射到了其他的字符。
大多数情况下,我们都会将这两个过程组合在一起成为字符编码,而在web编程里,称为字符集charset——http请求可以接受的字符编码方式。比如我们一般说ASCII编码,包含了将一串二进制数字变成字符。因为我们理解二进制的数据与字符的时候,不知不觉就将二进制数据拆分成8位作为一个单元,然后根据ASCII图,将这个单元表示的数字转换成对应的字符。实际上,我们面对二进制数据的时候如何拆分,如何将拆分后的单元变成数字,取决于我们采用的字符编码方案。而变成数字变成哪一个字符又取决于我们使用什么编码后字符集来做映射。
对于大部分二进制数据,如果我们采用的字符编码方案与编码后的字符集不对应,那么就会遇到错误。比如1101001110000000,如果我们以8位为单元并且直接使用二进制转换成数字得到211,这时候采用ASCII编码后的字符集,那么我们就会遇到困难,因为在ASCII字符集只包含了0到127个数字,211对应的字符根本找不到在哪里。实际上该二进制是采用UTF-8的字符编码方案,对应的数字应该是10011000000,也就是十进制的1216,对应字符'Ӏ', 如图3
可能你会问,为什么是这个字符啊?你怎么知道?
字符编码,字符集,字符编码方案,编码后的字符集,实际上在我们的生活中,我们还会遇到其他的一些类似的名词。这些概念乍看之下让人觉得差不多。有些文章对这些名词的使用又有自己的意思。导致容易分布不清。现在总结如下:
二进制数据变成字符包含了两个内容,一是编码字符方案,二是编码后的字符集。编码字符方案决定了如何将二进制数据转变成数字或者是将数字转变成二进制数据,编码后的字符集决定了如何将数字映射成字符,或者将字符映射成数字。它们的二者的结合的例子就是我现在写下的文字如何变成二进制数据传送到微信的后台(当然中间会经过压缩等等步骤,这是另外一个话题)。
字符集经常用在http里面,是对以上两个内容的组合的称呼。我们口语上说的编码方式也是。那我们这时候怎么知道采用了什么字符编码方案和编码后的字符集呢?答案是,根据语境,以及相应的习惯。Pretty danteng,是不是?
有趣的内容还有字符,字形,Unicoe, UCS, UTF-8,感兴趣可以根据这些名词去自己搜索。
附上如何探索1101001110000000对应哪个字符。
方法1:
>>>表示在Python 输入后面的字符串
将1101001110000000转换成16进制
>>>int('1101001110000000', 2)
'0xd380'
知道它是UTF-8编码(一般对应的编码后的字符集是UCS,因为UTF-8本来就是为UCS设计的字符编码方案),那么将这串二进制数据转变成对应的字符
>>>b'\xd3\x80'.decode('utf-8')
'Ӏ'
b表示接下来的字符串是二进制数据,\x表示接下来的两个数字是16进制的数字, decode('utf-8')表示使用utf-8对前面的二进制数据进行解码。
Pretty straighforward~~~
方法2:
如果知道1101001110000000是UTF-8编码
那么你就会进行如何的过程:
1101001110000000 ->10011000000->1216
然后根据关键词上网搜索一下在UCS里面1216对应的字符是什么
方法3:
如果不知道采用了什么编码方式,那么靠猜,猜对了那么就猜对了,猜错了,那就继续猜呗。当然现在都有相应的在线网站帮你猜,以及相应的库。
网站一搜一堆,找个你顺眼的开始用就行了。