我有一个选择。
我需要存储和访问许多已经排序的字符串。看起来我可以选择使用:
字符串的链接列表(单链接)
和艾伦在他的评论中建议我也加入选择:
TList<string>
在什么情况下这些都比其他情况好?
哪种小名单最适合(10项以下)?
哪个最适合大型列表(超过1000个项目)?
哪个最适合大型列表(超过1,000,000个项目)?
最好尽量减少内存使用?
最好尽量减少加载时间以在最后添加额外的项目?
最好尽量减少从头到尾访问整个列表的访问时间?
在此基础上(或任何其他人),哪种数据结构更可取?
供参考,我使用的是Delphi 2009。
Dimitry在评论中说:
描述您的任务和数据访问模式,然后就可以给您一个确切的答案
好。我有一个包含大量数据的家谱程序。
对于每个人,我有许多事件和属性。我将它们存储为短文本字符串,但每个人都有很多,范围从0到几百。我有成千上万的人。我不需要随机访问它们。我只需要将它们作为连接到每个人的已知顺序的多个字符串相关联。这是我的数千个“小名单”的案例。它们需要时间来加载和使用内存,如果我需要它们,则需要时间来访问(例如,导出整个生成的报告)。
然后我有一些较大的列表,例如我的“虚拟”树视图的所有部分的名称,可以有数十万个名称。我再次只需要一个可以通过索引访问的列表。它们与树视图分开存储以提高效率,树视图仅在需要时检索它们。这需要一段时间来加载,并且对于我的程序来说,内存非常昂贵。但我不必担心访问时间,因为一次只能访问少数访问时间。
希望这可以让你了解我想要完成的任务。
P.S。我在StackOverflow上发布了很多关于优化Delphi的问题。我的程序读取包含100,000人的25 MB文件,并在8秒内为它们创建数据结构和报告以及树视图,但使用175 MB的RAM来执行此操作。我正在努力减少这种情况,因为我的目标是在32位Windows中加载数百万人的文件。
我刚刚在StackOverflow问题上找到了一些优化TList的优秀建议: Is there a faster TList implementation?
答案 0 :(得分:10)
除非您有特殊需求,否则TStringList
很难被击败,因为它提供了许多组件可以直接使用的TStrings
接口。使用TStringList.Sorted := True
,将使用二进制搜索,这意味着搜索将非常快。您还可以免费获得对象映射,每个项目也可以与指针相关联,并且您可以获得编组,流接口,逗号文本,分隔文本等所有现有方法。
另一方面,出于特殊需要的目的,如果您需要进行多次插入和删除操作,那么更接近链接列表的内容会更好。但是后来搜索变慢了,这是一个罕见的字符串集合,实际上从不需要搜索。在这种情况下,通常使用某种类型的散列,其中散列是从字符串的前2个字节中创建的(预分配长度为65536的数组,并且字符串的前2个字节直接转换为散列该范围内的索引),然后在该哈希位置,存储链表,每个项密钥由字符串中的剩余字节组成(为了节省空间---哈希索引已经包含前两个字节)。然后,初始哈希查找是O(1),随后的插入和删除是快速链接列表。这是一个可以操纵的权衡,杠杆应该是明确的。
答案 1 :(得分:6)
TStringList。优点:具有扩展功能,允许动态增长,排序,保存,加载,搜索等。缺点:通过索引对项目的大量访问,字符串[索引]引入了明显的性能损失(几个百分点),比较访问数组,每个项目单元格的内存开销。
动态字符串数组。优点:将作为TStrings的动态增长能力与索引的最快访问速度相结合,最大限度地减少其他内存的使用。缺点:有限的标准“字符串列表”功能。
字符串的链接列表(单链接)。优点:将项目添加到列表末尾的线性速度。缺点:索引和搜索访问速度最慢,有限的标准“字符串列表”功能,“下一项”指针的内存开销,每个项目内存分配的开销。
&的TList LT;字符串&gt;。如上所述。
TStringBuilder。我没有一个好主意,如何使用TStringBuilder作为多个字符串的存储空间。
实际上,还有更多方法:
最佳方法取决于任务。
最适合小型名单(在 10项)?
任何人,甚至可能是具有总项数变量的静态数组。
哪个最适合大型列表(超过1000个项目)? 哪个最适合大型列表(超过1,000,000个项目)?
对于大型列表,我会选择: - 动态数组,如果我需要通过索引进行大量访问或搜索特定项目 - 哈希表,如果我需要按键搜索 - 动态数组的链接列表,如果我需要许多项目追加而且索引无法访问
最好尽量减少内存使用?
动态数组会占用更少的内存。但问题不在于开销,而在于这个开销变得合理的项目数量。然后如何正确处理这些项目。
最好尽量减少加载时间以在最后添加额外的项目?
动态数组可能会动态增长,但在真正大量的项目上,内存管理器可能找不到连续的内存区域。虽然链接列表将起作用,直到存在至少一个单元格的内存,但是每个项目的内存分配成本。混合方法 - 动态数组的链接列表应该可以工作。
最好尽量减少从头到尾访问整个列表的访问时间?
动态数组。
在此基础上(或任何其他人),哪种数据结构更可取?
执行哪项任务?
答案 2 :(得分:2)
如果您声明的目标是改进您的程序,以便它可以加载包含数百万人的家谱文件,那么在您的问题中确定四个数据结构之间并不能真正帮助您。
进行数学计算 - 您当前正在加载一个25 MB的文件,其中包含大约100,000个人,这会导致您的应用程序消耗175 MB的内存。如果您希望加载包含数百万人的文件,您可以估计如果不对程序进行重大更改,您还需要将内存需求乘以n * 10
。在32位进程中无法做到这一点,同时将内存中的所有内容保持在当前的状态。
您基本上有两个选择:
不会将所有内容保留在内存中,而是使用数据库或基于文件的解决方案,您可以在需要时加载数据。我记得你已经有过这方面的其他问题了,可能已经决定反对了,所以我会留下它。
将所有内容保存在内存中,但最节省空间的方式。只要没有64位Delphi,这应该允许几百万人,这取决于每个人的数据量。对64位进行重新编译也会消除该限制。
如果你选择第二个选项,那么你需要更积极地减少内存消耗:
使用string interning。程序中包含相同数据但包含在不同字符串中的每个已加载数据元素基本上都是内存浪费。我知道你的程序是一个查看器,而不是一个编辑器,所以你可能只能在你的实习字符串池中添加字符串。使用数百万字符串进行字符串实习仍然很困难,SmartInspect博客上的"Optimizing Memory Consumption with String Pools"博客帖子可能会给你一些好主意。这些人经常处理大量数据文件,并且必须使用它所面临的相同限制
这也应该将此答案连接到您的问题 - 如果您使用字符串实习,则不需要在数据结构中保留字符串列表,而是保留字符串池索引列表。
使用多个字符串池也可能是有益的,例如一个用于名称,而另一个用于城市或国家等位置。这样可以加快插入池中的速度。
使用提供最小内存中表示的字符串编码。将所有内容存储为本机Windows Unicode字符串可能比以UTF-8存储字符串消耗更多空间,除非您经常处理包含UTF-8编码中需要三个或更多字节的字符的字符串。
由于必要的字符集转换,您的程序将需要更多的CPU周期来显示字符串,但是由于内存访问将成为瓶颈,因此使用该数据量是值得的权衡,较小的数据大小有助于减少内存访问负载。
答案 3 :(得分:1)
TStringList
存储一个指向(string,TObject)记录的指针数组。
TList
存储一组指针。
TStringBuilder
无法存储字符串集合。它类似于.NET的StringBuilder,只能用于连接(多个)字符串。
调整动态数组的大小很慢,所以甚至不要将其视为一种选择。
我会在所有场景中使用Delphi的通用TList<string>
。它存储一个字符串数组(不是字符串指针)。由于没有(非)拳击,它应该在所有情况下都能更快地访问。
如果您只想要顺序访问,则可以找到或实施稍好的链接列表解决方案。请参阅Delphi Algorithms and Data Structures。
Delphi推广其TList
和TList<>
。内部阵列实现经过高度优化,使用时我从未遇到过性能/内存问题。见Efficiency of TList and TStringList
答案 4 :(得分:1)
一个问题:您如何查询:您是否匹配列表中的ID或位置的字符串或查询?
最适合小#字符串:
无论是什么让您的程序易于理解。程序可读性非常重要,您只应在应用程序的实际热点中牺牲它以提高速度。
最适合内存(如果这是最大限制)和加载时间:
将所有字符串保存在单个内存缓冲区(或内存映射文件)中,并仅保留指向字符串(或偏移量)的指针。每当你需要一个字符串时,你可以使用两个指针剪切掉一个字符串并将其作为Delphi字符串返回。这样就可以避免字符串结构本身的开销(refcount,length int,codepage int和每个字符串分配的内存管理器结构。
只有当字符串是静态的并且不会改变时,这才能正常工作。
TList,TList&lt;&gt ;,字符串数组和上面的解决方案每个字符串有一个指针的“列表”开销。链表具有至少2个指针(单个链表)或3个指针(双链表)的开销。链表解决方案没有快速随机访问,但允许O(1)调整大小,其他选项具有O(lgN)(使用调整大小的因子)或使用固定调整大小的O(N)。
我会做什么:
如果&lt; 1000个项目和性能并不是最重要的:使用TStringList或dyn数组是最简单的。 否则静态:使用上面的技巧。这将为您提供O(lgN)查询时间,最少使用的内存和非常快的加载时间(只需将其吞入或使用内存映射文件)
当使用需要在代码中动态链接的大量数据1M +字符串时,问题中提到的所有结构都将失败。那时我会使用余额二叉树或哈希表,具体取决于我需要的查询类型。
答案 5 :(得分:1)
从你的描述中,我不完全确定它是否适合你的设计,但是你可以通过使用trie来改善内存使用而不会遭受巨大的性能损失。
相对于二叉搜索树的优势
以下是主要优点 尝试二进制搜索树 (BSTS):
查找键更快。查找长度为m的关键字是最糟糕的情况 O(m)时间。 BST执行O(log(n)) 键的比较,其中n是 树中元素的数量, 因为查找取决于深度 树,这是对数的 树的键数 均衡。因此在最坏的情况下,a BST需要O(m log n)时间。此外, 在最坏的情况下,log(n)将接近 米此外,简单的操作尝试 在查找期间使用,例如数组 使用字符索引很快 在真机上。
当它们包含大量短路时,它可以占用更少的空间 字符串,因为键不是 显式存储,节点共享 在具有共同初始的键之间 子序列。
- 尝试促进最长前缀匹配,帮助找到密钥 分享最长的前缀 人物都很独特。
答案 6 :(得分:1)
可能的选择:
我最近发现了SynBigTable(http://blog.synopse.info/post/2010/03/16/Synopse-Big-Table),它有一个TSynBigTableString类,用于使用字符串索引存储大量数据。
非常简单,单层bigtable实现,它主要使用磁盘存储,在存储数十万条记录时消耗的内存比预期少得多。
简单如下:
aId:= UTF8String(格式('%s。%s',[name,surname]));
bigtable.Add(data,aId)
和
bigtable.Get(aId,data)
一个问题,索引必须是唯一的,更新的成本有点高(先删除,然后重新插入)