同一个字,两种念法

同一个字,两种念法

两岸汉语读音差异的一次数据考古

「茜」在字典里只有 qiàn。可现实里,宋茜读 xī,茜茜公主也读 xī。
那个 xī 从哪来?为什么台湾字典里查不到?

为了回答这一个问题,我花了几天时间,把两部字典的全部条目做了一遍机器比对。
过程中挖出的东西,比答案本身有意思得多——包括一个查了半年都没人发现的线上事故,
以及一句关于「自动化能做到什么」的教训。


一、从一个缺音说起

起因很小。

汉字卡的构建脚本里有一张人工维护的表,叫 READINGS_OVERRIDE,专门处理两岸读音分歧。里面躺着 10 个字:迹、企、菌、崖、发、括、携、堤、血、熟。

它们长这样:

"企": ["qǐ", "qì(台)"],     # 大陆企鹅的 qǐ,台湾企业的 qì  
"血": ["xuè", "xiě"],       # 大陆血液的 xuè,台湾血球的 xiě  

意思很清楚:萌典是台湾教育部的字典,收的是台湾读音。要做一个给大陆用户看的卡面,就得把大陆读音补上,台湾读音打上「(台)」的标记保留。

某天我查「茜」,发现卡面上只有 qiàn。

xī 呢?

宋茜、刘茜茜、奥地利皇后茜茜(Sissi)——这个音在现实里天天被用到,字典里却查不到。对用户来说,这解释不通。

于是我做了件很笨的事:写脚本把萌典和另一部大陆来源的字典做全量比对,想搞清楚到底有多少字「缺音」。


二、两岸为什么会念得不一样

在讲数据之前,得先说清一件事:读音差异不是「谁对谁错」,而是历史沉淀。

1. 文白异读:同一个字,两套系统

汉字里有一批字,从古到今就是两套读法并行。

最有名的是「行」:走路的行读 xíng,做买卖的行读 háng。「行当」「行家」「银行」都是 háng——因为「行」本来的意思就是「十字路口」,后来引申成「排成行列」,再引申成「行业」。

血也一样。上古音里血是入声(入声在普通话里已经消失了),读入声的字派生出两个读法:一个带 -s 尾,一个带 -t 尾。血保留了 xiě(书面)和 xuè(口语),到了台湾变成 xiě 和 xuè——恰好各取一个 mainland 的一半。

这类差异是最"讲道理"的:两边读音都能从汉字自己的历史里推出来,谁也没错。

2. 文言音的残留

还有一类差异来自更早的时期。

「之 zhū」这种读音,源自先秦的声母系统,现代普通话已经不用了,但台湾的文言传统保留了它。「亨 xiǎng」「仇 jū」「亏 yú」也属这类——它们在台湾字典里有,在大陆字典里往往没有,或者收作次要读音。

这类差异对普通用户几乎没有价值,但对读古书的人有用。

3. 破读字:为了区分词义而分音

「相」在「照相」里读 xiàng,在「相互」里读 xiāng。这类分音不是为了区分古今,而是为了在口语里区分不同的词。

两岸的破读习惯不完全一样,于是同一个字在两边的主读音可能不同。

4. 最复杂的一层:不同的审音机构

这一层最容易被忽略,也最容易出错。

大陆有《普通话异读词审音表》(1985 年发布,后有修订),台湾有教育部的《重編國語辭典修訂本》。两者是两个机构、两套标准,覆盖范围不同。

关键在于:审音表明确「不审专有名词」。所以「茜」在人名里的 xī,从一开始就不在审音表的管辖范围内——它是在日常使用中约定俗成形成的,不是有机构认定的。这个音在大陆是"事实上的标准",但在规范文件里找不到它。

我这次做卡面时遇到的「茜 xī」,就属于这一类。

5. 轻声与儿化:读音差异里最日常的部分

如果只看字典里的单字读音,会低估两岸差异的真实规模。真正让人「听不懂」的往往不是单字的读音,而是轻声和儿化。

实测几个词:

词 萌典(台湾) 大陆通行
舒服 shū fu shū fú
明白 míng bai míng bái
东西 dōng xi dōng xī
意思 yì si yì sī

台湾保留了更多中古汉语的轻声习惯(尤其是「子」「头」类词缀的轻声化),大陆则在普通话规范化过程中,把一批原本读轻声的字重新归入本调。

有意思的是,萌典把这些轻声读法都当作正式的读音收了,不是记在注释里。因此做跨海数据合并时,这批差异会以「读音条目」的形式出现,而不是「可选读法」——如果按单字去比对,根本看不出来。

6. 还有一层:简体字

这一层与技术实现直接相关,后面会详细讲。

简繁合并让一些原本不同的字变成了同一个简化字。最典型的是「朴」:

  • 「朴」(U+6734):读 piáo(朴姓)、pò(朴树)
  • 「樸」(U+6A38):读 pǔ(质朴、朴素)

繁体里这是两个字,简化合并成了「朴」。所以在大陆,「朴」这个词同时承载了两边的读音。

这类差异对做工具的人最麻烦:你不能假设「简体字查到繁体字」是一对一的关系。


三、第一次踩坑:以为找到了正确的数据源

要判断「哪些读音大陆有、萌典没有」,需要两部字典:一部台湾的(萌典现成),一部大陆的。

我先看的是手边的 makemeahanzi-dictionary.txt——名字听起来很通用,还带拼音字段,应该正好是大陆数据。

拿它和萌典一比:

茜 → 只有 qiàn(没有 xī)  
迹 → jī(这是台湾音,大陆读 jì)  

它是空的。

原来 makemeahanzi 的拼音和萌典同源。用它和萌典比对,等于拿同一份数据减去自己,差集必然是空。

这是第一个教训:数据源的"名字"不说明它是什么。你以为的对照,可能根本不是对照组。

真正的大陆来源应该是 mozillazg/pinyin-data——它整理自汉典和《现代汉语词典》,确实是大陆规范。这才是对的对照组。


四、第二个坑:变调混进了读音表

拿到正确的数据源,跑完比对,第一次结果差点让我下错结论。

跑出来 3275 个「大陆有、萌典没有」的读音。排在最前面的几个:

一 → 大陆 yī,yí,yì    萌典 yī  
七 → 大陆 qī,qí       萌典 qī  
九 → 大陆 jiǔ,jiū     萌典 jiǔ  
也 → 大陆 yě,yí       萌典 yě  

「一」有三个读音?「九」多一个 jiū?

那些是变调,不是读音。

普通话有变调规则:两个三声字相连,前一个变二声;「一」和「不」在四声前要变。这些音在真实语境里确实会被念出来,但它们不是字典里该单独列出的读音。

问题在于,pinyin-data 没给变调做标记,混在正常读音里。得自己判断。

我在这里踩了三个坑才做对:

第一个,正则表达式的字符类写成了 [a-z],匹配不了带声调符号的字母。qiū 里的 ū 不在 [a-z] 里,结果 20924 个字只解析出 99 个。

第二个,判断三声时只写了 ǎ,漏了 ě ǐ ǒ(也 yí 因此漏网)。三声有六种韵尾:ǎ ě ǐ ǒ ǔ ǚ。

第三个,我假设「第一个读音是基准音」,结果「丙」的语料里把 bìng(变调)排在第一位。改用不依赖顺序的判据才解决——只要存在同音节的三声读音,那个读音就是变调形式。

变调过滤掉之后,候选从 3275 降到 2584。

但这还是太多,而且明显还有噪音。


五、第三个坑:以为按词频排序能解决

剩下的噪音主要有三种:

类型 例子 问题
文言/旧读 之 zhū、亏 yú、亨 xiǎng、仇 jū 学习者永远不会遇到
轻声变体 道 dǎo(对 dào)、别 biè(对 bié) 不是独立读音
数据噪音 能 xióng、那 nǎi 源数据本身的问题

我的想法是:既然要判断「学习者会不会遇到这个音」,那就按字频排序——高频字排在前面,低频字沉底。人工核对时只看前面一批就够了。

于是我下载了一份中文词频表(5 万词),给候选字打分。

结果更糟了。

排名前列的是:

的 +dī      是 +tí      道 +dǎo  
有 +wěi     能 +xióng +nái +nài  

因为高频字的读音条目本来就多,噪音也被一起顶上来了。「的」「是」「有」这些字的读音列表里塞满了各种变体和罕见读音,按词频排反而让它们排在最前面。

我试过三条自动化路径——按常用度筛、按差集直接用、按词频排序——全部失败。

最终的结论是:自动化只能当候选生成器,最终必须人工核。 而且这里的「人工核」不是可选步骤,是唯一可行的路径。


六、意外发现:萌典的底子远比预期扎实

既然自动分诊走不通,我换了个笨办法:手挑一批「传统认知里的两岸多音重灾区」,逐字核对萌典到底收了什么。

给  萌典有 gěi,jǐ          ✓ 已齐  
谁  萌典有 shéi,shuí       ✓ 已齐  
薄  萌典有 bó,bò,báo       ✓ 已齐  
差  萌典有 chā,chà,chāi,cī ✓ 已齐  
卡  萌典有 kǎ,qiǎ          ✓ 已齐  
落  萌典有 là,lào,luò      ✓ 已齐  
秘  萌典有 bì,mì           ✓ 已齐  
携  萌典有 xié,xī          ✓ 已齐  
括  萌典有 kuò,guā         ✓ 已齐  

23 个候选里,16 个萌典本来就全收。

我原本的假设("萌典常只收 gěi"、"萌典把 qiǎ 另立或偏废")全都不成立。

更值得说的是,萌典对两岸差异的兼容度远超预期:

  • 垃圾:萌典收 lè, sè(台湾读音),大陆的 lā jī 另在别处
  • 削弱:萌典收 xuè,大陆读 xuē
  • 舒服:萌典收 shū fu(台湾轻声),大陆读 shū fú
  • 明白:萌典收 míng bai,大陆读 míng bái

萌典不只收台湾读音,它把两岸的读法都收了,只是排序上以台湾为主。

这直接改变了工作量的判断:原本准备补 20–30 个字,实测只需要 2 个。


七、朴:不是缺读音,是差一个声调

这两个字里,第一个就有意思。

朴在萌典里的读音是 pú(二声),大陆标准是 pǔ(三声)。

第一反应是「萌典缺 pǔ,得补」。但写代码时发现,现有逻辑处理不了——它靠前缀匹配把释义分组贴上新标签,而 ú(U+00FA) 和 ǔ(U+01D4) 是两个完全不同的 Unicode 字符,startswith("pú") 对 "pǔ" 根本不成立。

这不是「缺一个读音」,而是「同一个音节,声调不同」——和血(xuè/xiě)、熟(shú/shóu)同类,但更隐蔽,因为字形看起来一模一样。

顺带一提,我原本打算用简体繁体的映射来解决(因为「朴」在繁体里是「樸」)。实测发现没必要:萌典的「朴」条目自己就有「質樸」释义,而「樸」只有「未加工成器的木材」——加进去只会多一条噪音。

一个假设,两次实测,都推翻了。

这里值得多说一句「朴」的编码问题,因为它是整件事里最反直觉的一处:

污  U+6C61   ← 简体字形  
汙  U+6C59   ← 台湾正体  
着  U+7740   ← 简体字形  
著  U+8457   ← 台湾正体  

污和汙不是繁简关系,是两个完全不同的码位。它们看起来像简繁对应,但 Unicode 层面毫无关系——因为简化字方案里,「污」这个字形被用来承载了台湾写作「汙」的那个字,同时又保留了原义。

这带来一个实际后果:任何基于「简体 → 繁体」的自动映射,都可能对这类字失效。必须用「台湾正体」专属的转换规则,而不能图省事用通用规则。

至于朴,萌典的读音列表是 pò, piáo, pú——它其实把台湾那边的读音给全了,只是 mainland 要的 pǔ 没有。所以真正的工作只是把标签改对,内容不用造。


八、茜:唯一需要「造」数据的地方

茜是真缺。萌典只有 qiàn,xī 完全没有——不是声调问题,是这个读音在台湾根本不用。

如果只把 xī 加到读音列表里,卡片上会出现这样:

拼音:qiàn  xī  
释义:  
  qiàn  植物名。茜草科……  
  xī    (空)  

有音无义,比缺音更糟。 用户点进去发现没有解释,只会以为字典出错。

所以必须手工补一条释义:

xī —— 外国女子名字的译音用字,今亦多用于人名。
例:茜茜(Sissi)

这是全库唯一一个需要「造」数据的地方。原因也很清楚:它是大陆独有的读音,而台湾字典的编写范围里根本没有这类用法。


九、抽象出来的三件事

排查完,剩下两字,但踩过的坑里有三样东西值得留下来。

1. 繁简映射:不能只用通用繁体

这是本次最大的收获,也是一个查了半年都没人发现的线上事故。

萌典收的是台湾正体,而常用的简繁转换工具默认给的是通用繁体——后者经常给出异体字。

异体字在萌典里只有一条「『X』的異體字」的指针。而构建脚本本来就有一条防御规则:剔除没有实质释义的条目。

两条规则叠加,结果是:这个字在卡片上释义全空。

全量扫描发现 22 个 GB2312 一级常用字中招:

为  众  伪  启  着  污  痹  钵  狸  硷 …  

「为」「众」「伪」「启」「着」「污」——都是每天被查几百次的字。

修法是让候选链优先用「台湾正体」转换,通用繁体作备选。全量 9534 字实测:两种转换结果不同 42 字,修好 17 个,弄坏 0 个。

上线的第二天,「为」恢复 12 组释义,「着」恢复 25 组。

这个 bug 的隐蔽之处在于:它不会报错、不会崩溃,只会让内容静悄悄地消失。 如果没有全量扫描去数「哪些字释义为空」,永远不会有人发现。

2. 补丁机制:三种形态,不是三种技巧

原来那张表只能做一件事:给读音重新排序、给台湾读音加「(台)」标记。

但实际需要处理的形态有三类:

形态 例子 处理
主次颠倒 企 qǐ/qì 排序 + 标注
同音节声调不同 朴 pú/pǔ 显式声调映射
萌典完全没有 茜 xī 补义项

第三类和前两类的性质完全不同——前两类只是改标签,第三类要新增内容。

把它们混在一个机制里,要么表达不了,要么写出别扭的特例代码。分开之后,每种机制都很直白,而且都能单独测试。

3. 自动化:明确它做不到什么

这轮我试了三条自动化路径,全部失败。但失败得很有价值——每一次失败都让我更清楚「人工该做什么」。

尝试 结果 教训
按常用度筛范围 把「茜」自己筛掉了 范围不是越窄越好
差集直接当候选 1056 条里可用不到 10% 召回高 ≠ 可用
按词频排序 高频字噪音更多,更糟 排序会放大噪音

最后我用了最笨的办法:手挑 23 个候选字,逐字核对萌典的实际收录。

结果推翻了我原本的假设——萌典早就收了 16 个,工作量从「20–30 个字」降到「2 个字」。

如果我没做这次核对,就会照着「20–30 个」的预期去改表,最后给 16 个字加上不必要的覆盖,反而污染了数据。


十、一些反直觉的数字

整理下来有几个数字值得单独说:

萌典的两岸兼容度很高

手册上常见的「两岸读音差异 100 例」,萌典覆盖了其中绝大多数。实测:

差异类型 萌典的收录情况
台湾独有音(企 qì、菌 jùn、崖 yái、迹 jī) ✓ 收了,但排在大陆音之后
台湾音 + 大陆音并存(血 xiě,xuè / 熟 shóu,shú) ✓ 两个都收
台湾特有词读法(垃圾 lèsè / 削弱 xuè) ✓ 收了(在台湾语境内正确)
大陆独有音(茜 xī) ✗ 完全没有

萌典的立场不是「只讲台湾话」,而是「以台湾规范为主,兼容大陆读法」。这个判断如果早点知道,工作量会直接砍掉八成。

真正需要人工判断的极少

在 1056 个一级字缺音候选里,能用的不到 10%。剩下的九成:

文言/旧读:之 zhū、亏 yú、亨 xiǎng、仇 jū  
轻声变体:道 dǎo(对 dào)、别 biè(对 bié)  
源数据噪音:能 xióng、那 nǎi、她 chí  

自动化适合「找出来」,不适合「定下来」

差集能高召回地找出所有可能有问题的地方(2584 条)。但决定「这个差异该不该补」需要语言学判断——「之 zhū 是不是真的存在」「能 xióng 是不是录入错误」,机器回答不了。

我试过用词频给候选打分,结果更糟:高频字的读音条目本来就多,噪音被一起顶到了最前面。排名前列的是「的 dī」「能 xióng」这类。

萌典有 14 个常用字根本没收录

僳、勋、叁、墒、彝、挎、狸、硷、耪、茬、菏、酞、钎、锨——这些是萌典的覆盖边界,不是技术缺陷。台湾用不同字形(「叁」写作「參」),或者属于大陆简化新字。

要补就得找第四个数据源。而从使用频率看,这些字极罕见——为一亿个用户里可能查一次的字引入一条新数据链,性价比很低。


十一、回到最初的问题

现在回答开头那个问题:茜的 xī 从哪来?

它是大陆在几十年的使用中约定俗成形成的读音——受外来人名(茜茜公主 Sissi)影响,也被人名(宋茜)强化。这个读音在大陆是事实上的标准,但不在《普通话异读词审音表》里,因为审音表不审人名和译名。

台湾字典不收它,是因为台湾没有这种用法习惯——不是它「错了」,是它「不需要」。

所以「缺音」这个说法其实不太准确。准确的说法是:这个读音超出了台湾字典的收录范围。

至于为什么「为」这种字的释义会是空的——那跟语言学毫无关系,纯粹是一个转换器的默认行为撞上了一部字典的编排体例。

两件事,一个是语言学问题,一个是工程问题。但它们都藏在同一个「查不到字」的体验背后。


附:这次沉淀下来的三个工具

写这篇的过程中顺手做了三个脚本,都留在项目里:

  • check_common_hanzi.py —— 扫全量字卡,找「有拼音没释义」的空洞字
  • check_reading_gaps.py —— 核对指定一批字在萌典的收录情况
  • diff_readings.py —— 两部字典的读音差集(仅用于排查,不参与构建)

第一个特别有用:改完构建跑一次,对比基线数字,就能知道有没有引入回归。这次它一跑就报出 22 个空洞字——其中「为」「众」「伪」「启」「着」「污」都上线了半年没人发现。

静默的内容缺失,只有靠这种全量扫描才能发现。 这是整个教训里最实用的一条。


尾声:两类问题,藏在同一个体验背后

现在回答开头那个问题:茜的 xī 从哪来?

它是大陆在几十年的使用中约定俗成形成的读音——受外来人名(茜茜公主 Sissi)影响,也被人名(宋茜)强化。这个读音在大陆是事实上的标准,但不在《普通话异读词审音表》里,因为审音表不审人名和译名。

台湾字典不收它,是因为台湾没有这种用法习惯——不是它「错了」,是它「不需要」。

所以「缺音」这个说法其实不太准确。准确的说法是:这个读音超出了台湾字典的收录范围。

至于为什么「为」这种字的释义会是空的——那跟语言学毫无关系,纯粹是一个转换器的默认行为撞上了一部字典的编排体例。

两件事,一个是语言学问题,一个是工程问题。但它们都藏在同一个「查不到字」的体验背后。

而要把这两件事分开,唯一的办法就是——去数一遍。看看到底有多少字是这样,有多少不是。