繁体   English   中英

一种节省空间的数据结构,用于存储和查找大量(均匀分布的)整数

[英]A space efficient data structure to store and look-up through a large set of (uniformly distributed) Integers

我需要在内存中查找并查找一百万个均匀分布的整数。 我的工作量非常密集。
我当前的实现使用HashSet(Java)。 我看到了很好的查找性能,但内存使用情况并不理想(数十MB)。
您能想到更高效(内存)的数据结构吗?
编辑:解决方案需要支持少量的数据结构添加。

背景:
上述整数问题是以下问题的简化:
我有一百万个字符串(我的“字典”),我想知道字典是否包含给定的字符串。
字典太大而不适合内存,所以我愿意牺牲一点精度来减少内存占用。 我将通过切换到包含每个String的Hashcode值(整数)的Dictionary而不是实际的chars来实现。 我假设每个字符串发生碰撞的可能性只有1M/2^32

虽然Jon Skeet的回答为小额投资带来了可观的节省,但我认为你可以做得更好。 由于您的数字分布相当均匀,您可以使用插值搜索来获得更快的查找(大致为O(log log N)而不是O(log N))。 对于一百万个项目,您可以计划大约4个比较而不是大约20个。

如果你想做更多的工作来将内存(大致)再次削减一半,你可以将它构建为一个两级查找表,基本上是一种简单版本的trie。

在此输入图像描述

你可以将你的(大概)32位整数分成两个16位的整数。 您将前16位用作查找表第一级的索引。 在这个级别,你有65536个指针,每个可能的16位值对应整数的那一部分。 那会把你带到桌子的第二层。 对于这一部分,我们在所选指针和下一个指针之间进行二进制或插值搜索 - 即第二级中所有在前16位中具有相同值的值。

但是,当我们查看第二个表时,我们已经知道了16位的值 - 所以我们只需要存储该值的其他 16位,而不是存储该值的所有32位。

这意味着代替占用4兆字节的第二级,我们已将其减少到2兆字节。 除此之外,我们需要第一级表,但它只有65536x4 = 256K字节。

这几乎肯定会提高整个数据集的二进制搜索的速度。 在最坏的情况下(使用二级搜索进行第二级),我们可以进行多达17次比较(1 + log 2 65536)。 平均值会比那更好 - 因为我们只有一百万个项目,每个二级“分区”中平均只能有1_000_000/65536 = ~15个项目,大约1 + log 2 (16) = 5比较。 在第二级使用插值搜索可能会进一步减少,但是当你只进行5次比较时,你没有太多空间可以进行真正的改进。 鉴于二级平均只有约15项,您使用的搜索类型不会有太大差异 - 即使线性搜索也会非常快。

当然,如果你想要,你可以更进一步,而是使用一个4级表(整数中每个字节一个)。 然而,这可能是一个值得怀疑的问题,这是否会为你节省更多的钱来帮助你。 至少马上关闭,我的直接猜测是你要做相当少的额外工作以获得相当小的节省(只存储百万个整数的最后字节显然占用1兆字节,并且三个级别的表将导致占用相当多的数量,所以你可以将节省数量增加一倍,以节省半个兆字节的费用。如果你处于一个可以节省更多一点的情况会有很大的不同,那就去吧 - 但是,除此之外,我怀疑这种回报是否能证明额外的投资是合理的。

听起来你可以保持一个排序的int[] ,然后进行二分查找。 有一百万个值,即达到任何价值的~20个比较 - 这会足够快吗?

如果您愿意接受很小的误报机会以换取大量减少内存使用量,那么Bloom过滤器可能就是您所需要的。

Bloom过滤器由k个散列函数和一个n位表组成,最初为空。 要向表中添加项,请将其提供给每个k哈希函数(获取0到n -1之间的数字)并设置相应的位。 要检查项目是否在表格中,请将其提供给每个k哈希函数,并查看是否设置了所有相应的k位。

具有1%误报率的布隆过滤器每个项目需要大约10比特; 当您为每个项目添加更多位时,误报率会迅速下降。

这是Java中的开源实现。

在Github项目LargeIntegerSet中,有一些用于整数的Java实现,减少了内存消耗。

你可能想看看一个BitSet Lucene中使用的那个比标准的Java实现更快,因为它忽略了一些标准的边界检查。

我认为您可能会重新考虑原始问题(拥有有效的单词列表),而不是尝试优化“优化”。

我建议调查Radix tree / Trie。

https://en.wikipedia.org/wiki/Radix_treehttps://en.wikipedia.org/wiki/Trie

你基本上存储了一些带有字符串前缀的树,每次在字典中有选择时都会分支。 它有一些有趣的副作用(允许非常有效地过滤前缀),可以为具有较长公共前缀的字符串节省一些内存并且相当快。

基数树示例

一些示例实现:

https://lucene.apache.org/core/4_0_0/analyzers-stempel/org/egothor/stemmer/Trie.html

https://github.com/rkapsi/patricia-trie

https://github.com/npgall/concurrent-trees

这里有各种实现的有趣比较,更多地关注性能而不是内存使用,但它仍然有用

http://bhavin.directi.com/to-trie-or-not-to-trie-a-comparison-of-efficient-data-structures/

可用的基元有一些IntHashSet实现。

快速的谷歌搜索让我这一个 还有一个IntHashSet的apache [开源]实现。 我更喜欢apache实现,虽然它有一些开销[它实现为IntToIntMap ]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM