• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Java HashMap初始化大小设置多少合适

武飞扬头像
lzh_me
帮助1

修改公司老代码的时候,发现阿里编码规约插件提示HashMap初始化时尽量指定初始值大小,因为设置合理的初始值可以提升性能:

学新通

 HashMap继承自AbstractMap类,实现了Map、Cloneable、java.io.Serializable接口是基于散列表实现的双列集合,它存储的是key-value键值对映射,每个key-value键值对也被称为一条Entry条目。其中的 key与 value,可以是任意的数据类型,其类型可以相同也可以不同。但一般情况下,key都是String类型,有时候也可以使用Integer类型;value可以是任何类型。并且在HashMap中,最多只能有一个记录的key为null,但可以有多个value的值为null。HashMap中这些键值对(Entry)会分散存储在一个数组当中,这个数组就是HashMap的主体。

HashMap的实例有两个影响其性能的参数:初始容量和装载因子。容量是哈希表中的桶数,初始容量就是创建哈希表时的容量。负载因子是一种度量方法,用来衡量在自动增加哈希表的容量之前,哈希表允许达到的满度。当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希(即重新构建内部数据结构),这样哈希表的桶数大约是原来的两倍。

那么在使用HashMap时要求尽量指定初始值,该指定多少合适?(可以直接跳到后面看结论)

一般来说,初始值大小的设定应该根据实际需要进行设置。另外,也建议将初始值大小设置为2的幂次方,这样可以更好地利用HashMap的内部机制,提高程序的运行效率

如果不设置,默认值是16。

如我截图的代码,就存放2个变量,初始值设置2,但是设置2真的合理吗?

我们可以看一下HashMap的源码,看看它里面是怎么实现的。

不设置初始值的构造方法:

  1.  
    /**
  2.  
    * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  3.  
    * (16) and the default load factor (0.75).
  4.  
    */
  5.  
    public HashMap() {
  6.  
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  7.  
    }

设置初始值的构造方法:

  1.  
    /**
  2.  
    * Constructs an empty <tt>HashMap</tt> with the specified initial
  3.  
    * capacity and the default load factor (0.75).
  4.  
    *
  5.  
    * @param initialCapacity the initial capacity.
  6.  
    * @throws IllegalArgumentException if the initial capacity is negative.
  7.  
    */
  8.  
    public HashMap(int initialCapacity) {
  9.  
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
  10.  
    }

this方法的源码:

  1.  
    /**
  2.  
    * Constructs an empty <tt>HashMap</tt> with the specified initial
  3.  
    * capacity and load factor.
  4.  
    *
  5.  
    * @param initialCapacity the initial capacity
  6.  
    * @param loadFactor the load factor
  7.  
    * @throws IllegalArgumentException if the initial capacity is negative
  8.  
    * or the load factor is nonpositive
  9.  
    */
  10.  
    public HashMap(int initialCapacity, float loadFactor) {
  11.  
    if (initialCapacity < 0)
  12.  
    throw new IllegalArgumentException("Illegal initial capacity: "
  13.  
    initialCapacity);
  14.  
    if (initialCapacity > MAXIMUM_CAPACITY)
  15.  
    initialCapacity = MAXIMUM_CAPACITY;
  16.  
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
  17.  
    throw new IllegalArgumentException("Illegal load factor: "
  18.  
    loadFactor);
  19.  
    this.loadFactor = loadFactor;
  20.  
    this.threshold = tableSizeFor(initialCapacity);
  21.  
    }
学新通

里面的变量和方法,,我加了一些翻译说明:

  1.  
    /**
  2.  
    * The next size value at which to resize (capacity * load factor).
  3.  
    * 翻译:用于调整大小的下一次大小值(容量*负载因子)
  4.  
    * @serial
  5.  
    */
  6.  
    // (The javadoc description is true upon serialization.
  7.  
    // Additionally, if the table array has not been allocated, this
  8.  
    // field holds the initial array capacity, or zero signifying
  9.  
    // DEFAULT_INITIAL_CAPACITY.)
  10.  
    int threshold;
  11.  
     
  12.  
    //默认大小为16
  13.  
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  14.  
     
  15.  
     
  16.  
    /**
  17.  
    * Returns a power of two size for the given target capacity.(翻译:返回给定目标容量的大小为2的幂)
  18.  
    * 这段代码是一个名为 tableSizeFor 的静态方法,它接受一个整数参数 cap,并返回一个整数值。该方法的作用是计算并返回一个用于散列表(hash table)的合适的大小。
  19.  
    *
  20.  
    * 具体来说,该方法首先将 cap 减去 1,然后使用位运算符将结果向右移动,并按位或运算,以清除最低位的 1。
  21.  
    * 这个过程会重复 5 次,每次向右移动的位数为 1、2、4、8 和 16。最后,如果计算出的值小于 0,则返回 1;
  22.  
    * 如果计算出的值大于等于 MAXIMUM_CAPACITY,则返回 MAXIMUM_CAPACITY;否则返回计算出的值加 1。
  23.  
    *
  24.  
    * 这个方法的设计目的是为了在散列表中获得更好的负载因子(load factor),从而减少哈希冲突(hash collision)的概率。
  25.  
    * 负载因子是指散列表中元素的数量与散列表大小的比率。较小的负载因子意味着每个桶(bucket)中存储的元素较少,从而减少了哈希冲突的概率。
  26.  
    * 但是,较小的负载因子也会导致散列表的空间利用率较低。因此,选择合适的散列表大小非常重要。
  27.  
    * @param cap
  28.  
    * @return
  29.  
    */
  30.  
    static final int tableSizeFor(int cap) {
  31.  
    int n = cap - 1;
  32.  
    n |= n >>> 1;
  33.  
    n |= n >>> 2;
  34.  
    n |= n >>> 4;
  35.  
    n |= n >>> 8;
  36.  
    n |= n >>> 16;
  37.  
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n 1;
  38.  
    }
学新通

上面的源码可以看出threshold是个阈值,它的计算是根据tableSizeFor方法来的(根据设置的初始值计算的),到达这个阈值就会触发扩容,扩容是会影响性能的。

再看一下putMapEntries方法的源码,看注释的意思是实现了Map.putAll和Map构造函数:

  1.  
    /**
  2.  
    * Implements Map.putAll and Map constructor.
  3.  
    *
  4.  
    * @param m the map
  5.  
    * @param evict false when initially constructing this map, else
  6.  
    * true (relayed to method afterNodeInsertion).
  7.  
    */
  8.  
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
  9.  
    int s = m.size();
  10.  
    if (s > 0) {
  11.  
    if (table == null) { // pre-size
  12.  
    float ft = ((float)s / loadFactor) 1.0F;
  13.  
    int t = ((ft < (float)MAXIMUM_CAPACITY) ?
  14.  
    (int)ft : MAXIMUM_CAPACITY);
  15.  
    if (t > threshold)
  16.  
    threshold = tableSizeFor(t);
  17.  
    }
  18.  
    else if (s > threshold)
  19.  
    resize();
  20.  
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
  21.  
    K key = e.getKey();
  22.  
    V value = e.getValue();
  23.  
    putVal(hash(key), key, value, false, evict);
  24.  
    }
  25.  
    }
  26.  
    }
学新通

这里面“ float ft = ((float)s / loadFactor) 1.0F;”就是我们初始值的大小计算方法。

有兴趣的可以看下HashMap源码研究一下。

结论:默认不设置初始值大小,HashMap使用的是默认大小,值为16,如果要指定大小可以根据“存储数据的个数/0.75 1”计算出的结果作为初始值。

最后说明一下上面我的截图代码的问题,设置2是否合理?答案是不合理,根据计算公式得出,设置3是比较合理的。

原因是设置为2,刚好会触发扩容,造成完全没有必要的性能浪费。

参考以下代码,设置为2:

  1.  
    Map<String, String> map = new HashMap<>(2);
  2.  
    map.put("startTime", "");
  3.  
    getMapLength(map);
  4.  
    map.put("endTime", "");
  5.  
    getMapLength(map);

结果为:说明进行了扩容

学新通

设置为3:

  1.  
    Map<String, String> map = new HashMap<>(3);
  2.  
    map.put("startTime", "");
  3.  
    getMapLength(map);
  4.  
    map.put("endTime", "");
  5.  
    getMapLength(map);

结果为:没有进行扩容

学新通

打印Length方法:

  1.  
    /**
  2.  
    * 打印Length
  3.  
    * @param map
  4.  
    * @throws Exception
  5.  
    */
  6.  
    static void getMapLength(Map<String, String> map) throws Exception {
  7.  
    Field field = HashMap.class.getDeclaredField("table");
  8.  
    field.setAccessible(true);
  9.  
    Object[] elementData = (Object[]) field.get(map);
  10.  
    System.out.println(elementData == null ? 0 : elementData.length);
  11.  
    }

PS:关于是否设置初始值影响性能、初始值设置的合理与否影响性能,可以通过测试代码测一下,看看初始化的时间间隔。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfiiffc
系列文章
更多 icon
同类精品
更多 icon
继续加载