![]()
你敲下len("café"),Python告诉你4。转手传给一个编码函数,出来的字节是5个。你盯着屏幕的时间,比愿意承认的长得多。
然后巴西用户的名字炸了数据库。Windows同事打开你导出的CSV,é变成了é。你靠猜修复——这儿加个.encode('utf-8'),那儿加个.decode('utf-8'),不崩了。
但要是有人问为什么,诚实答案大概是:"编码什么的吧。"这篇文章填上这个坑。
01 根本不存在"纯文本"
有个事实让所有人第一次碰到都栽跟头:根本不存在"纯文本"这种东西。
你处理的每个字符串——Python里的、数据库里的、终端里的、HTTP响应里的——都是编码过的。没有中立格式。é这字母对应的比特序列,完全取决于你和对方约定用哪种编码。
字节不会自带编码说明书。编码是一份协议,双方谈崩了,你就得到乱码,或者更糟的偶发性诡异行为。
这就是全文核心。但历史解释了这团糟怎么来的,而且历史其实挺精彩。
02 128个字符统治世界
ASCII——美国信息交换标准代码——1963年发布。为电传打字机的英文文本设计,干这活儿它完美。
128个字符。7比特。A到Z,0到9,标点符号,33个控制码(比如"响铃""换行""退格")。每个字符塞得进单字节,还剩1比特。
对美国工程师跟美国计算机对话,ASCII够用了。而且大概有二十年,大多数软件都是美国工程师写的,跑在美国计算机上,彼此对话。闭环。完全够用。
![]()
然后世界其他地方也想要计算机。 funny how that works.
03 第8比特的野蛮生长
ASCII用7比特,那字节里第8比特空着。于是大家自然都用上了。用法各不相同。
ISO 8859-1(Latin-1)拿它加西欧语言的 accented 字符。Code page 437是IBM PC标准,带框线字符,撑起了所有DOS界面。Windows-1252是微软版Latin-1,略有不同。JIS X 0208覆盖日语。GB 2312覆盖简体中文——后来中国强制推行GB 18030,不同标准,覆盖完整Unicode范围。KOI8-R覆盖俄语。Big5用完全不同的思路处理繁体中文。
到1990年代,编码成百上千。没有编码信息,字节序列本身毫无意义。
这就是编码战争。你的俄罗斯朋友用KOI8-R发邮件,你Windows机器用Windows-1252打开,Привет变成Привет——如果运气好,你还能认出是乱码;运气不好,它看起来像个有效但荒谬的句子,你压根没意识到出错了。
04 Unicode不是编码
Unicode解决的是另一个问题:给每个字符一个唯一编号。
Unicode Consortium 1991年成立,目标简单:终结编码战争。不再让é在不同系统里是不同的字节。给每个字符分配一个"码点"(code point)——一个抽象的数字,比如U+00E9就是é。
但Unicode本身不是编码。它是字符到数字的映射表。把数字变成字节,你需要编码方案。
UTF-8是最常见的。变长编码:a用1字节,é用2字节,中文用3字节,emoji用4字节。向后兼容ASCII,纯英文文本不膨胀。互联网95%以上的文本用UTF-8。
UTF-16用2或4字节。Java、C#、Windows内部用。UTF-32固定4字节,浪费空间但索引快。还有GB 18030这种怪胎,中国强制标准,必须能表示所有Unicode字符,但结构完全不同。
![]()
05 你的bug从哪来
回到开头那个len("café") == 4但编码后5字节的问题。Python 3的字符串是Unicode码点序列,不是字节。é是一个码点,所以长度4。编码成UTF-8,é变成两个字节0xC3 0xA9,总共5字节。
巴西用户的名字炸数据库?大概率是数据库字段设了VARCHAR(50),存的是字节长度,不是字符长度。一个葡萄牙语名字可能50字符,但UTF-8编码后超了50字节。
Windows同事看到é?你用了UTF-8编码,但Excel默认按Windows-1252打开。UTF-8的0xC3 0xA9被当成两个Latin-1字符:Ã和©。
最阴险的bug是偶发性的。系统A用UTF-8,系统B用Latin-1,大部分字符在ASCII范围内重叠,测试全过。直到某个用户名字带ñ或ü,链条某处崩掉。
06 现在怎么办
第一,永远明确编码。打开文件时用encoding='utf-8',别依赖系统默认。Python 3.15终于要把UTF-8设为默认,但那是三十多年后的补丁。
第二,数据库用UTF-8,但注意CHAR和VARCHAR的长度限制是字节还是字符。MySQL的utf8mb4才是完整UTF-8,旧的utf8编码阉割过,emoji会炸。
第三,HTTP头要完整。Content-Type: text/html; charset=utf-8,缺了charset,浏览器猜,猜错就乱码。
第四,CSV是灾难。没有标准编码声明,Excel的行为因地区和版本而异。救急用UTF-8带BOM(字节顺序标记),虽然BOM在Unix世界是污染,但Excel认它。
第五,日志和错误信息里打印原始字节。看到b'\xc3\xa9'比看到乱码有用一百倍,你能直接查这是UTF-8的é。
那个巴西用户的名字,你最后怎么修的?是扩了数据库字段,还是终于在全链路统一了UTF-8,还是——至今不知道哪一步出的问题,只是不再崩溃了?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.