zoukankan      html  css  js  c++  java
  • java中hashmap容量的初始化

    HashMap使用HashMap(int initialCapacity)对集合进行初始化。

    在默认的情况下,HashMap的容量是16。但是如果用户通过构造函数指定了一个数字作为容量,那么Hash会选择大于该数字的第一个2的幂作为容量。比如如果指定了3,则容量是4;如果指定了7,则容量是8;如果指定了9,则容量是16。

    为什么要设置HashMap的初始化容量

    在《阿里巴巴Java开发手册》中,有一条开发建议是建议我们设置HashMap的初始化容量。

    下面我们通过具体的代码来了解下为什么会这么建议。

    我们先来写一段代码在JDK1.7的环境下运行,来分别测试下,在不指定初始化容量和指定初始化容量的情况下性能情况的不同。

    public static void main(String[] args) {
        int aHundredMillion = 10000000;
    
        // 未初始化容量
        Map<Integer, Integer> map = new HashMap<>();
        long s1 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map.put(i, i);
        }
        long s2 = System.currentTimeMillis();
        System.out.println("未初始化容量,耗时: " + (s2 - s1)); // 14322
    
        // 初始化容量为50000000
        Map<Integer, Integer> map1 = new HashMap<>(aHundredMillion / 2);
        long s3 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map1.put(i, i);
        }
        long s4 = System.currentTimeMillis();
        System.out.println("初始化容量5000000,耗时: " + (s4 - s3)); // 11819
    
        // 初始化容量为100000000
        Map<Integer, Integer> map2 = new HashMap<>(aHundredMillion);
        long s5 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map2.put(i, i);
        }
        long s6 = System.currentTimeMillis();
        System.out.println("初始化容量为10000000,耗时: " + (s6 - s5)); // 7978
    }

    从以上的代码不难理解,我们创建了3个HashMap,分别使用默认的容量(16)、使用元素个数的一半(5千万)作为初始容量和使用元素个数(一亿)作为初始容量进行初始化,然后分别向其中put一亿个KV。

    从上面的打印结果中可以得到一个初步的结论:在已知HashMap中将要存放的KV个数的时候,设置一个合理的初始化容量可以有效地提高性能。下面我们来简单分析一下原因。

    我们知道,HashMap是有扩容机制的。所谓的扩容机制,指的是当达到扩容条件的时候,HashMap就会自动进行扩容。而HashMap的扩容条件就是当HashMap中的元素个数(Size)超过临界值(Threshold)的情况下就会自动扩容。

    threshold = loadFactor * capacity

    在元素个数超过临界值的情况下,随着元素的不断增加,HashMap就会发生扩容,而HashMap中的扩容机制决定了每次扩容都需要重建hash表,这一操作需要消耗大量资源,是非常影响性能的。因此,如果我们没有设置初始的容量大小,HashMap就可能会不断发生扩容,也就使得程序的性能降低了。

    另外,在上面的代码中我们会发现,同样是设置了初始化容量,设置的数值不同也会影响性能,那么当我们已知HashMap中即将存放的KV个数的时候,容量的设置就成了一个问题。

    HashMap中容量的初始化

    开头提到,在默认的情况下,当我们设置HashMap的初始化容量时,实际上HashMap会采用第一个大于该数值的2的幂作为初始化容量。

    Map<String, String> map = new HashMap<>(1);
    map.put("huangq", "yanggb");
    
    Class<?> mapType = map.getClass();
    Method capacity = mapType.getDeclaredMethod("capacity");
    capacity.setAccessible(true);
    System.out.println("capacity : " + capacity.invoke(map)); // 2

    当初始化的容量设置成1的时候,通过反射取出来的capacity却是2。在JDK1.8中,如果我们传入的初始化容量为1,实际上设置的结果也是1。上面的代码打印的结果为2的原因,是代码中给map塞入值的操作导致了扩容,容量从1扩容到了2。事实上,在JDK1.7和JDK1.8中,HashMap初始化容量(capacity)的时机不同。在JDK1.8中,调用HashMap的构造函数定义HashMap的时候,就会进行容量的设定。而在JDK1.7中,要等到第一次put操作时才进行这一操作。

    因此,当我们通过HashMap(int initialCapacity)设置初始容量的时候,HashMap并不一定会直接采用我们传入的数值,而是经过计算,得到一个新值,目的是提高hash的效率。比如1->1、3->4、7->8和9->16。

    HashMap中初始容量的合理值

    通过上面的分析我们可以知道,当我们使用HashMap(int initialCapacity)来初始化容量的时候,JDK会默认帮我们计算一个相对合理的值当做初始容量。那么,是不是我们只需要把已知的HashMap中即将存放的元素个数直接传给initialCapacity就可以了呢?

    initialCapacity = (需要存储的元素个数 / 负载因子) + 1

    这里的负载因子就是loaderFactor,默认值为0.75。

    initialCapacity = expectedSize / 0.75F + 1.0F

    上面这个公式是《阿里巴巴Java开发手册》中的一个建议,在Guava中也是提供了相同的算法,更甚之,这个算法实际上是JDK8中putAll()方法的实现。这是公式的得出是因为,当HashMap内部维护的哈希表的容量达到75%时(默认情况下),就会触发rehash(重建hash表)操作。而rehash的过程是比较耗费时间的。所以初始化容量要设置成expectedSize/0.75 + 1的话,可以有效地减少冲突,也可以减小误差。

    总结

    当我们想要在代码中创建一个HashMap的时候,如果我们已知这个Map中即将存放的元素个数,给HashMap设置初始容量可以在一定程度上提升效率。

    但是,JDK并不会直接拿用户传进来的数字当做默认容量,而是会进行一番运算,最终得到一个2的幂。而为了最大程度地避免扩容带来的性能消耗,通常是建议可以把默认容量的数字设置成expectedSize / 0.75F + 1.0F。

    在日常开发中,可以使用Guava提供的一个方法来创建一个HashMap,计算的过程Guava会帮我们完成。

    Map<String, String> map = Maps.newHashMapWithExpectedSize(10);

    最后要说的一点是,这种算法实际上是一种使用内存换取性能的做法,在真正的应用场景中要考虑到内存的影响。

    "当你认真喜欢一个人的时候,你的全世界都是她。"

  • 相关阅读:
    zoj 2316 Matrix Multiplication 解题报告
    BestCoder7 1001 Little Pony and Permutation(hdu 4985) 解题报告
    codeforces 463C. Gargari and Bishops 解题报告
    codeforces 463B Caisa and Pylons 解题报告
    codeforces 463A Caisa and Sugar 解题报告
    CSS3新的字体尺寸单位rem
    CSS中文字体对照表
    引用外部CSS的link和import方式的分析与比较
    CSS样式表引用方式
    10个CSS简写/优化技巧
  • 原文地址:https://www.cnblogs.com/yanggb/p/11613070.html
Copyright © 2011-2022 走看看