HashMap插入底层源码解读

HashMap.put(K key, V value),当调用HashMap的put方法插入时,你知道HashMap底层是如何将元素插入到数组列表中的吗?今天,我们就通过对HashMap底层代码的解读,探寻这个问题。
首先,HashMap的put方法通过调用putVal逻辑实现,下面是putVal的源码

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;//tab:节点数组,p: 数组指定位置的节点
    if ((tab = table) == null || (n = tab.length) == 0) // 判断当前HashMap有没有存储空间
        n = (tab = resize()).length; // 扩容
    if ((p = tab[i = (n - 1) & hash]) == null) // 通过hash计算的节点位置,判断数组当前位置有没有其他节点p
        tab[i] = newNode(hash, key, value, null); // 没有则将节点放到数组该位置
    else { // 当前数组该位置有节点
        Node<K,V> e; K k; // e: 要添加的节点;k:键
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 如果指定位置处有节点p,且p的hash与要添加的节点的hash一样,并且p的键与要添加的键一样
            e = p; // 要添加的节点e 指向 p
        else if (p instanceof TreeNode) // 如果数组指定位置处节点p是TreeNode子类
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 调用红黑树的插入方法
        else { 
            for (int binCount = 0; ; ++binCount) { // 遍历当前位置链表所有的节点
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null); // 位置最后插入节点
                    if (binCount >= TREEIFY_THRESHOLD - 1) // 链表长度大于等于7
                        treeifyBin(tab, hash);// 链表转换为红黑树 (需要桶容量大于64,否则执行扩容)
                    break;
                }
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // 判断链表中有无相同节点
                    break;
                p = e;
            }
        }
        if (e != null) { // 数组中已有该节点
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null) // onlyIfAbsent:如果为true,表示可以修改原key旧值为新值;这里表示如果可以修改旧值,或旧值为null,就修改旧值为新值
                e.value = value;
            afterNodeAccess(e);
            return oldValue; // 返回旧值
        }
    }
    ++modCount;
    if (++size > threshold) // 长度大于阈值
        resize(); // 扩容
    afterNodeInsertion(evict);
    return null;
}

我们先将源码的逻辑拆分,整理出如下几步:

  • 检查容量,扩容
  • 将元素存储进数组列表
    • 如果数组列表处没有元素,直接添加
    • 如果数组列表处有元素
      • 当前元素与要添加的元素的key相同,指针指向该元素
      • 是TreeNode节点,则调用putTreeVal添加元素,如果红黑树中有key相同元素,指针指向该元素
      • 如果都不是,则遍历链表
        • 链表中有元素与要添加的元素key相同,指针指向该元素
        • 没有,则将元素添加到最后
    • 如果指针指向不为null,表示相同位置下,有key重复元素。
      • 如果onlyInAbsent=false或重复元素的值为null,则用新值替换旧值
  • 检查容量,扩容

源码难点解析:

  1. i = (n - 1) & hash
    n - 1是数组列表的长度-1,因为HashMap的capacity是一定为2的整数次方的,所以-1后,二进制就是以01111111…的形式存在的,这样与hash进行&的时候就会取到不超过数组长度的数。比如:capacity = 16 => 10000,n-1 = 01111,hash=10111101110100101011, (n-1)&hash=1011=11<16.

补充:
为什么HashMap的capacity一定是2的整数次方,因为HashMap在初始化的时候,如果传入initCapacity,就会执行tableSizeFor(int cap)方法

static final int tableSizeFor(int cap) { // cap = 17 --> 10001
	int n = cap - 1; // n = 10000
	n |= n >>> 1; // n = 10000 | 01000 = 11000
	n |= n >>> 2; // n = 11000 | 00110 = 11110
	n |= n >>> 4; // n = 11110 | 00001 = 11111
	n |= n >>> 8; // n = 11111 | 00000 = 11111
	n |= n >>> 16; // n = 11111 | 00000 = 11111
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

通过一系列逻辑或方法,将n转成011111…的格式,最后加一就是1000000…,是2的整数次方。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值