有效地迭代几个Java Map键集合的迭代

在我的一个Java 6项目中,我有一个LinkedHashMap实例数组作为要遍历所有键的方法的输入(即通过所有映射的键集的并集)并使用关联的值。 并非所有的键都存在于所有的地图中,并且该方法不应该经过一次以上的每个键或者改变输入地图。

我目前的实现如下所示:

Set<Object> keyset = new HashSet<Object>();

for (Map<Object, Object> map : input) {
    for (Object key : map.keySet()) {
        if (keyset.add(key)) {
            ...
        }
    }
}

HashSet实例确保没有任何密钥将被多次执行。

不幸的是,这部分代码在性能方面非常关键,因为它被称为非常频繁。 实际上,根据profiler超过10%的CPU时间花费在HashSet.add()方法中。

我正试图尽可能优化这些代码。 使用LinkedHashMap及其更有效的迭代器(与纯HashMap相比)显着提升,但我希望能够将本质上的书籍保留时间降至最低。

由于之后调用HashSet.contains()的代价,使用addAll()将所有键放在HashSet中效率较低。 目前,我正在研究是否可以使用位图(以及确切的boolean[] )来完全避免HashSet,但根据我的密钥范围它可能根本不可能。

有没有更有效的方法来做到这一点? 最好是不会对密钥造成限制的东西?

编辑:

一些澄清和评论:

  • 我确实需要地图上的所有值 - 我不能放弃他们中的任何一个。

  • 我也需要知道每个值来自哪个映射。 我的代码中缺少的部分( ... )是这样的:

    for (Map<Object, Object> m : input) {
        Object v = m.get(key);
    
        // Do something with v
    }
    

    一个简单的例子来了解我需要如何处理这些地图,就是像这样并行地打印所有的地图:

    Key Map0 Map1 Map2
    F   1    null 2
    B   2    3    null
    C   null null 5
    ...
    

    这不是我正在做的,但你应该明白。

  • 输入地图是非常可变的。 事实上,这个方法的每个调用都使用了一组不同的方法。 因此,我不会通过缓存键的联合来获得任何东西。

  • 我的密钥都是String实例。 他们使用单独的HashMap在堆上进行排序,因为它们非常重复,因此它们的哈希代码已经被缓存,并且大部分哈希验证(当HashMap实现正在检查两个密钥实际上是否相等,在哈希代码之后匹配)归结为一个身份比较( == )。 分析器确认只有0.5%的CPU时间花在了String.equals()String.hashCode()

  • 编辑2:

    根据答案中的建议,我一路做了一些测试,分析和基准测试。 我最终的表现大约增加了7%。 我做了什么:

  • 我将HashSet的初始容量设置为所有输入地图的总大小的两倍。 通过消除HashSet中的大部分(all?) resize()调用,这使我获得了1-2%的区域。

  • 我使用Map.entrySet()作为当前迭代的地图。 由于额外的代码和担心额外的检查和Map.Entry getter方法调用会超过任何优势,我最初避免了这种方法。 事实证明,整体代码稍快。

  • 我确信有些人会开始对我尖叫,但这里是:原始类型。 更具体地说,我在上面的代码中使用了HashSet的原始形式。 由于我已经在使用Object作为其内容类型,所以我不会失去任何类型的安全性。 调用HashSet.add()时,无用的checkcast操作的代价显然非常重要,可以在删除时提高4%的性能。 为什么JVM坚持检查Object转换超出了我的范围......


  • 无法为您的方法提供替代方案,但有一些建议(略)优化现有代码。

  • 考虑用容量初始化散列集(所有映射的大小之和)。 这样可以避免/减少在添加操作期间调整大小
  • 考虑不使用keySet()因为它总是会在后台创建一个新集合。 使用entrySet() ,应该快得多
  • 看看equals()hashCode() - 如果它们“很贵”,那么你对add方法有负面影响。

  • 你如何避免使用HashSet取决于你在做什么。

    每次input改变时,我只计算一次联合。 这与查询次数相比应该是相对罕见的。

    // on an update.
    Map<Key, Value> union = new LinkedHashMap<Key, Value>();
    for (Map<Key, Value> map : input) 
        union.putAll(map);
    
    
    // on a lookup.
    Value value = union.get(key);
    // process each key once
    for(Entry<Key, Value> entry: union) {
       // do something.
    }
    

    选项A是使用.values()方法并遍历它。 但我想你已经想到了。

    如果代码经常被调用,那么可能值得创建额外的结构(取决于数据更改的频率)。 创建一个新的HashMap; 你的任何hashmaps中的每个关键字都是这个关键字,并且这个关键字出现在该列表中。

    如果数据有点静态(与查询频率有关),这将有所帮助,因此管理结构的过载相对较小,并且如果密钥空间不是非常密集(密钥在不同的HashMaps中不会重复很多) ,因为它会节省很多不需要的contains()。

    当然,如果你要混合数据结构,最好是将所有数据封装在你自己的数据结构中。

    链接地址: http://www.djcxy.com/p/8097.html

    上一篇: Iterating through the union of several Java Map key sets efficiently

    下一篇: JSON string to JS object