手持两把锟斤拷,口中疾呼烫烫烫。
脚踏千朵屯屯屯,笑看万物锘锘锘。
开发者经常这样调侃编码问题:手持两把锟斤拷,口中疾呼烫烫烫。
程序员都知道字符编码,比如 ASCII,GBK,UTF-8,那肯定能理解这些神奇的 “乱码” 出现的原因,肯定是某些地方没有正确处理编码。
那为什么总是能看见 锟斤拷
,烫烫烫
呢?其背后隐藏更深的原因是啥?
烫烫烫
烫
,屯
,葺
三个字符的频繁出现是和微软系的编译器有关,
- 静态分配而未初始化的内存空间,默认使用 CC 填充,CCCC 对应 GBK 编码的
烫
- 动态分配而未初始化的内存空间,默认使用 CD 填充,CDCD 对应 GBK 编码的
屯
- 动态分配然后被回收的内存空间,默认使用 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,再输出,就可能见到。
锘锘锘
Windows 下 BOM 头问题(Byte order mark)
print((u'\ufeff'.encode('utf-8') * 2).decode('gbk'))
# 锘匡豢