【问题标题】:HashMap or TreeMap or LinkedHashMap which one is fastest to iterate over?HashMap 或 TreeMap 或 LinkedHashMap 哪个迭代最快?
【发布时间】:2013-07-28 10:37:15
【问题描述】:

我有一个Map,它在应用程序启动期间被填满。它在应用程序执行期间不会更改。稍后此映射仅用于迭代其中的所有元素。我应该选择Map 的哪个具体实现? HashMap 或 TreeMap 或 LinkedHashMap ?
更新
插入顺序无关紧要。唯一重要的是所有元素的快速迭代(比如 6000 个元素)。

【问题讨论】:

  • 插入顺序无关紧要。唯一重要的是所有元素的快速迭代(比如 6000 个元素)。
  • 考虑编辑您的帖子而不是添加评论,这很容易错过。
  • 请发布您的profile 结果。
  • @trashgod:我不明白你的意思。请澄清一下。

标签: java collections iteration


【解决方案1】:

从 Java 10 开始,您可以使用Map.of() 重载方法之一或Map.copyOf() 创建unmodifiable Map。由于这些方法返回的映射不支持将任何新条目放入映射中,因此现有的键/值以this answer 中所述的交错模式存储。这应该为您的用例提供最佳性能。

【讨论】:

    【解决方案2】:

    HashMap 通常最快,因为它具有最佳缓存行为(HashMap 直接迭代后备数组,而 TreeMap 和 LinkedHashMap 迭代链接数据结构)。

    如果地图在初始化后不会更改,您可能需要使用 ImmutableMap 或 UnmodifiableMap

    【讨论】:

    • 故事没那么简单。您应该从 HashMap Javadoc 中添加以下引用:“对集合视图的迭代需要的时间与 HashMap 实例的“容量”(桶的数量)加上其大小(键值映射的数量)成正比。因此,它是如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低),这一点非常重要。”
    • @MarkoTopolnik 您从 javadoc 发布的最后一行让我重新考虑我决定盲目使用HashMap 的原因。!!您对这种情况有什么建议?
    【解决方案3】:

    Map.forEach(BiConsumer) 几乎总是比迭代器快,有时甚至快很多。例如,如果您对这些值求和:

    public class TestForStackOverflow {
        int sum = 0;
    
        // Or whatever Map you are using
        Map<Object, Integer> map = new HashMap();
    
        static class C implements BiConsumer<Object, Integer> {
            int sum = 0;
    
            @Override
            public void accept(Object k, Integer v) {
                sum += v;
            }
        }
    
        public int getSum(Map map) {
            C c = new C();
            map.forEach(c);
            return c.sum;
        }
    
        public int getSum2(Map map) {
            map.forEach((k, v) -> sum += v);
            return sum;
        }
    }
    

    【讨论】:

      【解决方案4】:

      我不会使用地图。如果您只想遍历条目,请根据您的需要创建一个新的 ArrayList 并使用它 - 您无法比 ArrayList 更快地进行迭代。

      // Which map you use only chooses the order of the list.
      Map<Key,Value> map = new HashMap<>();
      // The list to iterate for maximum speed.
      List<Map.Entry<Key,Value>> list = new ArrayList<>(map.entrySet());
      

      这样,您只需遍历条目集一次即可构建列表。从那时起,您将一次又一次地遍历列表 - 这当然应该接近最佳状态。

      注意根据 Marko 的建议,从 LinkedList 更改为 ArrayList。

      【讨论】:

      • 由于元素的非常非常密集的包装,数组迭代当然要快得多。
      • 好点 Marko - 所以如果可以从 ArrayList 中选择观察到的加速,然后使用它,但你会占用更多内存。 LinkedList 将增加最小的内存开销。
      • 不,LinkedList 实际上是最浪费的:它为每个列表节点分配一个完整的对象。 ArrayList 只有一个数组,如果您从现有地图转换,自然可以调整其大小。但我的建议是一个普通数组而不是ArrayList。对于固定列表,您真的不会从 ArrayList 获得任何收益。
      • @MarkoTopolnik - 你是对的 - 我在发布之后才意识到这一点。我会改变我的帖子。有时我仍然在 C 中思考。
      【解决方案5】:

      此处的其他答案均未考虑 CPU 缓存的影响,当涉及迭代时,它可能巨大。

      改善这一点的一种方法是仅使用交错键和值的单个数组(偶数索引处的键,奇数索引处的值)。这会将这些数据项紧密地组合在一起,并最大限度地利用缓存,至少对于引用而言。

      但是,如果您可以避免创建保存数据的对象并只使用原始值数组,那么真正的、令人惊叹的改进将会实现。当然,这在很大程度上取决于您的用例。

      【讨论】:

      • 在我的例子中,我存储了一个ID,它是一个整数作为键,一个用户定义的数据类型的object 作为值。所以,我认为我不能为此目的使用单个数组,因为 key 和 value 的数据类型不同。
      • 如果您不知道值类型,那么您只能优化对键和值引用的访问。 可能有一个单独的原语int[] 用于键和另一个用于值。如果您真的想要最后一英寸的性能,则必须对这些替代方案进行基准测试。
      • 感谢您提供不同的创新选择。我一定很想尝试一下。但是我对这三个Maps 的迭代能力的一些疑问仍然没有消除:(。我知道value 类型,因为我自己设计了该类,其对象将成为map 的value。在我的情况下,三个Maps 中的哪一个将具有最快的迭代在我的脑海中仍然没有明确的硬连线:(
      • 没有人能给出最终答案:您必须对其进行基准测试。当您这样做时,请使用 Google Caliper 或 Oracle jmh 等微基准测试工具。否则你几乎肯定会得到不恰当的结果。是的,表演是一门艰巨的课题,需要大量的知识、经验和直觉。只是没有通往它的皇家之路。
      • 我根本不会打扰迭代器,根本不需要。关键是,当您迭代时,您访问的是连续的 RAM 地址,而不是到处撒播您的请求。所有 CPU 缓存架构都在假设下一次访问将在当前访问之前进行优化。
      【解决方案6】:

      我同意 Zim-Zam 的回答,并补充一点:如果您需要在迭代过程中同时访问 keys 和 values,最快的方法是使用entrySet() 方法,如 here 所述。

      现在,如果您确定地图永远不会改变,为什么不使用单独的数据结构进行迭代呢?例如:在完成的地图上迭代一次并用它的内容填充几个数组,一个带有键,另一个带有值,在相同的对应位置。然后遍历这些数组将尽可能快。

      【讨论】:

      • 是不是说Hashmap的entrySet()方法得到的set得到的Iterator会比TreeMap的快?我知道Hashmap 中的insertion 和searching 比Treemap 快,但我想知道Iterator 对他们俩的性能
      • @Mac 不,这意味着如果使用entrySet() 返回的集合,遍历地图会更快。另一件事是遍历HashMap 可能比遍历TreeMap 更快
      • 从HashMap 的entrySet() 获得的Iterator 可能更快,因为它是在内部实现的。
      • 你用过可能。但是,我可以保证HashMap 的性能会更快吗?
      • 您应该运行分析器并亲自查看,这是唯一确定的方法。我正在根据我对类内部的了解来估计相对速度,但有很多因素在起作用 - 不要相信我的话,进行一些测量。
      【解决方案7】:

      如果你的map只用于迭代元素,而且迭代速度很重要,那么将map转换为ArrayList并迭代它,这将是最快的方式。

      【讨论】:

      • 如何将HashMap 转换为ArrayList? Arraylist 将如何存储 key-value 对 Hashmap?
      • @Mac 遍历 Map 的 entrySet() 并将键复制到键数组中,并将值复制到值数组中,如我的回答中所建议的那样。
      • @Mac - 简单。创建一个类来保存您的键和值,然后创建该类的 ArrayList ...
      • @radai 该类已经存在,它是Map.Entry 内部使用的Map
      • @ÓscarLópez - 在任何不是地图的上下文中使用 Map.Entry (比如他从地图切换到列表)都是丑陋的。
      猜你喜欢
      • 2011-10-26
      • 2011-02-22
      • 2012-05-13
      • 2014-12-06
      • 2013-12-26
      • 2013-12-11
      • 2013-08-12
      • 2016-06-03
      • 2011-03-16
      相关资源
      最近更新 更多