java核心知识之集合详解八(HashMap源码解析)

HashMap底层类继承实现图

首先来看一下HashMap底层类的继承图
在这里插入图片描述
HashMap最高层的接口是Map接口。总的来说,HashMap继承了抽象AbstractMap实现类,并且实现了Serializable接口和Cloneable接口,这两个接口的具体功能请查看我关于ArrayList实现类的源码解析。

HashMap类的基本特点

  • 以Key-Value的格式存储,key值不可以重复,value值可以重复
  • 存储的key值和value值可以为null,但null值当做key键存储只能有一个,null值作为是value值可以有多个。
  • HashMap是线程不安全实现类。与HashTable有很大区别,HashTable是线程安全实现类。key和value值都不可以重复,且key值不能为null值。
  • HashMap类的初始化容量为16,每一次达到扩容后,扩容的容量是原来的两倍。
  • java1.8之前的底层数据结构是数组和链表。数组是HashMap的主体,jdk1.8以后的 HashMap 在为了解决哈希冲突而进行了改变,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于64,那么会优先选择数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
  • 使用2的幂次方作为哈希表的大小。因为在jdk7的HashMap类是基于数组和链表实现的,jdk8之后添加了红黑树,在HashMap类中,当某个key-value需要存储到数组里面时,必须要有对应的数组下标index,而且这个数组下标是不可以越界的,在存储过程中,首先由key得到hashCode值,hashCode是一个数值,它通过hashCode&(table.length-1)运算可以得到一个数组下标,这种运算方法是与运算,比取余运算速度快,因为是通过位运算进行的。在这里就可以知道为什么必须要使用2的幂次方作为哈希表的大小了,因为计算数组下标的运算方法限制:位运算

HashMap类底层数据结构解析

类的主要结构代码

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    }

这里没什么好说的,该说的上面已经提及。

类的主要变量

/**
 * 默认的初始容量,必须是2的幂次方
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

/*
 * 最大容量,如果一个更高的值由任何一个带参数的构造函数隐式指定时使用。
 * 必须是 2 <= 1<<30 的幂。
 */
 static final int MAXIMUM_CAPACITY = 1 << 30;
 
 // 构造函数中未指定时使用的负载因子
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 
/**
 * 树形阈值,该值必须要大于2并且应当至少为8
 */
 static final int TREEIFY_THRESHOLD = 8;
 
/**
 * 取消阈值, 最多 6 以在移除时进行收缩检测。
 */
 static final int UNTREEIFY_THRESHOLD = 6;

// 最小树形容量,可进行树化的最下容量,
 static final int MIN_TREEIFY_CAPACITY = 64;

// 存储元素的数组,总是2的幂次倍
 transient Node<k,v>[] table;
 
// 存放具体元素的集
 transient Set<map.entry<k,v>> entrySet;
 
// 存放元素的个数,注意这个不等于数组的长度。
 transient int size;
 
// 每次扩容和更改map结构的计数器
 transient int modCount;
 
// 临界值(容量*填充因子) 当实际大小超过临界值时,会进行扩容
int threshold;

// 加载因子
final float loadFactor;

加载因子:
loadFactor 加载因子是控制数组存放数据的疏密程度loadFactor 的值的范围是0到1,当loadFactor的值越接近于 1,那么 数组中存放的数据也就越多,存放的密度也就约密,而且链表的长度也会随之增加,loadFactor 的值越小,越接近于0,,数组中存放的数据也就越少,存放的密度也就越稀疏。

如果loadFactor 的值太大,当程序中需要查找元素时会导致查找元素效率低,如果loadFactor的值太小导致数组存放数据的利用率低,这样子存放的数据会很分散。loadFactor 的默认值为 0.75f,这个是官方给出的一个比较好值

HashMap类给定的默认容量为 16,负载因子为 0.75f。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,这是非常消耗性能的。

临界值threshold:
threshold = capacity * loadFactor,当 size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,临界值threshold是衡量数组是否需要扩增的一个标准。

Node节点类源码

static class Node<K,V> implements Map.Entry<K,V> {
    
    
		// 存储hash值
        final int hash;
        // HashMap的key值
        final K key;
        // HashMao的value值
        V value;
        // 存储下一个节点地址
        Node<K,V> next;
		// 构造方法
        Node(int hash, K key, V value, Node<K,V> next) {
    
    
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
		// 
        public final K getKey()        {
    
     return key; }
        public final V getValue()      {
    
     return value; }
        // 重写toString方法
        public final String toString() {
    
     return key + "=" + value; }
		// 获取hashCode值
        public final int hashCode() {
    
    
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
    
    
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
		// 重写Object类的equals方法
        public final boolean equals(Object o) {
    
    
        	// 判断是当前的对象
            if (o == this)
                return true;
             // 判断是否是Map.Entry
            if (o instanceof Map.Entry) {
    
    
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                // 调用Object方法判断key值与value值是否相等
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

树节点类源码解析

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    
    
        TreeNode<K,V> parent;  // 父节点
        TreeNode<K,V> left; // 左节点
        TreeNode<K,V> right; // 右节点
        TreeNode<K,V> prev;    // 前一个节点,需要在删除时取消链接
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
    
    
            super(hash, key, val, next);
        }

        /**
         * 返回包含此节点的树的根。
         */
        final TreeNode<K,V> root() {
    
    
            for (TreeNode<K,V> r = this, p;;) {
    
    
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }
}

HashMap类的hash源码对比

在JDK1.7和JDK1.7之前的HashMap类的底层是数组和链表,两者结合在一起就是链表散列。HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,也就是调用HashMap类的hash方法,使用这个方法也就是实现扰动,可以防止一些实现比较差的hashCode方法从而产生哈希碰撞。通过 (table.length - 1) & hash 判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。

拉链法:

将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
在这里插入图片描述

jdk1.8的HashMap类的hash源码:

 static final int hash(Object key) {
    
    
      int h;
      // key.hashCode():返回散列值也就是hashcode
      // ^ :按位异或
      // >>>:无符号右移,忽略符号位,空位都以0补齐
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  }

jdk1.7的HashMap类的hash源码:

static int hash(int h) {
    
    
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

代码分析:
整体来说,JDK7的HashMap类的hash方法实现的整体性能差,而JDK8实现的HashMap类的hash方法性能比较好,因为JDK7的HashMap类的hash方法扰动了四次。

HashMap类的构造方法

默认构造方法:

// 构造一个具有初始默认容量的空HashMap对象。
public HashMap() {
    
    
		// 适用于其他字段
        this.loadFactor = DEFAULT_LOAD_FACTOR; defaulted
    }

指定容量大小的构造方法:

// 构造一个具有指定初始容量和默认加载因子 (0.75) 的空HashMap,
// initialCapacity为指定的容量大小数值
 public HashMap(int initialCapacity) {
    
    
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

指定容量大小和加载因子的构造函数:

 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;
        this.threshold = tableSizeFor(initialCapacity);
    }

包含一个Map的构造函数:

 public HashMap(Map<? extends K, ? extends V> m) {
    
    
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

putMapEntries()方法

 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    
    
 		// 获取table大小
        int s = m.size();
        if (s > 0) {
    
    
        	// 判断table是否已经初始化
            if (table == null) {
    
    
            // 如果没有初始化,重新计算容量负载因子。s为table的大小
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
              // 如果超出最大的阈值,则重新计算并设置阈值。
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            // 如果已经初始化table,而且table的大小已经超出阈值,则扩容
            else if (s > threshold)
                resize();
            // 遍历每一个元素,然后调用putVal()方法存储
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
    
    
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

HashMap类添加元素

HashMap类添加元素首先需要根据hash值计算数组下标,如果该位置没有元素,那就直接插入,如果定位到的数组中有元素,那就需要和该元素比较hash值和key值,如果key值相等,那就直接覆盖,如果key值不相等,那就需要判断该节点是否为树节点或者是链表节点,如果是树节点,那就直接调用树节点的插入元素的方法e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)将元素添加进入。如果是链表节点,那就需要遍历链表节点并比较是否是相同的元素,如果实现相同的元素,那就执行值覆盖,返回旧值,如果链表中不存在相同的元素,那就把添加进的元素插入链表的尾部。

注意: 使用者只能使用HashMap提供的put()方法,而不能使用putVal()方法。putVal()方法只能内部使用。
在这里插入图片描述

// 将指定的值与此映射中的指定键相关联。如果映射先前包含键的映射,则替换旧值
public V put(K key, V value) {
    
    
		// 传入key的hash值,key值,value值
        return putVal(hash(key), key, value, false, true);
    }
/**
 * @param key的hash值
 * @param key的值
 * @param 要存放的value值
 * @param onlyIfAbsent 如果为真,则不改变已存在的值。
 * @param evict 如果为 false,则表处于创建模式。
 */ 
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    
    
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 判断table是否初始化完成,没有初始化完成则调用扩容函数
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,
        // 新生成结点放入桶中(此时,这个结点是放在数组中)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
         // 如果桶中已经存在了其他的元素,不为空
        else {
    
    
            Node<K,V> e; K k;
            // 分别比较hash值,key值是否相等而且不为空,如果符合,则取出这个节点
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 使用 e来存储 p
                e = p;
            //如果hash值不相等,也就是key不相等;则判断节点是否为树节点
            else if (p instanceof 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);
                         // 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法
                    	// 这个方法会根据 HashMap 数组来决定是否转换为红黑树。
                    	// 只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只能对数组扩容。
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 判断新添加的元素是否存在于链表中,也就是判断hash值和key值是否相等且不为空。
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        // 直接跳出,因为已经存在了该值。
                        break;
                    p = e;
                }
            }
            // e!=null 说明存在旧值的key与要插入的key"相等"
            if (e != null) {
    
    
                V oldValue = e.value;
                // 进行值覆盖,然后返回旧值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        // 如果是由于新添加的元素导致容量达到最大值,则执行扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

JDK8与JDK7的HashMap类的put方法对比

put(K key, V value)方法是将指定的key, value对添加到HashMap里。首先判断table是否为空,然后会对table做一次查找,看是否包含该元素,如果已经包含则执行值覆盖,然后直接返回,查找过程类似于getEntry()方法;如果没有找到,则会通过addEntry(int hash, K key, V value, int bucketIndex)方法插入新的entry,插入方式为头插法

public V put(K key, V value)
    if (table == EMPTY_TABLE) {
    
    
    inflateTable(threshold);
}
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    // 查找数组的位置
    int i = indexFor(hash, table.length);
    // 遍历数组存储的链表
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    
     // 先遍历
        Object k;
        // 如果值相等,那就直接覆盖,然后返回旧值
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    
    
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    // 如果元素不存在,那就执行插入
    addEntry(hash, key, value, i);  // 再插入
    return null;
}

void addEntry(int hash, K key, V value, int bucketIndex) {
    
    
	// 判断负载因子如果超过阈值,且传入的桶数组不为空
    if ((size >= threshold) && (null != table[bucketIndex])) {
    
    
    	//自动扩容
        resize(2 * table.length);
        // 重新计算哈希
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = hash & (table.length-1);
    }
    //在冲突链表头部插入新的entry
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

HashMap类获取元素

public V get(Object key) {
    
    
        Node<K,V> e;
        // 查找数组元素,如果查找为空,则返回null,如果不为空,那就返回对应的值。
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
// 查找元素
final Node<K,V> getNode(int hash, Object key) {
    
    
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // 判断table不为空且长度大于0,然后根据hash值计算数组下标
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
    
    
            // 找出数组下标,判断第一个节点是否就是要寻找的节点元素。
            if (first.hash == hash && // always check first node
            // 检查对应的key值是否相等
                ((k = first.key) == key || (key != null && key.equals(k))))
                // 确定第一个就是要找的,直接返回
                return first;
             // 第一个不是要找的,判断节点结构
            if ((e = first.next) != null) {
    
    
            // 如果节点是树节点
                if (first instanceof TreeNode)
                // 返回树节点查找元素的方法
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
    
    
                // 如果是链表节点,那就一个个循环遍历并比较对应的key值
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        // 找到直接返回
                        return e;
                } while ((e = e.next) != null);
            }
        }
        // 元素找不到返回为空
        return null;
    }

HashMap类删除元素

// 根据key值删除对应的映射
 public V remove(Object key) {
    
    
        Node<K,V> e;
        // 删除元素,如果删除的元素找不到,则返回null,如果成功删除值,那就返回要删除对应的值。
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
    
    
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        // 判断table是否不为空且长度大于0,根据hash值计算数组下标
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
    
    
            Node<K,V> node = null, e; K k; V v;
            // 判断第一个是否就是要删除的
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 存储要删除的节点
                node = p;
             // 判断接下来的数组存储的结构
            else if ((e = p.next) != null) {
    
    
            // 如果数组中存储的是树节点
                if (p instanceof TreeNode)
                // 调用树节点查找元素的方法,使用node节点存储要删除的元素
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
    
    
                // 如果数组中存储的是链表
                    do {
    
    
                 // 遍历并判断每一个节点是否是要删掉的元素,查找对于的key值。
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
    
    
                          	 node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            // 判断是否找到要删除的元素,或者是传入的要删除的值是否为空
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
    
    
               // 如果要删除的元素是树节点,那就调用对应的树节点删除元素的方法
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                // 如果要删除的元素是链表且是第一个
                else if (node == p)
                    tab[index] = node.next;
                // 删除的元素是链表且不是第一个
                else
                    p.next = node.next;
                ++modCount;
                // 删除长度
                --size;
                afterNodeRemoval(node);
                // 返回删除的节点数据
                return node;
            }
        }
        // 删除不成功
        return null;
    }

HashMap类的扩容流程

开辟内存空间: HashMap的扩容需要数组扩容,但是数组占用的是连续的内存空间,所以一旦需要扩容就需要重新开辟内存空间。

创建2的幂次方大小的数组: 因为HashMap计算数组下标的方法是通过位运算实现的,所以数组的大小必须是2的幂次方。

遍历数组并转移数据: 创建完数组后,就遍历旧数组上的每一个位置,如果数组位置上是一个链表,那么就把这个链表上的元素全部转移到新的数组上。

JDK7和JDK8扩容机制的不同对比

JDK7及其之前的java版本中HashMap的底层是链表加数组的结构,那么扩容的过程中,需要遍历链表上的每一个元素,然后按照每一个元素的hashCode值进行计算出新数组的下标,

JDK8之后的HashMap类的底层数据结构是数组和链表加红黑树,至于什么时候使用红黑树进行优化上文已经提及。在JDK8中会使用到一个双向链表来维护红黑树中的元素,首先jdk8在转移某个位置上的元素时,会判断这个位置是不是一个红黑树,如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。

扩容代码分析(如有纰漏,还望指正):

 final Node<K,V>[] resize() {
    
    
 		// 保存旧的位桶数组 Node<K,V> table 
        Node<K,V>[] oldTab = table;
        // 判断旧的是否为空,为空则赋值为零,不为空则赋值为旧的数组长度。
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 保存旧的HashMap的负载因子
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
    
    
        // 超过最大值,重新调整后赋值
            if (oldCap >= MAXIMUM_CAPACITY) {
    
    
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
          // 不超过最大值,则扩充为原来的两倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 负载因子扩展为原来的两倍
                newThr = oldThr << 1; 
        }
        // 初始容量被置于阈值
        else if (oldThr > 0) 
            newCap = oldThr;
            // 零初始阈值表示使用默认值
        else {
    
                   
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
         // 计算新的resize上限
        if (newThr == 0) {
    
    
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({
    
    "rawtypes","unchecked"})
        // 创建新的位桶数组对象,newCap为新的长度。
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
    
    
        // 遍历bucket,把每一个bucket都移动到新的buckets中
            for (int j = 0; j < oldCap; ++j) {
    
    
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
    
    
                    oldTab[j] = null;
                    // 如果该数组位置上只有单个元素,直接迁移这个元素
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                        // 判断是否为红黑树,拆分链表并存储数据
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else {
    
     
  					// 这块是处理链表的情况,
                    // 需要将此链表拆成两个链表,放到新的数组中,并且保留原来的先后顺序
                    // loHead、loTail 对应一条链表,hiHead、hiTail 对应另一条链表。
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
    
    
                        // 原索引
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
    
    
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // 原索引+oldCap
                            else {
    
    
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 原索引放到bucket里
                        if (loTail != null) {
    
    
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        // 原索引+oldCap放到bucket里
                        if (hiTail != null) {
    
    
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

文章参考:

  • https://javaguide.cn/java/collection/hashmap-source-code.html#put-%E6%96%B9%E6%B3%95
  • https://www.pdai.tech/md/java/collection/java-map-HashMap&HashSet.html

猜你喜欢

转载自blog.csdn.net/m0_46198325/article/details/123725964