第 5 章 · 01 缓冲区模型:不止是字符串 本节摘要:造文本编辑器,第一个要回答的问题是:「文本在内存里怎么存?」最直觉的答案「用一个大字符串」在小文件上够用,但一插入/删除就要搬移大量字符,大文件上极慢。本节导读三种专业缓冲区结构:行数组(每行一个字符串,简单)、gap buffer(在光标处留空隙,Vim 早期用它)、piece table(原文+增量片段表,VS Code 用它)。理解它们的取舍,你就懂得编辑器内核的基础。 内容来源:基于「Build your own X」Text Editor 域整理的导读。 学习目标 阅读完本节,你应当能够: 说清「一个大字符串」方案的缺陷:插入/删除要 O(n) 搬移。
本节摘要:造文本编辑器,第一个要回答的问题是:「文本在内存里怎么存?」最直觉的答案「用一个大字符串」在小文件上够用,但一插入/删除就要搬移大量字符,大文件上极慢。本节导读三种专业缓冲区结构:行数组(每行一个字符串,简单)、gap buffer(在光标处留空隙,Vim 早期用它)、piece table(原文+增量片段表,VS Code 用它)。理解它们的取舍,你就懂得编辑器内核的基础。
内容来源:基于「Build your own X」Text Editor 域整理的导读。
阅读完本节,你应当能够:
编辑器的核心操作是「频繁插入/删除字符」。这些操作的性能,完全由「文本在内存里怎么存」决定。选对数据结构,大文件也流畅;选错,小文件都卡。
本节不是抽象理论——它直接解释「为什么 Vim 能流畅编辑几百 MB 文件,而某些编辑器卡顿」:缓冲区结构不同。理解这点,你造编辑器时才能做出合适选择。
整篇文本 = "Hello, world!\nThis is..." 插入一个字符在中间: 后半部分全部右移一格, O(n)
小文件够用,大文件灾难。这是反面教材。
lines = ["Hello, world!", "This is...", ...] 每行一个字符串, 插入在某行内仍是 O(该行长), 但跨行操作简单
简单直观,适合大多数情况。很多教学编辑器(如 kilo)用它。缺点:单行很长时仍慢。
[abcde][ gap ][fghij] ↑光标在这里
在光标位置留一段空隙(gap)。插入:写进 gap,光标后移;删除:gap 扩大。光标移动时,gap 跟着移动(搬移少量字符)。优势:光标附近的插入/删除是 O(1)(只要 gap 没满)。Emacs 早期用它。
original: "原文, 只读" add: "增量缓冲, 只追加" pieces: [{from original, len 5}, {from add, len 3}, {from original, len 10}, ...]
原文只读、增量只追加,编辑器维护一个「片段表」记录「文档由哪些片段、按什么顺序拼成」。插入/删除只改片段表(记录「从哪到哪」的元数据),不动原文。VS Code、Word 用它。优势:编辑操作 O(1),内存高效(原文只存一份)。缺点:实现复杂。
| 方案 | 简单度 | 编辑性能 | 内存 | 谁用 |
|---|---|---|---|---|
| 行数组 | 最简 | 中 | 中 | kilo 等教学编辑器 |
| Gap buffer | 中 | 光标附近 O(1) | 中 | Emacs 早期 |
| Piece table | 复杂 | O(1) | 高效 | VS Code、Word |
造第一个编辑器,推荐行数组:简单、够用、能让你专注学其他子系统(渲染、输入)。实现:
lines: List<String> # 每行一个字符串 插入字符: 找到对应行, 在该行字符串里 splice 删除字符: 同上 光标: (行号, 列号) 二元组
行数组足够支撑你造一个能用的编辑器。等你理解了其他子系统,再考虑换 gap buffer 或 piece table 优化。
下一节讲光标移动与模式——致敬 Vim 的 normal/insert 双模式。