TOC

“锟斤拷” 到底是个啥?

img

手持两把锟斤拷,
口中疾呼烫烫烫。
脚踏千朵屯屯屯,
笑看万物锘锘锘。

开发者经常这样调侃编码问题:手持两把锟斤拷,口中疾呼烫烫烫。

程序员都知道字符编码,比如 ASCII,GBK,UTF-8,那肯定能理解这些神奇的 “乱码” 出现的原因,肯定是某些地方没有正确处理编码。

那为什么总是能看见 锟斤拷烫烫烫呢?其背后隐藏更深的原因是啥?

烫烫烫

三个字符的频繁出现是和微软系的编译器有关,

  1. 静态分配而未初始化的内存空间,默认使用 CC 填充,CCCC 对应 GBK 编码的
  2. 动态分配而未初始化的内存空间,默认使用 CD 填充,CDCD 对应 GBK 编码的
  3. 动态分配然后被回收的内存空间,默认使用 DD 填充,DDDD 对应 GBK 编码的

Windows 中文版本的默认编码是 GBK,一输出就显示成了 烫烫烫, 屯屯屯, 葺葺葺

print((b'\xcc' * 6).decode('gbk'))
# 烫烫烫

print((b'\xcd' * 6).decode('gbk'))
# 屯屯屯

print((b'\xdd' * 6).decode('gbk'))
# 葺葺葺

锟斤拷

Unicode 中有个字符为 �,注意:这不是乱码!!!就是一个方块,中间为一个问号。

这个符号名字是 Replacement Character,可以翻译成替换字符或者占位字符,码位为 FFFD,十进制就是 65533。
https://unicode-table.com/en/FFFD/

按照规范,如果遇到不支持的 Unicode 字符,就转换成这个符号。

print('�'.encode('utf-8'))
b'\xef\xbf\xbd'

print('锟斤拷'.encode('gbk'))
b'\xef\xbf\xbd\xef\xbf\xbd'

print('�'.encode('utf-8').decode('gbk'))
# UnicodeDecodeError: 'gbk' codec can't decode byte 0xbd in position 2: incomplete multibyte sequence

print('�'.encode('utf-8').decode('gbk', errors='ignore'))
锟

print('�'.encode('utf-8').decode('gbk', errors='replace'))
锟�

print('��'.encode('utf-8').decode('gbk'))
锟斤拷

从上面这个例子可以看出,如果两个字符被转换成 ��,然后被转码成 UTF-8,然后再当成 GBK 解析就会出现 锟斤拷 字样。

a = '天门'

# 原始数据
b = a.encode('gbk')
print(b)
# b'\xcc\xec\xc3\xc5'

# 模拟读数据遇到不支持的字符
c = b.decode('utf-8', errors='replace')
print(c.encode('unicode-escape'))
# b'\\ufffd\\ufffd\\ufffd\\ufffd'

# 模拟错误的编码解码操作
print(c.encode('utf-8').decode('gbk'))
# 锟斤拷锟斤拷

# =====================================

open('/tmp/aaa', 'wb').write('天门'.encode('gbk'))
open('/tmp/aaa', 'rb', encoding='utf-8', errors='replace').read()

# =====================================

print('武汉'.encode('gbk').decode('utf-8', errors='replace').encode('utf-8').decode('gbk'))
# 锟戒汉
print('火车站'.encode('gbk').decode('utf-8', errors='replace').encode('utf-8').decode('gbk'))
# 锟斤拷站

这是 Python3,默认采用 Unicode,带编码的字符(bytes 类型)只能显示出 ASCII 字符,所以最后还需要 decode 成 Unicode。
如果是 Python2,字符串默认使用执行环境字符编码,输出的时候 Unicode 也会转换成执行环境字符编码。
如果再运行在 Windows 下(默认编码为 GBK),锟斤拷的出现频率可能会更大一些。
只需要将一个 GBK 编码的文本,转换成 UTF-8,再输出,就可能见到。

C:\Windows\system32>python
Python 2.7.12 (v2.7.12:d33e0cf91556, Jun 27 2016, 15:24:40) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> a = u'天门'.encode('gbk')
>>> print(a.decode('utf-8', errors='replace').encode('utf-8'))
锟斤拷锟斤拷

锘锘锘

Windows 下 BOM 头问题(Byte order mark)。参考 BOM 头的研究

print((u'\ufeff'.encode('utf-8') * 2).decode('gbk'))
# 锘匡豢

声明

这个和 Python 无关,和任何语言都没关系,就是开发者没有正确处理编码问题。

可能是用错误编码读了一个文件,也可能是用错误编码存了数据库。

参考资料与拓展阅读