深入HashMap源码

前言

在初次看到 HashMap 时,都惊奇于其 O(1) 的 get 操作的时间复杂度。当时在我们已知的数据结构中,只有基于下标访问数组时,才能提供 O(1) get 操作的时间复杂度。

实际上,HashMap 所提供的 O(1) 是平均时间复杂度,大多数情况下保证 O(1) 。其实极端情况下,有可能退化为 O(N) 的时间复杂度噢,这又是为什么呢?

hashMap的属性

//初始化的容量 一般是调用无参构造方法后第一次put时初始化容量(延迟初始化)
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
     //HashMap的最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
     //默认的加载因子 当然也可以指定
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
     //树化的阈值
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
     //非树化的阈值其实就是退化链表
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
     //树化的最小容量
    static final int MIN_TREEIFY_CAPACITY = 64;

```java
//底层存储的数组
 transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     */
     //调用 `#entrySet()` 方法后的缓存
    transient Set<Map.Entry<K,V>> entrySet;

    /**
    key-value 的键值对数量
     * The number of key-value mappings contained in this map.
     */
    transient int size;

    /**
    HashMap 的修改次数
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;

    /**
    阀值 当 size 超过threshold时, 会进行扩容
 *
     * The next size value at which to resize (capacity * load factor).
     *
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     *  扩容因子
     */
    final float loadFactor;
**看下table数组组成**

```java
static class Node<K,V> implements Map.Entry<K,V> {

    /**
     * 哈希值
     */
    final int hash;
    /**
     * KEY 键
     */
    final K key;
    /**
     * VALUE 值
     */
    V value;
    /**
     * 下一个节点
     */
    Node<K,V> next;

    // ... 省略实现方法

}

实现了 Map.Entry 接口,该接口定义在 java.util.Map 接口中
hash + key + value 属性,定义了 Node 节点的 3 个重要属性。
next 属性,指向下一个节点。通过它可以实现 table 数组的每一个位置可以形成链表。

构造方法

① #HashMap() 延迟初始化
#HashMap() 构造方法,创建一个初始化容量为 16 的 HashMap 对象。代码如下:



/**
 * 默认的初始化容量
 *
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
 * 默认加载因子为 0.75
 *
 * The load factor used when none specified in constructor.
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
 * Constructs an empty {@code HashMap} with the default initial capacity
 * (16) and the default load factor (0.75).
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

初始化 loadFactor 为 DEFAULT_LOAD_FACTOR = 0.75 。
在该构造方法上,我们并没有看到 table 数组的初始化。它是延迟初始化,在我们开始往 HashMap 中添加 key-value 键值对时,在 #resize() 方法中才真正初始化。

HashMap(int initialCapacity)

/**
 * Constructs an empty <tt>HashMap</tt> with the specified initial
 * capacity and the default load factor (0.75).
 *
 * @param  initialCapacity the initial capacity.
 * @throws IllegalArgumentException if the initial capacity is negative.
 */
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

内部调用 #HashMap(int initialCapacity, float loadFactor) 构造方法。

 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
               // 避免 initialCapacity 超过 MAXIMUM_CAPACITY                                    
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
       // 计算 threshold 阀值
        this.threshold = tableSizeFor(initialCapacity);
    }

HashMap(Map<? extends K, ? extends V> m)

public HashMap(Map<? extends K, ? extends V> m) {
    // 设置加载因子
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    // <X> 批量添加到 table 中
    putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    // <1>
    if (s > 0) {
        // 如果 table 为空,说明还没初始化,适合在构造方法的情况
        if (table == null) { // pre-size
            // 根据 s 的大小 + loadFactor 负载因子,计算需要最小的 tables 大小
            float ft = ((float)s / loadFactor) + 1.0F; // + 1.0F 的目的,是因为下面 (int) 直接取整,避免不够。
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            // 如果计算出来的 t 大于阀值,则计算新的阀值
            if (t > threshold)
                threshold = tableSizeFor(t);
        // 如果 table 非空,说明已经初始化,需要不断扩容到阀值超过 s 的数量,避免扩容
        } else {
            // Because of linked-list bucket constraints, we cannot
            // expand all at once, but can reduce total resize
            // effort by repeated doubling now vs later
            while (s > threshold && table.length < MAXIMUM_CAPACITY)
                resize(); // 扩容
        }

        // <2> 遍历 m 集合,逐个添加到 HashMap 中。
        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(Object key) 静态方法,计算 key 的哈希值。代码如下:

static final int hash(Object key) {
    int h;
    // h = key.hashCode() 计算哈希值
    // ^ (h >>> 16) 高 16 位与自身进行异或计算,保证计算出来的 hash 更加离散
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

高效性:从整个计算过程上来说,^ (h >>> 16) 只有这一块的逻辑,两个位操作,性能肯定是有保障的。那么,如果想要保证哈希函数的高效性,就需要传入的 key 自身的 Object#hashCode() 方法的高效即可。位运算比快余块
位运算是更底层的计算机运算方式,加减乘除是建立在位运算的基础上实现的。层次越高效率越低。这是一种比较浅显的认识,如果细追得用指令集解释

put(K key, V value) 添加单个元素

public V put(K key, V value) {
    // hash(key) 计算哈希值
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; // tables 数组
    Node<K,V> p; // 对应位置的 Node 节点
    int n; // 数组大小
    int i; // 对应的 table 的位置
    // <1> 如果 table 未初始化,或者容量为 0 ,则进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize() /*扩容*/ ).length;
    // <2> 如果对应位置的 Node 节点为空,则直接创建 Node 节点即可。
    if ((p = tab[i = (n - 1) & hash] /*获得对应位置的 Node 节点*/) == null)
        tab[i] = newNode(hash, key, value, null);
    // <3> 如果对应位置的 Node 节点非空,则可能存在哈希冲突
    else {
        Node<K,V> e; // key 在 HashMap 对应的老节点
        K k;
        // <3.1> 如果找到的 p 节点,就是要找的,则则直接使用即可
        if (p.hash == hash && // 判断 hash 值相等
            ((k = p.key) == key || (key != null && key.equals(k)))) // 判断 key 真正相等
            e = p;
        // <3.2> 如果找到的 p 节点,是红黑树 Node 节点,则直接添加到树中
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // <3.3> 如果找到的 p 是 Node 节点,则说明是链表,需要遍历查找
        else {
            // 顺序遍历链表
            for (int binCount = 0; ; ++binCount) {
                // `(e = p.next)`:e 指向下一个节点,因为上面我们已经判断了最开始的 p 节点。
                // 如果已经遍历到链表的尾巴,则说明 key 在 HashMap 中不存在,则需要创建
                if ((e = p.next) == null) {
                    // 创建新的 Node 节点
                    p.next = newNode(hash, key, value, null);
                    // 链表的长度如果数量达到 TREEIFY_THRESHOLD(8)时,则进行树化。
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break; // 结束
                }
                // 如果遍历的 e 节点,就是要找的,则则直接使用即可
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break; // 结束
                // p 指向下一个节点
                p = e;
            }
        }
        // <4.1> 如果找到了对应的节点
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            // 修改节点的 value ,如果允许修改
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // 节点被访问的回调
            afterNodeAccess(e);
            // 返回老的值
            return oldValue;
        }
    }
    // <4.2>
    // 增加修改次数
    ++modCount;
    // 如果超过阀值,则进行扩容
    if (++size > threshold)
        resize();
    // 添加节点后的回调
    afterNodeInsertion(evict);
    // 返回 null
    return null;
}

<1> 处,如果 table 未初始化,或者容量为 0 ,则调用 #resize() 方法,进行扩容。
<2> 处,如果对应位置的 Node 节点为空,则直接创建 Node 节点即可。
i = (n - 1) & hash 代码段,计算 table 所在对应位置的下标。
调用 #newNode(int hash, K key, V value, Node<K,V> next) 方法,创建 Node 节点即可。代码如下:

Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}

这样,一个新的链表就出现了。当然,此处的 next 肯定是 null 。
<3> 处,如果对应位置的 Node 节点非空,则可能存在哈希冲突。需要分成 Node 节点是链表<3.3>,还是红黑树<3.2>的情况。
3.1处,如果找到的 p 节点,就是要找的,则则直接使用即可。这是一个优化操作,无论 Node 节点是链表还是红黑树。
3.2 处,如果找到的 p 节点,是红黑树 Node 节点,则调用 TreeNode#putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, int h, K k, V v) 方法,直接添加到树中。这块,咱就先不深入了。
3.3处,如果找到的 p 是 Node 节点,则说明是链表,需要遍历查找。比较简单,胖友自己看下代码注释即可。其中,binCount >= TREEIFY_THRESHOLD - 1 代码段,在链表的长度超过 TREEIFY_THRESHOLD = 8 的时候,会调用 #treeifyBin(Node<K,V>[] tab, int hash) 方法,将链表进行树化。当然,树化还有一个条件,具体在 「TODO. 树化」 中详细来看。
<4> 处,根据是否在 HashMap 中已经存在 key 对应的节点,有不同的处理。
<4.1> 处,如果存在的情况,会有如下处理:
1)如果满足需要修改节点,则进行修改。
2)如果节点被访问时,调用 #afterNodeAccess((Node<K,V> p) 方法,节点被访问的回调。目前这是个一个空方法,用于 HashMap 的子类 LinkedHashMap 需要做的拓展逻辑。
3)返回老的值。
<4.2> 处,如果不存在的情况,会有如下处理:
1)增加修改次数。
2)增加 key-value 键值对 size 数。并且 size 如果超过阀值,则调用 #resize() 方法,进行扩容。
3)调用 #afterNodeInsertion(boolean evict) 方法,添加节点后的回调。目前这是个一个空方法,用于 HashMap 的子类 LinkedHashMap 需要做的拓展逻辑。
4)返回 null ,因为老值不存在。

#putIfAbsent(K key, V value)方法,

当 key 不存在的时候,添加 key-value 键值对到其中。代码如下:

@Override
public V putIfAbsent(K key, V value) {
    return putVal(hash(key), key, value, true, true);
}

扩容

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    // <1> 开始:
    // <1.1> oldCap 大于 0 ,说明 table 非空
    if (oldCap > 0) {
        // <1.1.1> 超过最大容量,则直接设置 threshold 阀值为 Integer.MAX_VALUE ,不再允许扩容
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // <1.1.2> newCap = oldCap << 1 ,目的是两倍扩容
        // 如果 oldCap >= DEFAULT_INITIAL_CAPACITY 满足,说明当前容量大于默认值(16),则 2 倍阀值。
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    // <1.2.1>【非默认构造方法】oldThr 大于 0 ,则使用 oldThr 作为新的容量
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    // <1.2.2>【默认构造方法】oldThr 等于 0 ,则使用 DEFAULT_INITIAL_CAPACITY 作为新的容量,使用 DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY 作为新的容量
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 1.3 如果上述的逻辑,未计算新的阀值,则使用 newCap * loadFactor 作为新的阀值
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // <2> 开始:
    // 将 newThr 赋值给 threshold 属性
    threshold = newThr;
    // 创建新的 Node 数组,赋值给 table 属性
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 如果老的 table 数组非空,则需要进行一波搬运
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            // 获得老的 table 数组第 j 位置的 Node 节点 e
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                // 置空老的 table 数组第 j 位置
                oldTab[j] = null;
                // <2.1> 如果 e 节点只有一个元素,直接赋值给新的 table 即可
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                // <2.2> 如果 e 节点是红黑树节点,则通过红黑树分裂处理
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                // <2.3> 如果 e 节点是链表
                else { // preserve order
                    // HashMap 是成倍扩容,这样原来位置的链表的节点们,会被分散到新的 table 的两个位置中去
                    // 通过 e.hash & oldCap 计算,根据结果分到高位、和低位的位置中。
                    // 1. 如果结果为 0 时,则放置到低位
                    // 2. 如果结果非 1 时,则放置到高位
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    // 这里 do while 的原因是,e 已经非空,所以减少一次判断。细节~
                    do {
                        // next 指向下一个节点
                        next = e.next;
                        // 满足低位
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        // 满足高位
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    // 设置低位到新的 newTab 的 j 位置上
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 设置高位到新的 newTab 的 j + oldCap 位置上
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

树化

/**
 * 每个位置链表树化成红黑树,需要的链表最小长度
 *
 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2 and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 */
static final int TREEIFY_THRESHOLD = 8;

/**
 * HashMap 允许树化最小 key-value 键值对数
 *
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 */
static final int MIN_TREEIFY_CAPACITY = 64;

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    // <1> 如果 table 容量小于 MIN_TREEIFY_CAPACITY(64) ,则选择扩容
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    // <2> 将 hash 对应位置进行树化
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        // 顺序遍历链表,逐个转换成 TreeNode 节点
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        // 树化
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

首先,参考 泊松概率函数(Poisson distribution) ,当链表长度到达 8 的概率是 0.00000006 ,不到千万分之一。所以绝大多数情况下,在 hash 算法正常的时,不太会出现链表转红黑树的情况。
其次,TreeNode 相比普通的 Node 来说,会有两倍的空间占用。并且在长度比较小的情况下,红黑树的查找性能和链表是差别不大的。例如说,红黑树的 O(logN) = log8 = 3 和链表的 O(N) = 8 只相差 5 。
毕竟 HashMap 是 JDK 提供的基础数据结构,必须在空间和时间做抉择。所以,选择链表是空间复杂度优先,选择红黑树是时间复杂度优化。在绝大多数情况下,不会出现需要红黑树的情况。
<1> 处,如果 table 容量小于 MIN_TREEIFY_CAPACITY = 64 时,则调用 #resize() 方法,进行扩容。一般情况下,该链表可以分裂到两个位置上。极端情况下,解决不了,这时候一般是 hash 算法有问题。
<2> 处,如果 table 容量大于等于 MIN_TREEIFY_CAPACITY = 64 时,则将 hash 对应位置进行树化。一共有两步,因为和红黑树相关,这里就不拓展开了。
有树化,必然有取消树化。当 HashMap 因为移除 key 时,导致对应 table 位置的红黑树的内部节点数小于等于 UNTREEIFY_THRESHOLD = 6 时,则将红黑树退化成链表

remove(Object key). 移除单个元素

// HashMap.java

public V remove(Object key) {
    Node<K,V> e;
    // hash(key) 求哈希值
    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; // table 数组
    Node<K,V> p; // hash 对应 table 位置的 p 节点
    int n, index;
    // <1> 查找 hash 对应 table 位置的 p 节点
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, // 如果找到 key 对应的节点,则赋值给 node
                e;
        K k; V v;
        // <1.1> 如果找到的 p 节点,就是要找的,则则直接使用即可
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            // <1.2> 如果找到的 p 节点,是红黑树 Node 节点,则直接在红黑树中查找
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            // <1.3> 如果找到的 p 是 Node 节点,则说明是链表,需要遍历查找
            else {
                do {
                    // 如果遍历的 e 节点,就是要找的,则则直接使用即可
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break; // 结束
                    }
                    p = e; // 注意,这里 p 会保存找到节点的前一个节点
                } while ((e = e.next) != null);
            }
        }
        // <2> 如果找到 node 节点,则进行移除
        // 如果有要求匹配 value 的条件,这里会进行一次判断先移除
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            // <2.1> 如果找到的 node 节点,是红黑树 Node 节点,则直接在红黑树中删除
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            // <2.2.1> 如果查找到的是链表的头节点,则直接将 table 对应位置指向 node 的下一个节点,实现删除
            else if (node == p)
                tab[index] = node.next;
            // <2.2.2> 如果查找到的是链表的中间节点,则将 p 指向 node 的下一个节点,实现删除
            else
                p.next = node.next;
            // 增加修改次数
            ++modCount;
            // 减少 HashMap 数量
            --size;
            // 移除 Node 后的回调
            afterNodeRemoval(node);
            // 返回 node
            return node;
        }
    }
    // 查找不到,则返回 null
    return null;
}

get(Object key)

public V get(Object key) {
    Node<K,V> e;
    // hash(key) 哈希值
    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;
    // 查找 hash 对应 table 位置的 p 节点
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // 如果找到的 first 节点,就是要找的,则则直接使用即可
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            // 如果找到的 first 节点,是红黑树 Node 节点,则直接在红黑树中查找
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            // 如果找到的 e 是 Node 节点,则说明是链表,需要遍历查找
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值