需要一个有效的Map或Set,在添加和删除时不会产生任何垃圾

时间:2012-03-22 02:18:26

标签: java data-structures guava javolution trove4j

因为Javolution不起作用(see here)我非常需要一个高效的Java Map实现,并且在简单的使用下不会产生垃圾。添加和删​​除密钥时,java.util.Map会产生垃圾。我检查了Trove和Guava,但它看起来没有Set< E>实现。我在哪里可以找到java.util.Map的简单而有效的替代方案?

编辑EJP:

添加条目时会分配一个条目对象,并在删除它时将其释放到GC。 :(

   void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

4 个答案:

答案 0 :(得分:7)

从字面上看,我不知道Map或Set的任何现有实现,从不在添加和删除密钥时产生任何垃圾。

事实上,它甚至在技术上是唯一可行的方式(在Java中,使用定义的MapSet API)是否要对数字设置严格的上限的条目。实际的Map和Set实现需要与它们所持有的元素数量成比例的额外状态。此状态必须存储在某处,并且当超出当前分配时,需要扩展存储。在Java中,这意味着需要分配新节点。

(好吧,你可以设计一个永久保存在旧的无用节点上的数据结构类,因此永远不会生成任何可收集的垃圾......但它仍然会产生垃圾。)


那么你在实践中可以做些什么呢?减少生成的垃圾量。我们以HashMap为例:

  • 删除条目时会创建垃圾。这是不可避免的,除非您使用永远不会释放代表链条目的节点的实现替换哈希链。 (这是一个坏主意......除非你能保证免费节点池的大小总是很小。请参阅下面的为什么这是一个坏主意。)

  • 调整主哈希数组大小时会创建垃圾。这可以通过以下几种方式避免:

    • 您可以在HashMap构造函数中给出一个'capacity'参数,以将初始哈希数组的大小设置得足够大,以至于您永远不需要调整它的大小。 (但这可能会浪费空间......尤其是如果你无法准确预测HashMap将会增长多少。)

    • 您可以为“加载因子”参数提供一个荒谬的值,以使HashMap永远不会自行调整大小。 (但这会产生一个HashMap,其哈希链是无限制的,最终会有O(N)行为进行查找,插入,删除等。


事实上,创建垃圾对于性能而言一定不是。实际上,挂在节点上以便垃圾收集器不会收集它们实际上可能会更差以提高性能。

GC运行的成本(假设现代复制收集器)主要分为三个方面:

  • 查找非垃圾的节点。
  • 将这些非垃圾节点复制到“to-space”。
  • 更新其他非垃圾节点中的引用以指向“to-space”中的对象。

(如果您使用的是低暂停收集器,还有其他成本......通常与非垃圾量成正比。)

GC工作的唯一部分实际上取决于垃圾量,它将垃圾对象占用的内存归零,以便为重用做好准备。这可以通过单个bzero调用整个“从空间”...或使用虚拟内存技巧来完成。

假设您的应用程序/数据结构挂起到节点上以避免产生垃圾。现在,当GC运行时,它必须做额外的工作来遍历所有这些额外的节点,并将它们复制到“to-space”,即使它们不包含有用的信息。此外,这些节点正在使用内存,这意味着如果应用程序的其余部分生成垃圾,则保留它的空间将会减少,并且GC将需要更频繁地运行。

如果您使用弱/软引用来允许GC从数据结构中回收节点,那么GC的更多工作......以及表示这些引用的空间。

注意:我并没有声称对象池总是会让性能变得更糟,只是它经常会这样,特别是如果池意外地变大了。

当然,这就是为什么HashMap和类似的通用数据结构类不做任何对象池的原因。如果他们这样做了,他们会在程序员不期望的情况下表现得非常糟糕......而且他们会真的被打破,IMO。


最后,有一种简单的方法可以调整HashMap,这样在紧接着删除相同的键后立即生成不会产生垃圾(保证)。将其包装在一个缓存最后一个条目“已添加”的Map类中,并且只在添加下一个条目时才在真实put上执行HashMap。当然,这不是一个通用的解决方案,但它确实解决了您之前问题的用例。

答案 1 :(得分:4)

我猜你需要一个使用开放式寻址的HashMap版本,你需要比线性探测更好的东西。我不知道具体的建议。

答案 2 :(得分:4)

http://sourceforge.net/projects/high-scale-lib/具有Set和Map的实现,在添加或删除键时不会产生垃圾。该实现使用具有交替键和值的单个数组,因此put(k,v)不会创建Entry对象。

现在,有一些警告:

  • Rehash创建垃圾b / c它取代了底层数组
  • 我认为这个地图会重新给出足够的交错放置&amp;删除操作,即使整体大小稳定。 (收集墓碑值)
  • 如果您要求输入集(在迭代时一次一个),此地图将创建Entry对象。

该类称为NonBlockingHashMap。

答案 3 :(得分:0)

一种选择是尝试修复HashMap实现以使用条目池。我做到了。 :)还有其他优化速度,你可以在那里做。我同意你的观点:Javolution FastMap的这个问题令人难以置信。 :(