什么是最适合实现像记事本这样的编辑器的数据结构?

时间:2009-03-16 04:37:31

标签: data-structures text-editor

在记事本等编辑器的实现中使用了哪些数据结构。这个数据结构应该是可扩展的,并且应该支持各种功能,如编辑,删除,滚动,选择文本范围等?

6 个答案:

答案 0 :(得分:8)

我们为一台旧机器编写了一个编辑器(请记住,这是一段时间以前,大约在1986年,所以这是从记忆中,而且从那时起,技术水平可能有所提高)我们设法达到通过使用来自自我管理池的固定内存块,在性能方面发出尖叫声。

它有两个池,每个池包含固定数量的特定大小的块(一个池用于线结构,另一个池用于线段结构)。它基本上是一个链表的链接列表。

内存是预先分配的(对于每个区域)来自'malloc()'类似的调用,我们使用了65,535个块(0到65,534(含),块号65,535被认为是空块,结束了 - 清单指标)。

这允许每个65,535行(填充版本为384K或512K)和大约1.6G的文件大小(占用2G的分配空间),这在当时非常大。这就是理论文件大小限制 - 我认为我们从未接触到实际情况,因为我们从未分配过整套线段结构。

不必为每一小块内存调用malloc()都会给我们带来巨大的速度提升,特别是因为我们可以为固定大小的块优化我们自己的内存分配例程(包括在最终优化版本中内联调用)

两个池中的结构如下,每行是单个字节):

Line structure (6/8 bytes)     Line-segment structure (32 bytes)
+--------+                     +--------+
|NNNNNNNN|                     |nnnnnnnn|
|NNNNNNNN|                     |nnnnnnnn|
|PPPPPPPP|                     |pppppppp|
|PPPPPPPP|                     |pppppppp|
|bbbbbbbb|                     |LLLLLLLL|
|bbbbbbbb|                     |LLLLLLLL|
|........|                     |xxxxxxxx|
|........|                     :25 more :
+--------+                     : x lines:
                               +--------+

其中:

  • x以外的小写字母指向线段池。
  • 大写字母指向行池。
  • N是下一行的块编号(null表示这是文件中的最后一行)。
  • P前一行的块编号(null表示这是文件中的第一行)。
  • b是该行中第一个线段的块号(null表示该行为空)。
  • .是保留填充(将结构碰到8个字节)。
  • n是下一个线段的块编号(null表示这是该行中的最后一个段)。
  • p是前一个线段的块编号(null表示这是该行中的第一个段)。
  • L是分段行块的分组编号。
  • x是该细分受众群中的26个字符。

线路结构填充的原因是为了加快块编号到实际存储器位置的转换(向左移位3位比在该特定架构中乘以6快得多,并且使用的额外内存仅为128K,最小比较虽然我们确实为那些更关心内存的人提供了较慢的版本。

我们还有一个包含100个16位值的数组,其中包含大致该百分比的线段(和行号,因此我们可以快速转到特定行)(因此数组[7]是大约7的行) %到文件中)和两个自由指针来维护每个池中的空闲列表(这是一个非常简单的单向列表,其中结构中的Nn表示下一个空闲块和空闲块是分配并放回这些列表的前面。)

由于0字节在文件中无效,因此无需对每个线段中的字符进行计数。允许每个线段在最后被完全忽略的0字节。无论何时修改,都压缩线(即,线段被组合)。这使得块使用率很低(没有频繁和冗长的垃圾收集),并且大大加快了搜索和替换操作。

这些结构的使用允许非常快速编辑,插入,删除,搜索和导航文本,这是您可能在简单的文本编辑器中获得大多数性能问题的地方

使用选择(我们没有实现这个,因为它是一个文本模式编辑器,使用类似vi的命令,如3d删除3行或6x删除6个字符)可以通过使用{line#/block, char-pos}元组标记文本中的位置来实现,并使用其中两个元组作为选择范围。

答案 1 :(得分:5)

结帐Ropes。处理快速插入/删除/编辑字符串。在绳索实现中通常支持范围,并且可以使用倒置索引进行滚动。

答案 2 :(得分:3)

维基百科称许多编辑使用Gap Buffer。它基本上是一个在中间有一个未使用空间的数组。光标位于间隙之前,因此光标处的删除和插入是O(1)。它应该很容易实现。

查看Notepad ++的源代码(正如Chris Ballance在此帖子here中所建议的那样)表明它们也使用了间隙缓冲区。你可以从中得到一些实现想法。

答案 3 :(得分:3)

Piece Chains的作者James Brown撰写了一篇关于HexEdit的优秀文章。

简而言之:片段链允许您记录对文本所做的更改。加载后,您有一个跨越整个文本的链片。现在你插入中间的某个地方。

不是分配新缓冲区,复制文本等,而是创建两个新片段并修改现有片段:现有片段现在包含插入点之前的文本(即您只需更改长度()),然后你有一个新文本的片段,然后插入后的所有文本的新片段。原始文本保持不变。

对于撤消/重做,您可以简单地记住添加/删除/更改的部分。

使用连锁链时最复杂的区域是可见文本中的偏移与内存结构之间不再存在1:1映射。您要么必须搜索链,要么必须维护某种二叉树结构。

答案 4 :(得分:1)

查看Notepad ++的实现,您可以在SourceForge

上查看源代码

答案 5 :(得分:-1)

通常的事情是有一个列表或字符数组的数组。这些年来已经做了很多事情:你可能会看一下this google search