HashMap扩容原理

3 篇文章 0 订阅
1 篇文章 0 订阅

HashMap扩容原理

一. HashMap源码分析

1. HashMap重要属性介绍

    /**
     * 默认初始容量-16 必须是2的幂。
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    /**
     * 最大支持容量1073741824
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;
    /**
     * 默认负载因子0.75f
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /**
     * 当链表的长长度大于8时会将链表转换为红黑树
     */
    static final int TREEIFY_THRESHOLD = 8;
    /**
     * 当链表的长长度小于6时会将链表红黑树转换为连表
     */
    static final int UNTREEIFY_THRESHOLD = 6;
    /**
     * 当entry数组的长度大于64才允许将链表转换为红黑树,否则应该直接扩容而不是将连表树化
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * 存放entry的数组
     */
    transient Node<K,V>[] table;

    /**
     * 存放entry的数组
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * key-value对的数量
     */
    transient int size;

    /**
     * 扩容时的阈值,计算规则为 容量*负载因子
     */
    int threshold;

    /**
     * 负载因子,用于计算HashMap的容量达到多少时进行扩容
     */
    final float loadFactor;

2. HashMap构造方法说明

HashMap一共提供了三个构造方法,无参构造,指定初始容量和指定初始容量及负载因子的构造方法。

  • 无参构造:所有的属性都取默认值,如loadFactory = 0.75F,默认的初始容量为DEFAULT_INITIAL_CAPACITY即16。
    public HashMap() {
        //负载因子默认值 0.75f
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
  • 指定初始容量:负载因为为默认的0.75F,而指定初始容量之后,HashMap并没有直接将指定的初始容量作为真正的HashMap的容量,而是经过 计算获取一个比给定整数大或者等于的最接近的2的幂次方整数,如给定3,则设置为4,给定9,则设置为16。
    public HashMap(int initialCapacity) {
        //指定初始容量和负载因子
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
  • 指定初始容量及负载因子:其实该构造方法并不常用,因为负载因子的设定关系到扩容的时机,负载因子设置过大会导致HashMap的hash冲突比较严重,影响查询的效率,设置过小,会导致频繁地进行扩容,影响添加元素的效
    public HashMap(int initialCapacity, float loadFactor) {
        // 如果初始容量小于0则抛异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
        //如果初始容量超出最大容量则设置为最大容量
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //负载因子小于0或为NaN则抛异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

3. HashMap容量为什么要设置为2的幂次方整数

HashMap初始化容量非得是2的幂次方,2的倍数不行么,奇数不行么?

  • 2的幂次方:hashmap在确定元素落在数组的位置的时候,计算方法是(n - 1) & hash,n为数组长度也就是初始容量 。hashmap结构是数组,每个数组里面的结构是node(链表或红黑树),正常情况下,如果你想放数据到不同的位置,肯定会想到取余数确定放在那个数组里,计算公式:hash% n,这个是十进制计算。在计算机中, (n - 1) & hash,当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n,计算更加高效。
  • 奇数不行:在计算hash的时候,确定落在数组的位置的时候,计算方法是(n - 1) & hash,奇数n-1为偶数,偶数2进制的结尾都是0,经过hash值&运算后末尾都是0,那么0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这样就会造成空间的浪费而且会增加hash冲突。
 static final int tableSizeFor(int cap) {
        //其中capacity设置为2的幂次方整数是为了减少hash冲突的第一步
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

4. HashMap扩容逻辑

  1. 通过hash(Object key)算法得到hash值;
  2. 判断table是否为null或者长度为0,如果是执行resize()进行扩容;
  3. 通过hash值以及table数组长度得到插入的数组索引i,判断数组table[i]是否为空或为null;
  4. 如果table[i] == null,直接新建节点添加,转向 8),如果table[i]不为空,转向 5)
  5. 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,这里的相同指的是hashCode以及equals,否则转向 6)
  6. 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转7)
  7. 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可
  8. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容

5. HashMap的扩容机制

HashMap扩容的时机取决于扩容阈值threshold,阈值的计算规则为threshold = capicaty *loadFactor,初始容量capicaty 默认16,负载因子为0.75, 当HashMap的size大于或等于 threshold则HashMap会进行扩容处理。

详情请参考以下链接:
tableSizeFor
hashmap扩容
HashMap原理(二) 扩容机制及存取原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值