
你好,世界!
这是本站的第一篇正式文章,介绍站名的由来是个相当不错的选择!
“绁堢シ”三个字确实容易让人摸不着头脑,甚至让人乍一看以为是日语,毕竟其中确实有日文符号。但实际上这并不是日文,甚至也不是中文。虽然它由汉字和片假名组成,但实际上是地地道道的乱码,与众所周知的“锟斤拷”的原理相似。
在进入正题之前,先插播一个小趣闻,这也是笔者开始对字符编码感兴趣的契机。
卧槽!这是什么??

当年学计算机网络,学到http时,教材上提到可以尝试使用telnet连接web服务器,手动发送http请求报文。
笔者当即陷入震惊,“卧槽!还有这种操作?!”
事不宜迟,马上开始实践!我去浏览器里边随便复制来一段http请求头,大致内容如下
GET / HTTP/1.1
Host: www.cs.zju.edu.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: keep-alive
Cookie: SESSION=580b1268-7f32-4342-abd4-937c79aeac63
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Pragma: no-cache
Cache-Control: no-cache
如你所见,这是浙江大学计算机学院的官网,虽然不知道他们为什么不使用https,但既然如此,我正好直接拿来用hehehe。
telnet 连接"www.cs.zju.edu.cn",指定端口80。很顺利地连上了。


接下来就是发送请求报文了,没什么可说的,直接把上面的请求头复制粘贴。
正当我期待收到一个html格式的文档时,意外出现了。

这无论怎么看都是一个正常的html文档,但其中却掺杂着许多面目狰狞的生僻字。
虽然我知道浙江大学定然有深厚的文化底蕴,但用这种火星文来传达含蓄的感情实在有些不体面。
一定是哪里出错了吧?遇事不决问百度,度娘给出了如下解释。

看来在我的认知之外,还有另一种使用汉字的语言在中国被大规模使用。
开个玩笑。总之历经一番波折,我终于知道这些东西是什么玩意了,总结一句就是
“以UTF-8格式编码的字符被错误地当作GBK编码来解码所得到的乱码”
简单来说,汉字作为表意文字,在计算机中储存和传播都相当不容易,需要约定一种编码字符集。
举个例子:
标准A规定 AB='我',CD='爱',EF='你'
标准B规定 ABC='滚',DEF='蛋'
上面的情况相当于:有人使用标准A向心上人表白,而对方按照标准B解读了!
表白能不能成我们暂且不得而知,不过至少现在肯定是要出大问题了。

回到现实,例子中的标准A和标准B差不多就是GBK和UTF-8的关系。
那么,UTF-8和GBK又是什么?
UTF-8
现代计算机技术发源于西方,而实际上英语又是一种相当简单的语言(它只有26个字母),所以对于计算机来说,它只需要为数字0-9和26个字母的大小写以及其它一些必须的符号编码,就能储存大多数的英语文字信息了。
ASCII(美国信息交换标准代码) 就是这样一种解决方案,它仅使用了0x00
-0x7F
共128个编码,就能覆盖大多数英文文字资料。
但是计算机这种好东西,当然不只有西方人能用。世界各国为了在计算机中表示自己的文字,也进行了许多尝试。如今在我们周围,就存在着许多成熟的编码字符集,比如GBK、GB2312、Big5、Shift_JIS等等。
但如果各国都只使用自己的标准,这些标准又互不兼容时,“表白失败”的例子就会层出不穷。
为了增强字符编码的通用性,Unicode(“统一码”)诞生了。
它的目标是容纳世界上所有语言的文字和符号,为每个字符分配一个独有的编码。
这些编码显然会很长(4字节),如果计算机储存的字符每个都占用4字节,那么对于出现频率更高的英文字符来说就太不公平。反映到结果上,就是计算机为了储存字符浪费了太多空间,所以Unicode在实际的应用中,会使用变长编码。
Unicode的实现方法有UTF-8、UTF-16、UTF-32。其中最广泛使用的就是UTF-8。
UTF-8是如何把Unicode储存在计算机中的呢?
首先要知道UTF-8是一种变长编码,最多支持4个字节,具体规则如下:
- 一个US-ASCIl字符只需1字节编码(Unicode范围由U+0000~U+007F)。
- 带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母则需要2字节编码(Unicode范围由U+0080~U+07FF)。
- 其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)包含了大部分常用字,使用3字节编码。
- 其他极少使用的语言字符使用4字节编码。
具体编码规则
- 单字节:首字节的第一位为0,对于所有ASCII编码(
0x00
-0x7F
),UTF-8编码即为ASCII编码,所以UTF-8完全兼容ASCII。 - 二字节:首字节的前三位为
110
,其余字节前两位为10
。 - 三字节:首字节的前四位为
1110
,其余字节前两位为10
。 - 四字节:首字节的前五位为
11110
,其余字节前两位为10
。
下面举个具体例子:
emoji表情“😇”的Unicode为0x1F607
,共17位,所以应该采用四字节编码。
- Unicode:
0001
1111
0110
0000
0111
- UTF-8:首字节
11110
000
第二字节10
011111
第三字节10
011000
第四字节10
000111
- 16进制为
0xF09F9887
验证:

GBK
中国对汉字信息化的尝试其实很早。1980年,中国公布了国家标准《信息交换用汉字编码字符集》,也就是所谓的GB2312。
GB2312收录了6763个汉字以及683个非汉字图形字符。整个字符集分成94个区,每区有94个位。每个区位对应着一个字符。定位一个字符时,同时需要区号和位号,称为“区位码”。
下图是GB2312编码表的一部分。

GB2312已经覆盖了日常生活的绝大多数使用场景,但是在涉及到一些古籍文献以及一些生僻字和少数民族文字时,GB2312就显得不太够用。因此需要对它进行扩展。因此后来又出现了GBK(powershell使用的编码)、GB18030等支持更多字符的编码表,它们都兼容GB2312。
乱码的产生
乱码是怎么来的?如果读到这里大家应该都能猜到了。
与上面“表白失败”的例子很相似,GB使用2字节编码,而UTF-8中汉字使用3字节编码。国内GB编码十分通用,但在国际上更通用的是UTF-8。因此在某些情况下会出现乱码。
可以简单验证一下,想想浙江大学对我说了什么?
娴欐睙澶у璁$畻鏈哄闄?
为了看看它是不是在对我表白,我们尝试还原一下。
既然乱码是错误解码产生的,那我们只需要纠正解码方式就好了!
随便找一个支持改变字符集的文本编辑工具,这里使用notepad++,将编码改为UTF-8,就能还原原始信息了。

看来浙江大学并未向我表白,不过这不要紧,我们至少知道了一部分乱码产生的原因:
对于包含大量生僻字+日文韩文的乱码,大概率是按照GBK解码UTF-8编码产生的
如果反过来会怎么样呢?
在notepad++中编写一段中文内容,记得先将编码切换为GB2312(ANSI)

再将编码切换为UTF-8,乱码产生了!

但严格意义上图并不是乱码,notepad++将无法解码的部分按16进制原样输出了。更一般的情况,这些无法解码的部分会被替换为"�",所以你很可能会看到这样的东西。

再来想想更有意思的情况,我们将上面两种情况结合一下会怎么样?
也就是说,"中文"->编码为UTF-8->按照GBK解码->编码为GBK->按照UTF-8解码->?
显然,如果原始中文信息的长度为偶数,那么最终会得到正确还原的信息,因为UTF-8和GBK对汉字的编码长度比为3:2,两个UTF-8中文编码对应着三个GBK中文编码。如果原始长度为奇数的话,由于大部分软件会将无法解码的部分替换为问号,所以会损失一部分。
反过来,"中文"->编码为GBK->按照UTF-8解码->编码为UTF-8->按照GBK解码->?
在此过程中,按照UTF-8解码时,通常会出现大量无法解码的部分,这些部分全部会被替换为"�"。
"�"的UTF-8编码为0xEFBFBD
,当两个"�"连起来时,就是0xEFBFBDEFBFBD
。这时候按照GBK解码会发生什么?
没错,每两个字节会变成一个中文字符,最终会变成“锟斤拷”。
验证一下
python中可以很方便地对字符串进行编解码,编写验证代码。
def gbk_to_utf8(messed_str):
"""GBK转换为UTF-8"""
byte_data = messed_str.encode('gbk', 'replace')
messed_str = byte_data.decode('utf-8', 'replace')
return messed_str
def utf8_to_gbk(str):
"""UTF-8转换为GBK"""
byte_data = str.encode('utf-8', 'replace')
str = byte_data.decode('gbk', 'replace')
return str
def main():
strr = input("请输入需要转换的字符串:\n")
print(gbk_to_utf8(strr))
print(utf8_to_gbk(strr))
print(utf8_to_gbk(gbk_to_utf8(strr)))
print(gbk_to_utf8(utf8_to_gbk(strr)))
return
if __name__ == "__main__":
main()

站名的由来
废话这么多,最开始的问题呢?站名是怎么来的?
“绁堢シ”一词中既有生僻汉字也有日文片假名,所以显然是UTF-8编码按照GBK解码得到的。来试试还原。

没错,就是“祈祷”。
“祈祷”是我相当喜欢的词语,而它的乱码形态又没有那么面目狰狞,因此被我当作网名一直用到了现在。
结语
此情可待成追忆,只是当时已惘然
Comments NOTHING