Hanzi Flow:自适应中文阅读练习应用
Hanzi Flow 是一款围绕一个想法打造的汉字练习应用:自适应句子选择。它会根据你当前的掌握程度,从 79,000 多条真实例句中挑选含 2 到 5 个生字的句子,让你始终处于 90% 到 95% 的理解区间。不用管理卡组,只需输入拼音开始练习。
源码: github.com/brianhliou/hanzi-flow
现有工具的问题
中文学习应用基本分为三类,而它们都有各自的短板。
像 Anki 这样的闪卡应用让你死记孤立的汉字。整理卡组花的时间比真正学习还多。等你终于在真实文本里见到某个字时,却认不出来,因为你是脱离语境学的。
像 Pleco 这样的词典应用查词很好用,但完全是被动的。你读了,你懂了,什么也没留下。没有主动回忆,没有打字练习。
阅读应用和家教给你的要么是儿童读物(无聊),要么是母语者内容(难度过高)。你要么无聊,要么招架不住,两者都无法培养流利度。
缺失的一环是保持心流状态。也就是 90% 到 95% 的理解区间,让你有挑战但不至于沮丧。你需要难度恰当、自动挑选的句子,并通过打字进行主动回忆。
我做了什么
Hanzi Flow 是带自适应难度的句子练习。你读中文、输入拼音,应用根据你对每个字的掌握程度挑选下一句。它有 Anki 的间隔重复,却不用管理卡组;有 Duolingo 的单次练习流程,但用的是真实句子而非会话手册里的例句。

这款应用:
- 来自 Tatoeba 的 79,000 多条句子(真实用法,而非教科书例句)
- 4,000 多个汉字:HSK 1 至 9 级,外加约 1,000 个超纲字
- 自适应算法:根据你的掌握程度挑选含 2 到 5 个生字的句子
- 掌握度追踪:字级得分配合间隔重复(EWMA + SRS)
- 对齐 HSK 3.0:可按官方大纲等级筛选(1 至 9 级或超纲)
- 简繁支持:简体、繁体或混合
- 实时反馈:彩色视觉提示,外加 1,598 个音频文件(覆盖全部拼音音节)
- 100% 本地优先:无需账号,不做追踪,可离线使用
你练习与自己水平相符的地道句子,通过拼音输入培养打字流利度,系统会随你的进步自动调整。无需手动管理卡组。
使用体验
首次运行:设置偏好
第一次打开应用时,你要选两项:
字形:简体、繁体或混合。这会筛选语料库以匹配你的目标。
HSK 等级:筛选是累积的。选「HSK 1-3」,你得到的句子只包含 1、2、3 级的字。选「HSK 超纲」则纳入官方大纲之外约 1,000 个进阶字。
这些设置随时可在设置页更改。更改后,应用会清空当前的句子队列,并按新的筛选条件重新生成。

练习循环
练习流程很直白:
- 读句子:汉字大字号显示
- 输入拼音:逐字输入,声调可选(
wo3或wo都行) - 获得反馈:当前字高亮为蓝色,错误则红色闪烁并可重试(最多 4 次)
- 查看拼音:每个字下方显示带声调的正确答案
- 显示译文:完成后可选择在底部查看英文
- 下一句:按空格键或点击按钮
反馈即时且可视化。你的注意力始终落在当前字上(蓝色高亮)。答错时红色闪烁并播放正确读音的音频。你有 4 次机会,之后系统会继续往下并把该字标为紫色(放弃)。系统接受异读(地 既接受 de 也接受 di),但会显示符合语境的那个。

关键机制在于反馈回路。现实中,你可能在书里看到一个字,猜错了,却永远不知道。或者看电视时完全漏掉读音。这类学习循环很慢。随意学习的人可能要花好几个月,才能积累足够的接触量来真正学会一个字。
这款应用压缩了这个循环。你看到一个字,输入它,立刻得到带音频的纠正,马上重试,然后继续。全程十秒。一次练习做 100 个字,就跑完了 100 次学习循环。主动回忆(打字)、即时纠正(音频加视觉)和高频次(句子源源不断)叠加起来,产生的效果是被动接触给不了的。
进度追踪
统计页展示四项主要指标:
- 练习过的汉字总数:你见过的不重复汉字
- 已掌握汉字:掌握度 ≥ 0.8 的字
- 练习过的句子:完成的不重复句子
- 整体正确率:首次尝试即正确的百分比
掌握度分布把汉字分为几组:
- 学习中(s < 0.6):仍在建立熟悉度
- 熟练(0.6 ≤ s < 0.8):快了
- 已掌握(s ≥ 0.8):牢固
每个字都会按间隔重复记录下次复习时间。


自适应算法(NSS)
随机选句效果很差。你会反复看到相同的句子,在已经掌握的内容上浪费时间,或者被含 10 个以上生字的句子压垮。
下一句选择(NSS)就是挑选下一句的算法。它在新知识、间隔重复、新鲜度和难度之间取得平衡。
核心想法
最佳区间:每句 2 到 5 个生字。
- 0 到 1 个生字:太简单,只是复习
- 2 到 5 个生字:恰到好处,挑战足够,语境也足够
- 6 个以上生字:太难,你只能瞎猜
算法还要平衡:
- 新知识(你从未见过的字)
- 间隔重复(复习到期的字)
- 新鲜度(不要反复刷同样的句子)
- 多样性(混合不同难度)
工作原理
NSS 按批次运行。你开始练习时,它生成 10 个句子。做完 8 个后,它在后台预取下一批。你永远不用等待。
四个步骤:
1. 筛选符合条件的句子
从完整语料库(79K)开始,应用你的筛选条件:
- 字形(简体/繁体/混合)
- HSK 等级(你选择的范围)
- 冷却(排除最近 20 分钟内见过的句子)
这样得到候选池,通常是 20K 到 40K 条句子。
2. 随机抽取 300 个候选
从池中随机取 300 条。数量足够保证多样性,又小到能快速打分(100 毫秒以内)。随机抽样确保你每次练习看到的句子都不同。
3. 为每个候选打分
对这 300 条中的每一条,按四个因素计算得分:
- 基础收益:每个字的 (1 - 掌握度) 之和。掌握度低的字得分更高。若已过复习期,收益翻倍。
- 新鲜度:按距上次见到的时间给加分。鼓励多样性,避免反复刷。
- 通过惩罚:如果这句你已多次全对,就降低得分。避免过度练习。
- k 惩罚:惩罚生字数落在 2 到 5 区间之外的句子。强制控制难度。
加总起来:score = base_gain + novelty - pass_penalty - k_penalty
4. 取前 10,打乱,入队
按得分排序,取前 10 条,打乱顺序以混合难度,然后加入队列。
你练这 10 条。剩下 2 条时,算法会在后台再跑一次,预取下一批。
掌握度追踪
每个字都有一个 0.0 到 1.0 的掌握度得分(s)。
- s = 0.3(新字的默认值)
- s → 1.0(稳定答对)
- s → 0.0(一直答错)
更新采用 α=0.15 的指数加权移动平均(EWMA)。近期表现权重更大,但变化是渐进的。一次失误不会让你跌到底,一次答对也不会虚高。
答对一个字后,它的稳定度(SRS 间隔)会增长为 1.2 倍。起点是 1 小时,然后 1.2 小时,再 1.44 小时。相比 Anki 激进的跳跃(1 天 → 3 天 → 7 天),这让汉字在轮换中停留更久。
为什么更慢?当天复习很重要。你需要在一次练习中见到某个字 3 到 5 次,才能把它从短期记忆转入长期记忆。间隔跳得太快会错过这个窗口。
当一个字到期(超过它的 next_review_ts)时,它在打分中获得双倍权重。算法会优先选择含过期字的句子。这就是经典的间隔重复。
数据管线
做这个需要完备的数据:带读音的汉字、带译文的句子、难度标签、质量过滤。下面是我构建语料库的过程。
字集
从 Unicode CJK 统一汉字区(U+4E00 至 U+9FFF)入手,共 20,992 个字。涵盖现代汉语的所有汉字,外加罕见异体字和历史字形。
然后用三个来源的数据为每个字补充信息:
- Unihan 数据库:拼音读音、异体字、部首拆分
- CC-CEDICT:英文释义与例词(学 → “study; learn”,学生 → “student”)
- OpenCC:简繁对应关系,用于字形分类
结果:99.7% 的字有拼音,67% 有英文释义,35% 有异体对应。我知道哪些字真的出现在 Tatoeba 里(5,000 多个),哪些只是理论存在(罕见的文言字)。

这条曲线显示汉字在各 HSK 等级上的累积情况。HSK 1 约 300 字,HSK 2 再加约 300 字,以此类推。等级越高曲线越平缓,逐渐接近约 3,000 字的识字门槛。再往上,边际收益递减。
频率分布遵循齐普夫定律:少数汉字占主导,大多数都很罕见。前 500 个字覆盖 75% 的文本,前 2,000 个覆盖 95%。这对自适应算法很重要,因为先学高频内容能立刻带来阅读能力。

HSK 分级
HSK(汉语水平考试)是中国的官方水平测试。2021 年修订版(HSK 3.0)定义了 9 个等级,总计 3,000 个汉字。我整合了来自 elkmovie/hsk30 的官方字表(由政府 PDF 经 OCR 得到)。
每个句子按其中最难的字分级:
- 全是 HSK 1 的字?标为「1」
- 最难的字是 HSK 3?标为「3」
- 含 HSK 9 以上的字?标为「beyond-hsk」
这驱动了筛选功能。当你选择「HSK 3」时,你只会看到每个字都属于 1 至 3 级的句子。不会有意外。


句子处理
从众包翻译数据库 Tatoeba 抓取了 79,000 多条中文句子。每条一边是英文,一边是中文。
难点:语境感知的拼音。汉字有多音,读法取决于语境。我先用 jieba + pypinyin 跑第一遍,再用 GPT-4o-mini 校验(79K 句约花 2 美元)。在 地(de 对 di4)、著(zhe 对 zhu4)、谁(shei2 对 shui2)等常见多音字上修正了 10,336 处错误。
多音字示例:
- 了:
le(助词)对liao3(完结) - 行:
xing2(可以)对hang2(行列) - 长:
zhang3(生长)对chang2(长)
光靠查表是不行的。你得理解词语所处的语境。
第 1 步:jieba + pypinyin。jieba 把「我们试试看」切分为 [“我们”, “试试”, “看”],然后 pypinyin 为每个词生成拼音:我们试试看 → wo3 men shi4 shi4 kan4。多数情况有效,但在助词和口语读音上会出错。
第 2 步:GPT-4o-mini 校验。把整个语料库过了一遍 OpenAI 的 API(79K 句约 2 美元,每次调用批量处理 10 条)。提示词要求逐字给出带声调的拼音。在 79,603 条句子中发现 10,336 处错误。主要修正:
- 覺(120 处修改):pypinyin 给出
jue2(感觉),OpenAI 给出jiao4(睡觉)- 你應該去睡覺了吧:「你该去睡觉了」
- 著(696 处修改):pypinyin 给出
zhu4(穿着),OpenAI 给出zhe(助词)- 生活就是當你忙著進行你的計劃時…:「生活就是当你忙于推进自己的计划时发生的事」
- 谁(404 处修改):pypinyin 给出
shui2(书面),OpenAI 给出shei2(口语)- 你知不知道他们是谁?:「你知道他们是谁吗?」
- 长(136 处修改):pypinyin 给出
zhang3(生长),OpenAI 给出chang2(长)- 我看见一个长头发的女生:「我看见一个长头发的女生」
我手动审阅了高频字,并应用了高置信度的修正。OpenAI 的语境理解能力捕捉到了基于规则的方法漏掉的错误。
英文译文:同样用 GPT-4o-mini 生成(总计约 1 美元)。Tatoeba 的数据集只有约 2,000 条句子带译文。若只用这些,语料库会从 79,000 多条缩到 2,000 条,自适应算法的效果就毁了。所以我用 GPT-4o-mini 翻译了全部 79,000 多条句子。大语言模型在翻译上很擅长,因为它们会利用语境,而这正是中译英准确性所需要的。示例:
- 我們試試看! → Let’s give it a try!
- 你在干什麼啊? → What are you doing?
- 今天是6月18号,也是Muiriel的生日! → Today is June 18th, and it’s Muiriel’s birthday!
- 生日快乐,Muiriel! → Happy birthday, Muiriel!
- 密码是”Muiriel”。 → The password is “Muiriel.”
还应用了一些质量过滤:
- 字形分类:简体、繁体、中性或混合
- 粗俗内容移除:过滤脏话(约 10 条句子)
- 长度上限:移除超过 50 字的句子(约 270 条),以免出现滚动
- 元句子:移除 Tatoeba 自我指称的句子(约 80 条)
最终语料库:79,333 条干净句子。

架构与性能
使用现代 Web 技术构建,针对速度与隐私做了优化。
为什么本地优先?
- 隐私:无账号、无追踪、无遥测。你的进度永不离开设备。我甚至没接分析工具。数据归你所有。
- 性能:没有网络延迟。句子选择在 100 毫秒内完成。打字反馈即时。统计页 50 毫秒内加载。
- 简单:无后端要维护,无数据库要扩容,无鉴权。应用就是静态文件加客户端 JS。我免费托管在 Vercel 上。
- 代价:没有跨设备同步。笔记本和手机上的练习是分开的。对 MVP 来说可以接受。多数人只有一台主力设备。以后可以加导出/导入或可选的云同步。
技术栈
前端:
- Next.js 15,使用 App Router
- React 19 + TypeScript
- Tailwind CSS
- Dexie.js 操作 IndexedDB
数据管线:
- Python 3.9+ 编写处理脚本
- jieba 做分词
- pypinyin 生成语境感知的拼音
- pandas + matplotlib 做分析
- GPT-4o-mini 做翻译与拼音校验
音频:
- AWS Polly(Zhiyu 音色)做语音合成
- 1,598 个音节,覆盖全部声调
- 采用 OGG 格式以兼容网页浏览器
部署:
- Vercel(零配置)
- 静态站点,无服务器
- 全球 CDN
数据加载与缓存
应用在首次访问时加载大数据集,随后积极缓存。
首次加载:
- 79,333 条句子(约 41MB JSON):只取一次,存入内存
- 汉字映射 CSV(约 2MB):ID、拼音、释义
- 音频文件:按需获取(每个 1 到 2KB)
内存缓存:
- 句子存在 JavaScript 数组里
- 汉字到 ID 的映射存在 Map 里(O(1) 查找)
- 会话期间不重新请求
IndexedDB 持久化:
- WordMastery 表:每个练习过的字一行(掌握度得分、SRS 间隔、下次复习时间、次数统计)
- SentenceProgress 表:每条完成的句子一行(通过率、尝试次数、上次见到时间)
- SentenceQueue 表:存放接下来的 10 个句子(更改偏好时重新生成)
所有数据都留在你的设备上。除首次加载外没有网络请求。
性能技巧:
- 音频预加载:NSS 挑出下一句时,会在后台预加载其拼音音频(3 到 5 个文件,不到 10KB)。等你看到它时,音频已缓存好。
- 队列预取:当你做完 10 句中的 8 句时,NSS 在后台为下一批运行。你永远不用等待。
- 惰性加载:统计页只在你访问时才计算分布,而不是每次操作都算。
离线: 首次加载之后,可以完全离线使用。在飞机上、地铁里,随处都能练。进度会立刻保存到 IndexedDB(无需保存按钮)。
关键设计决策
声调数字还是声调符号
应用接受声调数字(wo3、shi4)而不是声调符号(wǒ、shì)。为什么?
- 更好输入(不需要特殊键盘)
- 是中文输入法的标准做法(为真实打字做准备)
- 无歧义(
v与ü等价,声调符号需要 Unicode 组合字符) - 迫使你明确思考声调(
wo3= 第三声),而不是靠视觉记忆
基于句子的学习
为什么用句子而不是孤立的词?
- 语境:在真实用法中学汉字。「我们试试看」教的是 我们、试试、看,同时还有语法结构。
- 阅读练习:贴近真实阅读。培养解析汉字序列的能力。
- 打字流利度:训练完整句子的肌肉记忆,跟你实际使用的一样。
- 难度校准:句子天然具有难度分布(生字数量)。词级做不到这一点。
- 动力:完成一个句子让人觉得有进展。你在读真正的中文,而不是刷「的 的 的」闪卡。
接下来
就日常练习而言,这款应用的功能已经完整。如果我回头继续做,可能会加上:
- 移动端 PWA(可安装)
- HSK 统计细分
- 复习模式(以 SRS 为核心的练习)
- 整句音频(完整发音,而非仅字级)
- 汉字卡片(字源、笔顺、组词)
- 声调分析(追踪你容易混淆的声调)
- 自定义语料库(导入你自己的句子)
- 导出/导入(跨设备迁移)
这是开源项目。有想法?欢迎提 issue 或 PR。
结语
做这个是为了解决自己的痛点。我想练中文阅读,又不想承担闪卡的额外负担。真实句子、自适应难度、干净的打字界面。
结果变成了对数据管线(79K 多条带语境感知拼音的句子)、自适应算法(EWMA + SRS)和本地优先架构(IndexedDB + 缓存)的深入钻研。我在多音字、齐普夫定律,以及为当天复习调优间隔重复方面学到了很多。
如果你在学中文,试试看。如果你是开发者,看看仓库。句子选择算法、数据处理和本地优先方案里都有些有意思的东西。
鸣谢:Tatoeba 提供句子,MDBG 提供 CC-CEDICT,elkmovie 提供 HSK 3.0 字表,Unicode 联盟提供 Unihan。
自己试试
访问 hanzi.brianhliou.com 开始。
- 选择你的字形和 HSK 等级
- 用声调数字输入拼音(
wo3、ni3、ta1) - 到统计页查看进度
- 每天练习,看掌握度上升
源码在 GitHub,采用 MIT 许可证。克隆后本地运行:
git clone https://github.com/brianhliou/hanzi-flow.git
cd hanzi-flow/app
npm install
npm run dev
打开 http://localhost:3000。