需要高效的内存方式来存储大量字符串(以前是:Java 中的 HAT-Trie 实现)
Posted
技术标签:
【中文标题】需要高效的内存方式来存储大量字符串(以前是:Java 中的 HAT-Trie 实现)【英文标题】:Need memory efficient way to store tons of strings (was: HAT-Trie implementation in java) 【发布时间】:2011-01-14 04:53:43 【问题描述】:我正在使用大量 (5-20 百万) 字符串键 (平均长度 10 个字符),我需要将它们存储在内存数据结构中支持在恒定时间或接近恒定时间的以下操作:
// Returns true if the input is present in the container, false otherwise
public boolean contains(String input)
事实证明,就吞吐量而言,Java 的 Hashmap 非常令人满意,但占用了大量内存。我正在寻找一种内存高效的解决方案,并且仍然支持不错的吞吐量(与散列相当或几乎一样好)。
我不关心插入/删除时间。在我的应用程序中,我将仅执行插入操作(仅在启动时),随后将仅在应用程序的生命周期内使用 contains
方法查询数据结构。
我读到 HAT-Trie 数据结构最适合我的需要。我想知道是否有一个有实现的库。
欢迎提供其他带有实现指针的建议。
谢谢。
【问题讨论】:
我猜如果用 Java 实现,其他所有数据结构都会占用大量内存。 @ebo 如果底层实现使用 chars / char 数组,则不会。无需持久化输入 String 对象。一般来说,尝试应该使用较少的内存。 谁提出了这个问题,您希望对 contains(...) 的调用返回 true 还是 false? @WizardOfOdds 是的。我正在寻找一个布尔答案。 【参考方案1】:与 trie 类似的是三叉搜索树,但三叉搜索树的优点是使用的内存更少。您可以阅读三元搜索树 here、here 和 here。 Jon Bentley 和 Robert Sedgewick 关于该主题的主要论文之一是here。它还谈到了快速排序字符串,所以不要因此而推迟。
【讨论】:
“三叉树明显大于散列图或大多数二叉树设计”(abc.se/~re/code/tst/tst_docs/perf_notes.html)【参考方案2】:trie 对于您的约束来说似乎是一个非常好的主意。
“跳出框框思考”的替代方案:
如果你有能力为不存在的字符串回答“存在”的概率
编辑:如果您能承受误报,请按照 WizardOfOdds 在 cmets 中的建议使用 Bloom filter。
对于 k=1,布隆过滤器就像一个没有键的哈希表:每个“桶”只是一个布尔值,用于判断是否存在至少一个具有相同哈希值的输入。如果 1% 的误报是可以接受的,那么您的哈希表可以小到大约 100 * 2000 万位或大约 200 MiB。每 1000 个误报中就有 1 个是 2GiB。
使用多个哈希函数代替一个可以提高相同位数的误报率。
【讨论】:
@Pascaul Cuoq:我并没有反对你,但你在这里重新发明了一个***,可能比现有的效率低。我不知道你从哪里得到你的数字,但是有一个已知的数据结构允许 % 的误报,它被称为“布隆过滤器”。对于 2 亿条可接受的误报率为 1% 的条目,布隆过滤器需要 154 MB。 实际上,23MB 用于 2000 万条目作为原始海报指定。但是当然我们没有被告知误报是可以的...... @WizardOfOdds 感谢您的指点。我的意思是确实是一个幼稚的 (k=1) 布隆过滤器。 @All 我以前没有听说过 Bloom 过滤器。它们似乎非常适合我的目的。我可以容忍 0.1% 的误报率。 code.google.com/p/java-bloomfilter 有一个漂亮的 Java Bloom 过滤器实现和一个简单 + 干净的 API 【参考方案3】:Google 在HAT tries in Java 上发布了一篇博文。但是我看不出这将如何直接解决您的问题:该结构是对键前缀的浅层尝试,叶子是哈希表,其中包含具有给定前缀的所有键的后缀。因此,总的来说,您有很多哈希表来存储当前一个大哈希表中的所有键(由于通用前缀,可能每个键总体上节省了几个字节)。无论哪种方式,您都需要一个比默认 Java 哈希表更节省空间的哈希表,否则每个对象的开销都会对您造成同样严重的影响。那么,如果您采用这种方法,为什么不从仅用于字符串键的专用哈希表类开始,并且仅在看起来仍然值得时才担心 trie 部分?
【讨论】:
【参考方案4】:为了节省空间、O(log(n)) 查找和简单代码,请尝试对字符数组进行二进制搜索。 2000 万个平均长度为 10 的键产生 2 亿个字符:如果需要 2 个字节/字符,则为 400MB; 200MB,如果你能逃脱 1。最重要的是,你需要以某种方式表示数组中键之间的边界。如果您可以保留分隔符,那是一种方法;否则您可能会使用 int 偏移量的并行数组。
最简单的变体将使用字符串数组,但每个对象的开销会导致空间成本很高。它在空间效率方面应该仍然胜过哈希表,尽管没有那么令人印象深刻。
【讨论】:
@Darius Bacon:使用 O(log n) 查找的整个字典可以使用每个字符串不到 10 位来存储 (!!!)。真的。不到10位,我已经做到了。还有一种用于字典的高压缩算法,每字使用 12 位,也允许快速查找建议。但是最初的问题明确询问了一个 O(1) 包含,而不是 O(log n) 一个,所以我不能建议这种“高压缩,每字 10 位”数据结构作为答案。 是的,我在回答另一个问题时指出了这种压缩字典。我不会尝试像我在这里的第一个建议那样花哨的东西——如果可以做到的话,要让它尽快完成需要相当多的工作,不是吗?这个问题要求接近恒定时间;这是否足够接近将取决于原始海报。 (实际上,在我的工作生活中,哈希表遇到内存限制被更改为二进制搜索的情况之前已经上演过。遇到这个问题的初级程序员正在策划一个复杂的解决方案,但二进制搜索工作正常。顺便说一下,我将 Bloom 过滤器引入了同一个项目的另一部分......这就像在这里评论这个 *** 问题的所有准备。)以上是关于需要高效的内存方式来存储大量字符串(以前是:Java 中的 HAT-Trie 实现)的主要内容,如果未能解决你的问题,请参考以下文章