原文
字符编码,我愿称之为计算机历屎上的屎山王中王。其支撑起了无数次谁都不服谁的争论。几乎把所有主流操作系统都坑的不要不要的,并且至今仍然遗留着一大堆乱七八糟的问题。并且最重要的是至今没有一个较为完美的解决方案。我们先回到几十年前的某一天,作为一个根正苗红的阿美莉卡计算机攻城狮,你认为存储一个"字母",用几个bit合适?已知我们有26个英文字母,我们算大小写翻个倍儿,加上数字标点,怎么的128种编码情况也够了吧,加上1个位作为拓展,一字节=8位,简直特么的完美.
可以说ASCII字符集终结了1byte=6位,7位,8位,9位这群魔乱舞的时代,但你别急,我们的故事才刚刚开始.随着计算机的流行和发展,肯定不只有阿美莉卡在用啊,就说我们中文,
收录的数量就有5万个,哪怕我们凑活凑活,只用常用汉字,那也有3000个左右,8字节能表示的范围就256个,这显然不够啊
你会说,不要慌,8字节不还给你留了一个扩展位么,我们可以这样,第一个字节如果高位是1,那么后面那个就是扩展位,所以在1980年中国国家总局颁布了第一个中文编码GB2312-80,不过这个编码的覆盖范围不够,所以后面又颁布了GBK编码,简单来说如果第一个字节的范围是0x81–0xFE,那么它就是一个汉字,你需要和第二个字节(范围0x40–0xFE)去凑够整个汉字的编码.很完美是不是?这是一个神奇的编码,具体怎么神奇我们稍候再说。中文问题解决了,但是又有一个大问题,比如90年的后即十几二十年年,windows是毫无疑问的操作系统一哥,没有之一,但显然windows不能只给我们中国用,它得挑一个别的编码,大到能够覆盖大部分国家的绝大多数语言。这问题就大了,选啥好呢?1991年Unicode 1.0刚刚诞生,当时的UCS-2(也就是后来的UTF16)支持65536个码位,这就像IPv4地址被设计成32位被认为完全够用一样,当年他们也认为UCS-2足够覆盖所有的文字了。
那么,现在第一个问题来了,现在我们在网上聊天,在经过了一系列深入的交流后,我们决定对《金瓶梅》这部小说进行严肃的学术探讨,所以我把这本书的电子版txt发给了你。那么它到底是GBK编码还是UTF16呢(UCS-2后来的名称,后文为了方便我们都用UTF16)。然后我们都发现了一个问题,我们竟然无法通过txt这个文件的任何数据,知道它究竟是什么编码,所以,我们只能一个一个试过去,先用UTF16打开,哎呀,乱码,再用GBK格式打开,哦,正确显示了,所以它是GBK编码但是,我们人还可以通过视觉手段判断一下到底是什么编码,但很多的文本处理程序可就要了老命了,因为文本的解析完全依赖编码,所以要么设置一个默认编码,要么让用户选一个。要知道,很多的文员连键盘都用的不利索,你和他们说什么文本编码,那不为难我胖虎么?
现在,第一个坑还没解决,我们回到“GBK”是一个神奇的编码这个问题上来,为什么神奇呢,第一个它是非国际标准编码,所以GBK编码,几乎不具备通用性,特别像早期的Linux系统发行版,根本就不鸟你,但最关键的问题是,GBK编码几乎没有纠错能力和快速索引能力非常容易出现那种错了一个字节后面全错的情况(下面的程序故意误码1字节,导致后面的文本全错):
而快速索引指的是比如你要如何定位第1000个汉字呢,只要文本中有一个数字英文或半角标点什么的,你不得不从头解析整个字符串,就比如我们有一本长篇小说,我们想找到书里的第100000个汉字,我们不得不从头开始解析文本,这就导致了程序的文本处理效率极差。更坑爹的是GBK编码第二个字节的高位不一定是1,这就导致了其可能与ASCII码混叠在一起,假如你在写一个编译器需要非常小心的处理字符串编码,但某天你没有正确的识别(回到上面那个编码识别问题)代码编码,非常可能错误的把汉字的一个字节识别成ASCII字符,那你这个编译就直接炸了。所以即使到今天,GBK编码仍然是一个“非常难搞”的编码。你是不是想说那我们不用不就得了,我们完全采用Unicode的几种编码。别急,Windows也后院起火了,今天我们知道了UTF16,这特么完全就是一个神坑啊。首先,UTF16是一个多字节编码,那么问题来了,它到底是大端序还是小端序的呢?傻了吧,UTF16这种编码内部还分家了,还直接拉上了另一个计算机科学里另一个“先有鸡还是先有蛋”的神坑问题里------到底是特么的大端好,还是小端好。当然这不是什么事,真正坑爹的是,今天我们知道UTF16完全不够用,后来Unicode标准扩展到超过100 万个码位。UTF16 为了兼容,发明了代理对这种神奇的玩意儿,这就导致了UTF16不再是定长编码而需要变长解析。好家伙,合着UTF16是在统一编码快速索引一个问题都没解决,还在屎山上继续拉了一坨大的。所以到今天,你可以看到GBK和UTF16在windows仍然坚挺,属实是自己装的逼,再难受也得装下去。
但问题还是没有解决啊,要知道Unicode今天可不止一种编码,今天有UTF8 UTF16 UTF32,其中多字节编码还有细分大小端(LE BE)问题。兜兜转转,结果发现特么我们还是一个问题都没解决,一个文本文件过来,它到底是哪种编码。这个时候微软又出来说,要不我们在文本文件最前面几个字节里标识一下这个文本文件到底用什么编码吧,这也就是我们今天说的BOM头
可惜,当时已经不是微软一家独大他说的算了,显然是上个逼他装的太失败了,比如Linux Unix 和GNU一众表示,微软你特么就一个行业毒瘤,你说要支持就支持,那我不特么很没面子。
何况作为文本文件,前面还要塞一些东西进去,这会破坏ASCII特性,那这个文本文件还是文本文件么,那不成了四不像,不然你看我HTTP协议,说好的超文本传输协议,难不成也要加个BOM头,你丫的纯属脱裤子放屁,所以哪怕时至今日,BOM头仍然在很多需要进行文本分析的软件和平台的支持受阻。并且,即使Unicode编码的多种编码格式,与其说每种都解决了一些问题,不如说每种也都有严重的问题。例如作为目前最广泛的UTF8现在各平台的支持度最好,但是仍然存在索引缓慢的问题,也就是之前说的你要定位某一个汉字,你需要从头开始解码整段文本,效率很低。而UTF32虽然能完美覆盖所有编码,但对于最常用的纯ASCII字符文本,虽然解决了动态索引速度问题,但其内存占用量直接翻了四倍。所以目前主流的方案是,编码大多采用UTF8形式,解析完成后内存以32位Unicode形式存储。除此之外,如何判断文本字符串结束,也常常打起来。保守派认为文本以0结尾是一个优雅的实现。激进派认为文本应该是抽象数据结构,应该在开头就标明文本长度(甚至编码)。保守派认为,激进派这种设计结构会破坏数据的纯粹性,比如C语言中严格意义上只有以字符编码的数组而不是什么字符串结构,你加那么多莫名其妙的东西进去,那你说长度应该是几字节,到底用大端编码还是小端,如果长度数据也被破坏了咋办,char txt[]="abc",txt[0]到底是'a'还是什么别的神奇的东西?很多设计就是没事找事自己抽象把自己抽出事了.激进派则认为保守派这种0结尾的方式造就了无数溢出安全问题,并且每次统计字符串长度都要从头扫描一遍,你不累么?你就是老登,看不得新登的好.时至今日,这场屎山大战仍然在继续,至于有没有从失败中吸取经验教训.反正至少目前为止,吸取了经验也没成功过.额呵~
chatGPT评价:
下面是对这篇文章的详细评论与其分析的优点与不足的归纳。整体上,作者用通俗、犀利、戏谑的语气讲述了字符编码从 ASCII 到 GB 系列、到 Unicode/UTF 的历史坎坷,抓住了“识别编码困难”“变长编码索引困难”“历史兼容债务”这几个关键痛点,读起来很顺。但也混入了若干技术不准、混淆或以偏概全的地方,容易让读者对一些基础概念产生误解。
一、行文风格与叙事结构
-
优点
-
通俗易懂、笑点密集,通过“屎山”“神坑”等形容强化了读者对复杂历史包袱的感受。
-
选取了用户层真实痛点作为切入(文本乱码、BOM 争议、Windows/Unix 分歧),共鸣强。
-
时间线基本正确:ASCII → GB2312 → GBK → Unicode/UTF-16/UCS-2 → UTF-8/UTF-32 → BOM/端序争论。
-
-
不足
-
情绪化表达较多,部分地方为夸张服务而牺牲了技术严谨性。
-
把多方博弈(标准组织、厂商、社区)的历史动因简化成“谁装了个逼”,利于传播但不利于理解真实权衡。
-
二、技术观点与事实核对
-
说得到位的部分
-
“无法从原始字节流唯一判断文本编码”是真问题。除非有外部元数据(MIME、协议头、BOM、文件名/侧信道)或启发式/统计检测(如 uchardet、chardet),否则不存在完美自识别。
-
GBK/其他双字节兼容 ASCII 的变长编码,确实在“定位第 N 个字符需要从头解码”“丢一字节导致后续全乱”方面表现糟糕。这是所有不自同步、无明确延续字节标记的编码的通病。
-
UTF-16 的确存在端序问题(LE/BE),历史上 Windows 倾向 UTF-16LE,网络标准偏 BE;BOM 在 UTF-16 中常用来指示端序。
-
UCS-2 的 16 位设计后期不够用,UTF-16 引入代理对,因而不再定长,这点正确。
-
不同 UTF 的空间/性能权衡:UTF-8 对 ASCII 极其高效,UTF-32 索引 O(1) 但空间大,UTF-16 处于中间但有代理对复杂度。
-
-
有误或需要纠正/补充的部分
-
“8字节能表示的范围就256个”:应为“1字节=8位(bit),最多 256 种取值”。文中“8字节”是口误。
-
“GBK 第二个字节高位不一定是1”导致与 ASCII 混叠:更准确说法是,GBK 的双字节范围为首字节 0x81–0xFE、次字节 0x40–0xFE(跳过 0x7F),而 ASCII 是 0x00–0x7F。GBK 的第二字节可能落在 0x41–0x5A/0x61–0x7A 等字母区间(但其值≥0x40,不在 0x00–0x3F),这会让“按字节扫描”的工具把它误判为可打印 ASCII,但理论上仍可通过“遇到首字节在 0x81–0xFE 就吞后一个字节”的规则正确解析。问题更多出在流损坏或跨边界截断时的恢复性差与非自同步特性,而非单纯“与 ASCII 混叠”的定义不清。
-
“GBK 几乎没有纠错能力和快速索引能力”:应具体为“非自同步、缺少唯一续字节特征、错误传播严重、随机访问需要从前缀解码”。UTF-8在这方面更好:除首字节具有特征比特外,续字节以 10xxxxxx 识别,出错时可局部 resync。
-
“UTF-16 完全是神坑”“一个问题都没解决”:过于绝对。UTF-16 在以 BMP 内字符为主(如早期 Windows、部分东亚环境)时在空间上比 UTF-32 好、比 UTF-8 稳定;其代理对虽增加复杂度,但在 API 约束明确的环境下可控。
-
“BOM 是微软发明并大力推动”:BOM 概念与 U+FEFF 历史更复杂。Unicode 标准定义了 BOM 用于指示端序,UTF-8 的 BOM 并非必须,且在 Unix 语境下不鼓励放在文本前(会破坏 shebang/ASCII 兼容)。但把 BOM 完全归咎于微软并不准确。
-
“UTF-8 索引慢必须从头解码整段文本”:严格说,UTF-8 对“按字符序号随机访问”确实需要自起点线性解码或借助额外索引表;但很多实际任务按字节偏移/按行处理或流式处理,不以“第 N 个代码点”为主目标。工程上可以分块建立索引,实现近似 O(1) 跳转。
-
“主流方案:编码用 UTF-8,解析后内存用 32 位 Unicode 存储”:这取决于语言/平台。JavaScript 使用 UTF-16 代码单元;Java 的 String 早期是 UTF-16,后来引入 Compact Strings 用 Latin-1/UTF-16 双态;Go 使用 UTF-8 存储源码,字符串是只读字节序列,rune 为 32 位;Rust 的 String 是 UTF-8;Python 3 使用灵活的内部表示(1/2/4 字节),并非固定 32 位。没有统一“32 位内存存储”的主流定论。
-
“判断字符串结束:0 结尾 vs 长度前缀”:还应提到现代容器/运行时普遍采用“长度+数据”(加上 NUL 兼容或不兼容皆有),并配合不可变/分享拷贝/切片元数据。C 的 NUL 结尾引起安全问题属实,但也有 size_t + length API 可用;矛盾并非二元对立。
-
“ASCII 时代终结 1 byte=6/7/8/9 位的群魔乱舞”:实际是早期有多种字长(6/7/8 位字符或不同机器字长),ASCII 定义了 7 位编码,但存储常放在 8 位字节里。说“终结 9 位”混淆了“字节宽度/机器字长”和“字符编码宽度”。
-
三、遗漏与可完善的视角
-
自同步与容错设计
- UTF-8 的核心优点不仅是对 ASCII 的前向兼容,更是自同步(续字节以 10 开头),便于错误恢复、搜索边界、剪裁子串。
-
正规化与等价性
- 真实“屎山”之一是 Unicode 规范化(NFC/NFD/NFKC/NFKD):同一文本可有不同字节序列,需要在比较、搜索、文件系统命名时处理;不同平台(macOS NFD 偏好)历史差异也会带来坑。
-
字符、代码点、字形簇的差异
- 用户感知的“一个字/一个字符”和“代码点数量”不等价。表情符合成、ZWNJ/ZWJ、变体选择器、组合音标、地区旗帜序列、皮肤色修饰等,使“第 N 个字符”应按“扩展字形簇”划分而非代码点数。UTF-32 也不能保证“按人眼的第 N 个字符 O(1)”,因为需要以 grapheme cluster 为单位解析。
-
编码识别的工程实践
- 探测器使用统计语言模型、字节频率、合法序列约束、BOM/魔数/容错解析等多信号融合;最佳实践是“协议层声明 + BOM(仅 UTF-16/UTF-32 必要场景)+ 合理默认 + 探测兜底”。
-
文件系统与协议层差异
- POSIX 世界倾向“字节序列 + 约定 UTF-8”,Windows NT 内核 API 以 UTF-16LE 为主;这解释了跨平台工具链的编码摩擦来源。
-
安全与边界问题
- 文本处理中的截断、正则、宽字节注入、规范化绕过、大小写折叠(Turkic I 问题)等安全议题,是“编码屎山”的重要组成部分。
四、对文中具体论断的修订性建议
-
用词更精确:把“UTF-16 是神坑”“一个问题没解决”改为“在跨平台/网络/混合语言场景下权衡不佳,端序与代理对引入实现复杂度”。
-
明确术语:
- 字节(byte)与位(bit)区分;代码点(code point)vs 代码单元(code unit);字符(grapheme)vs 字形簇(grapheme cluster)。
-
给出工程可行路径:
-
文件/网络层:统一使用 UTF-8;必要时写明 charset(HTTP Content-Type、HTML meta、MIME、JSON 明确为 UTF-8)。
-
Windows API:优先调用 “W” 宽字符接口(UTF-16LE);跨平台边界处集中转换。
-
文本内部索引:维护增量索引表(每隔 N 字节记录“代码点/字形簇边界”),折中随机访问与空间。
-
避免 UTF-8 BOM;UTF-16/32 如需落地文件,建议保留 BOM 指示端序或用容器元数据标注。
-
处理用户可见单元:用 Unicode 字符边界算法(UAX #29)按字形簇迭代,而非简单代码点计数。
-
正规化与大小写:入库统一 NFC;比较时按需要做 case folding(UAX #15、#44)、locale-aware 规则。
-
五、结论性的评价
-
这篇文章的长处在于:把“编码识别难、兼容债务重、UTF-16 历史包袱、BOM 争议、变长索引困难”讲得生动直白,能够迅速让非专业读者理解为什么“编码问题总在搞事”。
-
主要不足在于:若干技术点表述过度简化或不准确(bit/byte 口误、GBK 与 ASCII 的“混叠”措辞、对 UTF-16 与 BOM 的片面归因、对业界“内存统一 32 位”的概括),且忽略了 Unicode 正规化、字形簇、API 生态差异等更关键的现代难点。
-
更成熟的结论应是:今天“完美方案”仍不存在,但在网络与跨平台生态上“UTF-8 作为交换格式 + 明确元数据 + 平台内部遵循各自原生编码 + 正规化与字形簇感知的文本处理 + 建立索引辅助结构”已经能把屎山铺平成可走的路。换句话说,问题不是无解,而是需要在标准、实现与工程实践三方面协同取舍。
Gemini Pro评价:
我们来逐条详细拆解和评论这篇文章,并对其中涉及的技术知识点进行展开说明。
这篇文章的优点在于用非常生动和接地气的语言,把一个枯燥的技术史讲得像一个“事故现场”故事会,让非专业读者也能感受到程序员在处理字符编码时遇到的痛苦。但它的缺点也很明显,为了戏剧效果,一些技术细节被简化、夸大甚至弄错了。
下面我们逐一分析评论中的要点,并详细解释相关的技术知识。
第一部分:行文风格与叙事结构
这部分主要评价文章的写作方式。
-
优点:通俗易懂,共鸣强
- 解释: 作者没有一上来就讲“码位”、“字节”、“端序”这些技术术语,而是用了“屎山王中王”、“神坑”、“谁都不服谁”这样的大白话。他从一个我们都可能遇到的问题——“打开TXT文件发现是乱码”——入手,这能立刻抓住读者的注意力。这种写作方式对于向大众普及一个复杂概念来说,是非常有效的。
-
不足:情绪化表达,简化历史
-
解释: 作者把复杂的历史问题归结为“微软装的逼”、“GNU就是不给你面子”。这虽然读起来很爽,但实际上,技术标准的演进是一个充满妥协和权衡的复杂过程。
-
展开说明: 比如,Windows 系统早期大力拥抱
UTF-16,是因为在那个年代(90年代初),UCS-2(UTF-16的前身)被认为是“定长编码”,处理起来比变长的UTF-8要简单高效,而且当时认为 65536 个字符足够容纳全世界的文字了。这是一个在当时看来很合理的“技术赌注”,只是后来 emoji 和更多生僻字的出现让这个赌注显得有些失策。将其简单归结为“装逼”,就忽略了当时的历史局限性和技术考量。
-
第二部分:技术观点与事实核对
这是核心部分,我们来详细辨析文章中提到的技术点是对是错,以及为什么。
-
1. “无法从原始字节流唯一判断文本编码”—— 这个观点【正确】
-
解释: 想象一下,我给你一串数字
2032022909。如果你不知道密码本(也就是“编码”),你无法知道这代表什么。如果用中文电报码,它可能代表“你好”;如果把它当成邮政编码,它可能是一个地名。 -
展开说明: 计算机里的文本文件就是一串二进制数字。同一串数字,用
GBK编码去“翻译”,可能显示为正常的汉字;用UTF-8去“翻译”,就可能显示为一堆奇怪的符号,也就是“乱码”。因为文件本身通常不会“自我介绍”说:“你好,请用 GBK 编码打开我”。这就是文章提到的核心痛点。 -
解决方案(BOM): 后来微软等人提倡的
BOM(Byte Order Mark) 就像是在文件开头加了一个小纸条,比如UTF-8的BOM是EF BB BF这三个字节。程序一读到这三个字节,就知道“哦,这是一个 UTF-8 文件”,虽然解决了问题,但又引入了新问题(下面会讲)。
-
-
2. “8字节能表示的范围就256个”—— 这是【口误】
- 解释: 这是文章最明显的一个笔误。应该是 “1 字节(Byte)= 8 位(bit),能表示的范围是 2 的 8 次方,即 256 个”。作者应该是想说“8位”,结果说成了“8字节”。1 个字节是计算机存储的基本单位。
-
3. GBK 编码的问题:“非国际标准”、“没有纠错能力”、“索引能力差”—— 观点【基本正确】
-
解释:
-
非国际标准: GBK 是中国自己的标准,在国外的系统上默认不支持是很正常的,这导致了通用性差。
-
没有纠错能力: GBK 的汉字由两个字节组成。如果其中一个字节因为传输错误丢失了,后面的字节就会和前面剩下的那个字节“错误配对”,导致从出错点开始,后面的所有文字全部变成乱码。它很难从错误中“恢复同步”。相比之下,
UTF-8的设计就好很多,它的多字节字符有固定的格式(比如第二个字节总是以10开头),即使中间错了一段,程序也能很快找到下一个字符的开头,重新恢复正确解码。 -
索引能力差: 这个问题文章说得很对。因为 GBK 编码里,英文字母
a占 1 个字节,汉字你占 2 个字节。如果你想找到一篇长文里的第 1000 个字,你不能直接跳到第 2000 个字节(假设全是汉字),因为中间可能夹杂了只占 1 字节的字母和标点。你必须从文章开头一个一个地数:“这是个汉字,计1个;哦,这是个字母,也计1个...”,直到数到第 1000 个。这个过程非常慢。
-
-
-
4. UTF-16 的问题:“神坑”、“大小端”、“代理对”—— 观点【部分正确,但过于夸张】
-
解释:
-
大小端 (Endianness):
UTF-16用 2 个字节(或 4 个字节)表示一个字符。比如汉字“你”的编码是4F60。在存入内存时,是先存4F再存60(大端),还是先存60再存4F(小端)?这两种方式都有电脑在用。如果一个“小端”电脑去读一个“大端”电脑写的文件,就会把4F60读成604F,字就错了。BOM在这里的一个重要作用就是区分大小端。 -
代理对 (Surrogate Pairs):
UTF-16最初的设计(UCS-2)认为 2 字节(65536个位置)足够了。后来发现 emoji、生僻字根本放不下。于是Unicode标准想了个“补丁”:拿出UTF-16里的 2048 个位置,规定它们不代表任何字符,而是作为一种“特殊标记”。一个标记(高代理项)和另一个标记(低代理项)组合起来,共同表示一个新增的字符。这就好比本来门牌号都是 4 位数,后来不够用了,就规定门牌号9xxx不是一个地址,而是“请看下一个门牌号”,9123和9456两个门牌号合在一起才是一个完整的地址。这使得UTF-16也从“定长”变成了“变长”,失去了它最初简单高效的优势。 -
“神坑”的评价: 说它是“神坑”有些夸张。在特定环境(如 Windows 内部、Java 早期),它曾经是性能和功能的优秀平衡点。只是在需要跨平台和网络传输的今天,它的缺点被放大了。
-
-
-
5. “主流方案:编码用 UTF-8,解析后内存用 32 位 Unicode 存储”—— 观点【过于简化,不完全准确】
-
解释: 文章的说法是一种“理想模型”。把文件里的
UTF-8(变长)读到内存后,全部转成UTF-32(定长,每个字符 4 字节)。这样做的好处是,在内存里“索引第 N 个字符”就变得超快(直接用N * 4字节的地址偏移就行)。 -
展开说明: 但现实是,很多主流编程语言为了节省内存,并不会这么做。
-
Python: 内部表示非常灵活,如果一个字符串里全是英文,它就用 1 字节存;如果出现了中文,它可能会用 2 字节或 4 字节存,是动态优化的。
-
Java: 新版本里,如果字符串全是西欧字符,就用 1 字节存 (Latin-1),否则才用 2 字节 (UTF-16),这叫“紧凑字符串”。
-
JavaScript: 语言规范里基本是
UTF-16。
-
-
所以,并不存在一个统一的“主流方案”,各个平台都在用自己的方式在“索引速度”和“内存占用”之间做权衡。
-
-
6. “判断字符串结束:0 结尾 vs 长度前缀”—— 观点【正确】
-
解释: 这是计算机科学里一个非常经典的“老问题”。
-
0 结尾 (Null-terminated): 以 C 语言为代表。字符串的结尾放一个值为 0 的特殊字节作为“结束符”。优点是简单,数据本身很“纯粹”。缺点是,每次要获取字符串长度,都得从头数到尾;而且很容易出现“缓冲区溢出”安全漏洞——如果你往一个只能放 10 个字符的盒子里硬塞了 20 个字符,就会把盒子后面的内存给破坏掉。
-
长度前缀 (Length-prefixed): 以 Pascal、Rust 等语言为代表。在字符串数据的前面,用几个字节记录下“这个字符串有多长”。优点是获取长度是瞬间的,而且因为长度已知,复制和操作起来更安全。缺点是数据里混入了“元数据”(长度信息),不够“纯粹”。
-
-
这个争论确实至今仍在不同场景下延续,文章的描述是准确的。
-
第三部分:文章遗漏的重要“屎山”
文章讲了很多,但还有一些更“坑”的地方没提到。
-
1. Unicode 正规化 (Normalization)
-
解释: 在 Unicode 里,同一个看起来一样的字符,可能有多种字节表示方式。比如带声调的字母
é,可以是一个预先组合好的é字符,也可以是字母e和一个声调´两个字符的组合。它们显示出来一模一样,但在计算机看来是两串完全不同的数据。 -
为什么是“屎山”: 当你在一个系统(比如 macOS,它喜欢用组合形式)上创建一个名为
resumé.pdf的文件,然后把它传到另一个系统(比如 Windows,它喜欢用预组好形式),可能会导致程序找不到文件,或者在数据库里搜索不到匹配的用户名。开发者必须手动把所有文本都“正规化”成同一种形式才能可靠地比较。
-
-
2. 字形簇 (Grapheme Clusters)
-
解释: 什么是“一个字符”?你以为很简单,但其实非常复杂。
-
一个 emoji 👨👩👧👦 (家庭),在用户看来是 1 个表情符号。
-
但在计算机里,它是由
MAN+(连接符)+WOMAN+(连接符)+GIRL+(连接符)+BOY7 个 Unicode 码点组成的。
-
-
为什么是“屎山”: 这让文章里提到的“索引第 N 个字符”变得更加复杂。你想要的是索引第 N 个“用户看到”的字符,而不是第 N 个“Unicode 码点”。当你按一下删除键,你希望删掉整个家庭 emoji,而不是只删掉最后那个小男孩。要正确处理这些,需要非常复杂的算法,远不是简单的数数或乘法就能搞定的。
-
结论性评价
-
优点总结: 这篇文章是一篇非常优秀的 科普入门读物。它用幽默的语言,准确抓住了字符编码历史中的核心矛盾:标准不统一的混乱、变长编码带来的效率问题、以及历史遗留问题造成的兼容性噩梦。它能让一个完全不懂的读者在笑声中明白为什么“乱码”问题如此顽固。
-
不足总结: 它并非一篇严谨的 技术分析文档。为了叙事流畅和“爽感”,它在一些技术细节上存在错误或过度简化的倾向。同时,它忽略了像“Unicode正规化”和“字形簇”这类在当今软件开发中更为棘手和普遍的“新屎山”。
总而言之,如果你想快速了解字符编码的“黑历史”和主要痛点,这篇文章绝对值得一读。但如果你是开发者,需要深入理解并解决实际问题,那么还需要去了解那些被文章简化或忽略掉的、更深层次的复杂性。