这个问题可能会让我听起来很无能为力。那是因为我。
我只是想,如果我假设有兴趣设计我自己的文本编辑器GUI控件,小部件或任何你想要它的东西(我不是),我怎么能这样做呢?
像我这样的新手的诱惑是以字符串的形式存储文本编辑器的内容,这看起来非常昂贵(并不是说我太熟悉字符串实现在一种语言/平台之间的区别而且我知道在.NET中,例如,它们是不可变的,所以频繁的操作,例如你需要在文本编辑器中支持的内容,将会非常浪费,以非常快速的方式构建一个接一个的字符串实例继承)。
据推测,使用包含文本的一些可变数据结构;但弄清楚这个结构可能会是什么样的,这对我来说是一个挑战。随机访问会很好(我会认为,无论如何 - 毕竟,你不希望用户能够跳到文本的任何地方吗?),但后来我不知道这个成本比如说,导航到一个巨大文档中间的某个地方并立即开始输入。同样,新手方法(比如你将文本存储为可调整大小的字符数组)会导致性能非常差,我想,就像用户输入的每个字符一样,会有大量的数据要“移位”结束了。
因此,如果我不得不猜测,我认为文本编辑器采用某种结构将文本分解成更小的部分(行,可能?),它们分别包含随机访问的字符数组,以及它们本身可以作为离散块随机访问。即使,似乎也必须是一个相当可怕的过度简化,但是,如果它甚至可以远程接近开始。
当然,我也意识到文本编辑器的实现可能不是 的“标准”方式;也许它在一个编辑器之间变化很大。但我想,由于这显然是一个已被解决很多次的问题,这些年来可能已经出现了一种相对常见的方法。
无论如何,我只是想知道是否有人对这个话题有一些了解。就像我说的那样,我绝对不想写自己的文本编辑器;我只是好奇。
答案 0 :(得分:36)
一种常见的技术(特别是在较旧的编辑器中)称为分割缓冲区。基本上,您将文本“分解”为光标之前的所有内容以及光标之后的所有内容。之前的一切都在缓冲区的开头。一切都在缓冲区的末尾。
当用户键入文本时,它会进入中间的空白区域而不移动任何数据。当用户移动光标时,您可以将相应数量的文本从“中断”的一侧移动到另一侧。通常情况下,围绕一个区域移动很多,所以通常一次只能移动少量文本。最大的例外是如果你有一种“转向xxx”的能力。
查尔斯克劳利写了一篇更完整的discussion of the topic。您可能还想查看The Craft of Text Editing,它更深入地介绍了分割缓冲区(以及其他可能性)。
答案 1 :(得分:2)
前段时间,我在Tcl中编写了自己的文本编辑器(实际上,我从某个地方窃取了代码并将其扩展到无法识别,开源的奇迹啊。)
正如您所提到的,对非常非常大的字符串执行字符串操作可能很昂贵。因此编辑器会在每个换行符(“\ n”或“\ r \ n”或“\ r \ n”或“\ r \ n”)中将文本拆分为较小的字符串。所以我剩下的就是在行级编辑小字符串并在行之间移动时进行列表操作。
这样做的另一个好处是,它是一个简单而自然的概念。我的思想已经认为,每一行文本都要通过多年的编程来加强,其中新行具有风格或语法上的重要性。
我的文本编辑器的用例也是程序员编辑器。例如,我实现了语法高亮,但没有实现单词/换行。所以在我的情况下,文本中的换行符和屏幕上绘制的行之间有一个1:1的映射。
如果你想看看,这是我的编辑器的源代码:http://wiki.tcl.tk/16056
这不是玩具BTW。我每天都使用它作为我的标准控制台文本编辑器,除非文件太大而不适合RAM(严重的是,什么文本文件是什么?甚至小说,通常是4到5 MB,适合RAM。我只看到日志文件增长到数百MB)。
答案 2 :(得分:1)
根据一次需要在编辑器中显示的文本量,整个缓冲区方法的一个字符串可能没问题。我认为记事本这样做 - 有没有注意到在大文件中插入文本要慢多少?
哈希表中每行一个字符串似乎是一个很好的折衷方案。它可以导航到特定的行并删除/粘贴效率,而不会有太多复杂性。
如果你想实现一个撤销功能,你需要一个表示,它允许你回到以前的版本而不存储30个整个文件的副本进行30次更改,尽管如果文件是足够小。
答案 3 :(得分:1)
最简单的方法是使用该语言提供的某种字符串缓冲类。即使是简单的char对象数组也会在紧要关头完成。
添加,替换和搜索文本的速度相对较快。当然,其他操作可能更耗时,因为在缓冲区的开头插入一系列字符是更昂贵的操作之一。
但是,对于简单的用例,这在性能方面可能完全可以接受。
如果插入和删除的成本特别重要,我很想通过创建一个内部维护缓冲区对象列表的缓冲区包装类来进行优化。未在现有缓冲区尾部发生的任何操作(除了简单替换)将导致相关缓冲区在相关点处被分割,因此缓冲区可以在其尾部进行修改。但是,外包装器将保持与简单缓冲区相同的接口,因此我不必重写例如我的搜索行动。
当然,这种简单的方法很快会以极其分散的缓冲区结束,并且我会考虑在适当的时候使用某种规则来合并缓冲区,或者在例如适当的情况下推迟拆分缓冲区。单个字符插入。也许规则是我最多只有2个内部缓冲区,并且在创建新缓冲区之前我会合并它们 - 或者当有人要求我立即查看整个缓冲区时。不确定。
重点是,我开始简单但通过精心选择的界面访问可变缓冲区,如果分析显示我需要,则使用内部实现。
但是,我肯定不会以不可变的String对象开始!