为啥创建 HashMap 比创建 Object[] 更快?

Posted

技术标签:

【中文标题】为啥创建 HashMap 比创建 Object[] 更快?【英文标题】:Why is creating a HashMap faster than creating an Object[]?为什么创建 HashMap 比创建 Object[] 更快? 【发布时间】:2015-10-03 19:44:16 【问题描述】:

我尝试构建自己的 Map 以提高特殊环境的性能,并且我意识到一些非常有趣的事情:创建 new Hashmap<Integer,String>(2000)new Object[2000] 快​​ - 无论我以什么顺序执行这些命令。这让我很困惑,尤其是。因为Hashmap构造函数包含table = new Entry[capacity],根据this。我的测试台有问题吗?

public static void test(int amm) //amm=1_000_000
    Map<Integer,String> m1 = null;
    Object[] arr = null;

    long time = System.nanoTime();
    for(int i = 0; i < amm; i++)
        m1 = new HashMap<Integer, String>(2000);
    
    System.out.println("m1: " + (System.nanoTime() - time)); //m1: 70_455_065

    time = System.nanoTime();
    for(int i = 0; i < amm; i++)
        arr = new Object[2000];
    
    System.out.println("arr: " + (System.nanoTime() - time)); //arr: 1_322_473_803

我很想看看在另一台计算机上的测试结果。我不知道为什么创建 HashMap 比创建 Object[] 快 10 倍。

【问题讨论】:

这取决于JDK的实现。您使用哪个 JDK 和版本来执行此测试? @BladeCoder 我根据 java.version 使用 1.7.0_79 (IceTea 2.5.5) (7u79-2.5.5-0ubuntu0.14.04.2)。 确实在 OpenJDK 1.7 的封闭版本中,HashMap 的构造函数中没有初始化数组(它在第一次插入时完成),这解释了速度差距:grepcode.com/file_/repository.grepcode.com/java/root/jdk/… 【参考方案1】:

如果你看HashMap的实现,构造函数看起来像:

public HashMap(int initialCapacity, float loadFactor) 
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();

init() 看起来像:

/**
 * Initialization hook for subclasses. This method is called
 * in all constructors and pseudo-constructors (clone, readObject)
 * after HashMap has been initialized but before any entries have
 * been inserted.  (In the absence of this method, readObject would
 * require explicit knowledge of subclasses.)
 */
void init() 

所以initialCapacity 实际上并不习惯于创建数组。它在哪里使用?查看put() 方法。

public V put(K key, V value) 
    if (table == EMPTY_TABLE) 
        inflateTable(threshold);
    
    // hidden
 

在执行 put 时,实际上创建了数组。我没有显示inflateTable(),但它做了一些数学运算并初始化了数组。

【讨论】:

你的源代码来自哪里?我找到了docjar.com/html/api/java/util/HashMap.java.html - 有什么不同。不过,我会尝试一下 :) 编辑:你是对的,添加 put(5,"Hello") 可以解决问题。有趣,你 @alexberne 我使用了 intellij 的视图源,在 MacOS 上是 JDK_1.7.0_51 有趣...因此,如果您有一个程序在加载时初始化一个大 HashMap 并在用户单击按钮时将元素放入其中,那么 OpenJDK 实现将显示不同的性能。 -- 在 OpenJDK 上,应用程序将需要很长时间才能加载,并且在用户交互时会很快在 OracleJDK 上,应用程序将在第一次按钮单击时快速加载并且需要很长时间。 -- OracleJDK 解决方案的好处显然是能够创建大量未使用的 HashMap 而不会占用太多内存 @Falco:我怀疑您是否会注意到在单击按钮时创建大小为 2000 的对象数组对性能的影响。 @Falco,不,此代码来自OpenJDK,而不是某些 Oracle 专有补丁。 Oracle Java 非常接近 OpenJDK,只是并非所有组件都已打开(WebStart、JMC、HotSpot for ARM 等)。【参考方案2】:

一个空的 HashMap 对象比一个包含 2000 个 Object 引用的数组小得多。即使您将 2000 传递给 HashMap 构造函数的 initialCapacity 参数,它实际上还没有为对象创建 2000 个空间。

【讨论】:

以上是关于为啥创建 HashMap 比创建 Object[] 更快?的主要内容,如果未能解决你的问题,请参考以下文章

对象比相应的 .RData 文件大得多。为啥?可以手动做吗?

为啥 Android Studio 创建的 APK 比 Eclipse 创建的 APK 大?

为啥这个循环比创建字典的字典理解更快?

[笔记]为啥hashmap查询速度快? 如何理解hashmap的散列?

为啥在 Windows 上创建新进程比在 Linux 上更昂贵?

为啥 hash map 比 trie map 好?