深入浅出分析 HashMap

作者:炸鸡可乐
原文出处:www.pzblog.cn

一、摘要

在集合系列的第一章,咱们了解到,Map的实现类有HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。

深入浅出分析 HashMap

关于HashMap,一直都是一个非常热门的话题,只要你出去面试,我保证一定少不了它!

本文主要结合JDK1.7和JDK1.8的区别,就HashMap的数据结构和实现功能,进行深入探讨,废话也不多说了,直奔主题!

二、简介

在程序编程的时候,HashMap是一个使用非常频繁的容器类,它允许键值都放入null元素。除该类方法未实现同步外,其余跟Hashtable大致相同,但跟TreeMap不同,该容器不保证元素顺序,根据需要该容器可能会对元素重新哈希,元素的顺序也会被重新打散,因此不同时间迭代同一个HashMap的顺序可能会不同。

HashMap容器,实质还是一个哈希数组结构,但是在元素插入的时候,存在发生hash冲突的可能性;

对于发生Hash冲突的情况,冲突有两种实现方式, 一种开放地址方式(当发生hash冲突时,就继续以此继续寻找,直到找到没有冲突的hash值),另一种是拉链方式(将冲突的元素放入链表)Java HashMap采用的就是第二种方式,拉链法。

在jdk1.7中,HashMap主要是由数组+链表组成,当发生hash冲突的时候,就将冲突的元素放入链表中。

从jdk1.8开始,HashMap主要是由数组+链表+红黑树实现的,相比jdk1.7而言,多了一个红黑树实现。当链表长度超过8的时候,就将链表变成红黑树,如图所示。

深入浅出分析 HashMap

关于红黑树的实现,因为篇幅太长,在《集合系列》文章中红黑树设计,也有所介绍,这里就不在详细介绍了。

三、源码解析

直接打开HashMap的源码分析,可以看到,主要有5个关键参数:

  • threshold:表示容器所能容纳的key-value对极限。
  • loadFactor:负载因子。
  • modCount:记录修改次数。
  • size:表示实际存在的键值对数量。
  • *table:一个哈希桶数组,键值对就存放在里面。
public class HashMap<k,v> extends AbstractMap<k,v>
    implements Map<k,v>, Cloneable, Serializable {

    //&#x6240;&#x80FD;&#x5BB9;&#x7EB3;&#x7684;key-value&#x5BF9;&#x6781;&#x9650;
    int threshold;

    //&#x8D1F;&#x8F7D;&#x56E0;&#x5B50;
    final float loadFactor;

    //&#x8BB0;&#x5F55;&#x4FEE;&#x6539;&#x6B21;&#x6570;
    int modCount;

    //&#x5B9E;&#x9645;&#x5B58;&#x5728;&#x7684;&#x952E;&#x503C;&#x5BF9;&#x6570;&#x91CF;
    int size;

    //&#x54C8;&#x5E0C;&#x6876;&#x6570;&#x7EC4;
    transient Node<k,v>[] table;
}
</k,v></k,v></k,v></k,v>

接着来看看 Node这个类, NodeHashMap的一个内部类,实现了 Map.Entry接口,本质是就是一个映射(键值对)

static class Node<k,v> implements Map.Entry<k,v> {
        final int hash;//hash&#x503C;
        final K key;//k&#x952E;
        V value;//value&#x503C;
        Node<k,v> next;//&#x94FE;&#x8868;&#x4E2D;&#x4E0B;&#x4E00;&#x4E2A;&#x5143;&#x7D20;
}
</k,v></k,v></k,v>

在HashMap的数据结构中,有两个参数可以影响HashMap的性能: 初始容量(inital capacity)负载因子(load factor)

初始容量(inital capacity)是指table的初始长度length(默认值是16);
负载因子(load factor)用指自动扩容的临界值(默认值是0.75);

thresholdHashMap所能容纳的最大数据量的 Node(键值对)个数,计算公式 threshold = capacity * Load factor。当entry的数量超过 capacity*load_factor时,容器将自动扩容并重新哈希,扩容后的 HashMap容量是之前容量的 两倍所以数组的长度总是2的n次方

初始容量负载因子也可以修改,具体实现方式,可以在对象初始化的时候,指定参数,比如:

Map map = new HashMap(int initialCapacity, float loadFactor);

但是,默认的负载因子0.75是对空间和时间效率的一个平衡选择,建议大家不要修改,除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。 同时,对于插入元素较多的场景,可以将初始容量设大,减少重新哈希的次数。

HashMap的内部功能实现有很多,本文主要从以下几点,进行逐步分析。

  • 通过K获取数组下标;
  • put方法的详细执行;
  • resize扩容过程;
  • get方法获取参数值;
  • *remove删除元素;

3.1、通过K获取数组下标

不管增加、删除还是查找键值对,定位到数组的位置都是很关键的第一步,打开hashMap的任意一个增加、删除、查找方法,从源码可以看出,通过 key获取数组下标,主要做了3步操作,其中 length指的是容器数组的大小。

深入浅出分析 HashMap

源码部分:

/**&#x83B7;&#x53D6;hash&#x503C;&#x65B9;&#x6CD5;*/
static final int hash(Object key) {
     int h;
     // h = key.hashCode() &#x4E3A;&#x7B2C;&#x4E00;&#x6B65; &#x53D6;hashCode&#x503C;&#xFF08;jdk1.7&#xFF09;
     // h ^ (h >>> 16)  &#x4E3A;&#x7B2C;&#x4E8C;&#x6B65; &#x9AD8;&#x4F4D;&#x53C2;&#x4E0E;&#x8FD0;&#x7B97;&#xFF08;jdk1.7&#xFF09;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//jdk1.8
}
/**&#x83B7;&#x53D6;&#x6570;&#x7EC4;&#x4E0B;&#x6807;&#x65B9;&#x6CD5;*/
static int indexFor(int h, int length) {
    //jdk1.7&#x7684;&#x6E90;&#x7801;&#xFF0C;jdk1.8&#x6CA1;&#x6709;&#x8FD9;&#x4E2A;&#x65B9;&#x6CD5;&#xFF0C;&#x4F46;&#x662F;&#x5B9E;&#x73B0;&#x539F;&#x7406;&#x4E00;&#x6837;&#x7684;
     return h & (length-1);  //&#x7B2C;&#x4E09;&#x6B65; &#x53D6;&#x6A21;&#x8FD0;&#x7B97;
}

3.2、put方法的详细执行

put(K key, V value)方法是将指定的key, value对添加到map里。该方法首先会对map做一次查找,看是否包含该K,如果已经包含则直接返回;如果没有找到,则将元素插入容器。具体插入过程如下:

深入浅出分析 HashMap

具体执行步骤

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

put方法源码部分

/**
 * put&#x65B9;&#x6CD5;
 */
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}

插入元素方法

/**
 * &#x63D2;&#x5165;&#x5143;&#x7D20;&#x65B9;&#x6CD5;
 */
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<k,v>[] tab; Node<k,v> p; int n, i;
        //1&#x3001;&#x5224;&#x65AD;&#x6570;&#x7EC4;table&#x662F;&#x5426;&#x4E3A;&#x7A7A;&#x6216;&#x4E3A;null
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //2&#x3001;&#x5224;&#x65AD;&#x6570;&#x7EC4;&#x4E0B;&#x6807;table[i]==null
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<k,v> e; K k;
            //3&#x3001;&#x5224;&#x65AD;table[i]&#x7684;&#x9996;&#x4E2A;&#x5143;&#x7D20;&#x662F;&#x5426;&#x548C;&#x4F20;&#x5165;&#x7684;key&#x4E00;&#x6837;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //4&#x3001;&#x5224;&#x65AD;table[i] &#x662F;&#x5426;&#x4E3A;treeNode
            else if (p instanceof TreeNode)
                e = ((TreeNode<k,v>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //5&#x3001;&#x904D;&#x5386;table[i]&#xFF0C;&#x5224;&#x65AD;&#x94FE;&#x8868;&#x957F;&#x5EA6;&#x662F;&#x5426;&#x5927;&#x4E8E;8
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //&#x957F;&#x5EA6;&#x5927;&#x4E8E;8&#xFF0C;&#x8F6C;&#x7EA2;&#x9ED1;&#x6811;&#x7ED3;&#x6784;
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //&#x4F20;&#x5165;&#x7684;K&#x5143;&#x7D20;&#x5DF2;&#x7ECF;&#x5B58;&#x5728;&#xFF0C;&#x76F4;&#x63A5;&#x8986;&#x76D6;value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //6&#x3001;&#x5224;&#x65AD;size&#x662F;&#x5426;&#x8D85;&#x51FA;&#x6700;&#x5927;&#x5BB9;&#x91CF;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
}
</k,v></k,v></k,v></k,v>

其中,与jdk1.7有区别的地方,第4步新增了红黑树插入方法,源码部分:

/**
   * &#x7EA2;&#x9ED1;&#x6811;&#x7684;&#x63D2;&#x5165;&#x64CD;&#x4F5C;
   */
final TreeNode<k,v> putTreeVal(HashMap<k,v> map, Node<k,v>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            TreeNode<k,v> root = (parent != null) ? root() : this;
            for (TreeNode<k,v> p = root;;) {
                //dir:&#x904D;&#x5386;&#x7684;&#x65B9;&#x5411;&#xFF0C; ph:p&#x8282;&#x70B9;&#x7684;hash&#x503C;
                int dir, ph; K pk;
                //&#x7EA2;&#x9ED1;&#x6811;&#x662F;&#x6839;&#x636E;hash&#x503C;&#x6765;&#x5224;&#x65AD;&#x5927;&#x5C0F;
                // -1:&#x5DE6;&#x5B69;&#x5B50;&#x65B9;&#x5411; 1:&#x53F3;&#x5B69;&#x5B50;&#x65B9;&#x5411;
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                //&#x5982;&#x679C;key&#x5B58;&#x5728;&#x7684;&#x8BDD;&#x5C31;&#x76F4;&#x63A5;&#x8FD4;&#x56DE;&#x5F53;&#x524D;&#x8282;&#x70B9;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                //&#x5982;&#x679C;&#x5F53;&#x524D;&#x63D2;&#x5165;&#x7684;&#x7C7B;&#x578B;&#x548C;&#x6B63;&#x5728;&#x6BD4;&#x8F83;&#x7684;&#x8282;&#x70B9;&#x7684;Key&#x662F;Comparable&#x7684;&#x8BDD;&#xFF0C;&#x5C31;&#x76F4;&#x63A5;&#x901A;&#x8FC7;&#x6B64;&#x63A5;&#x53E3;&#x6BD4;&#x8F83;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<k,v> q, ch;
                        searched = true;
                        //&#x5C1D;&#x8BD5;&#x5728;p&#x7684;&#x5DE6;&#x5B50;&#x6811;&#x6216;&#x8005;&#x53F3;&#x5B50;&#x6811;&#x4E2D;&#x627E;&#x5230;&#x4E86;&#x76EE;&#x6807;&#x5143;&#x7D20;
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }
                    //&#x83B7;&#x53D6;&#x904D;&#x5386;&#x7684;&#x65B9;&#x5411;
                    dir = tieBreakOrder(k, pk);
                }
                //&#x4E0A;&#x9762;&#x7684;&#x6240;&#x6709;if-else&#x5224;&#x65AD;&#x90FD;&#x662F;&#x5728;&#x5224;&#x65AD;&#x4E0B;&#x4E00;&#x6B21;&#x8FDB;&#x884C;&#x904D;&#x5386;&#x7684;&#x65B9;&#x5411;&#xFF0C;&#x5373;dir
                TreeNode<k,v> xp = p;
                //&#x5F53;&#x4E0B;&#x9762;&#x7684;if&#x5224;&#x65AD;&#x8FDB;&#x53BB;&#x4E4B;&#x540E;&#x5C31;&#x4EE3;&#x8868;&#x627E;&#x5230;&#x4E86;&#x76EE;&#x6807;&#x64CD;&#x4F5C;&#x5143;&#x7D20;,&#x5373;xp
                if ((p = (dir <= 0) ? p.left : p.right)="=" null) { node<k,v> xpn = xp.next;
                    //&#x63D2;&#x5165;&#x65B0;&#x7684;&#x5143;&#x7D20;
                    TreeNode<k,v> x = map.newTreeNode(h, k, v, xpn);
                    if (dir <= 0) xp.left="x;" else xp.right="x;" 因为treenode今后可能退化成链表,在这里需要维护链表的next属性 xp.next="x;" 完成节点插入操作 x.parent="x.prev" = xp; if (xpn !="null)" ((treenode<k,v>)xpn).prev = x;
                    //&#x63D2;&#x5165;&#x64CD;&#x4F5C;&#x5B8C;&#x6210;&#x4E4B;&#x540E;&#x5C31;&#x8981;&#x8FDB;&#x884C;&#x4E00;&#x5B9A;&#x7684;&#x8C03;&#x6574;&#x64CD;&#x4F5C;&#x4E86;
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
       }
}
</=></k,v></=></k,v></k,v></k,v></k,v></k,v></k,v></k,v>

3.3、resize扩容过程

在说jdk1.8的HashMap动态扩容之前,我们先来了解一下jdk1.7的HashMap扩容实现,因为jdk1.8代码实现比Java1.7复杂了不止一倍,主要是Java1.8引入了红黑树设计,但是实现思想大同小异!

3.3.1、jdk1.7的扩容实现

深入浅出分析 HashMap

源码部分

/**
  * JDK1.7&#x6269;&#x5BB9;&#x65B9;&#x6CD5;
  * &#x4F20;&#x5165;&#x65B0;&#x7684;&#x5BB9;&#x91CF;
  */
void resize(int newCapacity) {
    //&#x5F15;&#x7528;&#x6269;&#x5BB9;&#x524D;&#x7684;Entry&#x6570;&#x7EC4;
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    //&#x6269;&#x5BB9;&#x524D;&#x7684;&#x6570;&#x7EC4;&#x5927;&#x5C0F;&#x5982;&#x679C;&#x5DF2;&#x7ECF;&#x8FBE;&#x5230;&#x6700;&#x5927;(2^30)&#x4E86;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        //&#x4FEE;&#x6539;&#x9608;&#x503C;&#x4E3A;int&#x7684;&#x6700;&#x5927;&#x503C;(2^31-1)&#xFF0C;&#x8FD9;&#x6837;&#x4EE5;&#x540E;&#x5C31;&#x4E0D;&#x4F1A;&#x6269;&#x5BB9;&#x4E86;
        threshold = Integer.MAX_VALUE;
        return;
    }
    //&#x521D;&#x59CB;&#x5316;&#x4E00;&#x4E2A;&#x65B0;&#x7684;Entry&#x6570;&#x7EC4;
    Entry[] newTable = new Entry[newCapacity];
    //&#x5C06;&#x6570;&#x636E;&#x8F6C;&#x79FB;&#x5230;&#x65B0;&#x7684;Entry&#x6570;&#x7EC4;&#x91CC;&#xFF0C;&#x8FD9;&#x91CC;&#x5305;&#x542B;&#x6700;&#x91CD;&#x8981;&#x7684;&#x91CD;&#x65B0;&#x5B9A;&#x4F4D;
    transfer(newTable);
    //HashMap&#x7684;table&#x5C5E;&#x6027;&#x5F15;&#x7528;&#x65B0;&#x7684;Entry&#x6570;&#x7EC4;
    table = newTable;
    threshold = (int) (newCapacity * loadFactor);//&#x4FEE;&#x6539;&#x9608;&#x503C;
}

transfer复制数组方法,源码部分:

//&#x904D;&#x5386;&#x6BCF;&#x4E2A;&#x5143;&#x7D20;&#xFF0C;&#x6309;&#x65B0;&#x7684;&#x5BB9;&#x91CF;&#x8FDB;&#x884C;rehash&#xFF0C;&#x653E;&#x5230;&#x65B0;&#x7684;&#x6570;&#x7EC4;&#x4E0A;
void transfer(Entry[] newTable) {
    //src&#x5F15;&#x7528;&#x4E86;&#x65E7;&#x7684;Entry&#x6570;&#x7EC4;
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j < src.length; j++) {
        //&#x904D;&#x5386;&#x65E7;&#x7684;Entry&#x6570;&#x7EC4;
        Entry<k, v> e = src[j];
        //&#x53D6;&#x5F97;&#x65E7;Entry&#x6570;&#x7EC4;&#x7684;&#x6BCF;&#x4E2A;&#x5143;&#x7D20;
        if (e != null) {
            //&#x91CA;&#x653E;&#x65E7;Entry&#x6570;&#x7EC4;&#x7684;&#x5BF9;&#x8C61;&#x5F15;&#x7528;&#xFF08;for&#x5FAA;&#x73AF;&#x540E;&#xFF0C;&#x65E7;&#x7684;Entry&#x6570;&#x7EC4;&#x4E0D;&#x518D;&#x5F15;&#x7528;&#x4EFB;&#x4F55;&#x5BF9;&#x8C61;&#xFF09;
            src[j] = null;
            do {
                Entry<k, v> next = e.next;
                //&#x91CD;&#x65B0;&#x8BA1;&#x7B97;&#x6BCF;&#x4E2A;&#x5143;&#x7D20;&#x5728;&#x6570;&#x7EC4;&#x4E2D;&#x7684;&#x4F4D;&#x7F6E;
                //&#x5B9E;&#x73B0;&#x903B;&#x8F91;&#xFF0C;&#x4E5F;&#x662F;&#x4E0A;&#x6587;&#x90A3;&#x4E2A;&#x53D6;&#x6A21;&#x8FD0;&#x7B97;&#x65B9;&#x6CD5;
                int i = indexFor(e.hash, newCapacity);
                //&#x6807;&#x8BB0;&#x6570;&#x7EC4;
                e.next = newTable[i];
                //&#x5C06;&#x5143;&#x7D20;&#x653E;&#x5728;&#x6570;&#x7EC4;&#x4E0A;
                newTable[i] = e;
                //&#x8BBF;&#x95EE;&#x4E0B;&#x4E00;&#x4E2A;Entry&#x94FE;&#x4E0A;&#x7684;&#x5143;&#x7D20;&#xFF0C;&#x5FAA;&#x73AF;&#x904D;&#x5386;
                e = next;
            } while (e != null);
        }
    }
}
</k,></k,>

jdk1.7扩容总结:newTable[i]的引用赋给了e.next,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了hash冲突的话),这一点和Jdk1.8有区别。在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。

3.3.2、jdk1.8的扩容实现

深入浅出分析 HashMap

源码如下

final Node<k,v>[] resize() {
        //&#x5F15;&#x7528;&#x6269;&#x5BB9;&#x524D;&#x7684;node&#x6570;&#x7EC4;
        Node<k,v>[] oldTab = table;
        //&#x65E7;&#x7684;&#x5BB9;&#x91CF;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //&#x65E7;&#x7684;&#x9608;&#x503C;
        int oldThr = threshold;
        //&#x65B0;&#x7684;&#x5BB9;&#x91CF;&#x3001;&#x9608;&#x503C;&#x521D;&#x59CB;&#x5316;&#x4E3A;0
        int newCap, newThr = 0;
        if (oldCap > 0) {
            //&#x5982;&#x679C;&#x65E7;&#x5BB9;&#x91CF;&#x5DF2;&#x7ECF;&#x8D85;&#x8FC7;&#x6700;&#x5927;&#x5BB9;&#x91CF;&#xFF0C;&#x8BA9;&#x9608;&#x503C;&#x4E5F;&#x7B49;&#x4E8E;&#x6700;&#x5927;&#x5BB9;&#x91CF;&#xFF0C;&#x4EE5;&#x540E;&#x4E0D;&#x518D;&#x6269;&#x5BB9;
                threshold = Integer.MAX_VALUE;
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // &#x6CA1;&#x8D85;&#x8FC7;&#x6700;&#x5927;&#x503C;&#xFF0C;&#x5C31;&#x6269;&#x5145;&#x4E3A;&#x539F;&#x6765;&#x7684;2&#x500D;
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //&#x5982;&#x679C;&#x65E7;&#x5BB9;&#x91CF;&#x7FFB;&#x500D;&#x6CA1;&#x6709;&#x8D85;&#x8FC7;&#x6700;&#x5927;&#x503C;&#xFF0C;&#x4E14;&#x65E7;&#x5BB9;&#x91CF;&#x4E0D;&#x5C0F;&#x4E8E;&#x521D;&#x59CB;&#x5316;&#x5BB9;&#x91CF;16&#xFF0C;&#x5219;&#x7FFB;&#x500D;
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            //&#x521D;&#x59CB;&#x5316;&#x5BB9;&#x91CF;&#x8BBE;&#x7F6E;&#x4E3A;&#x9608;&#x503C;
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            //0&#x7684;&#x65F6;&#x5019;&#x4F7F;&#x7528;&#x9ED8;&#x8BA4;&#x503C;&#x521D;&#x59CB;&#x5316;
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //&#x8BA1;&#x7B97;&#x65B0;&#x9608;&#x503C;&#xFF0C;&#x5982;&#x679C;&#x65B0;&#x5BB9;&#x91CF;&#x6216;&#x65B0;&#x9608;&#x503C;&#x5927;&#x4E8E;&#x7B49;&#x4E8E;&#x6700;&#x5927;&#x5BB9;&#x91CF;&#xFF0C;&#x5219;&#x76F4;&#x63A5;&#x4F7F;&#x7528;&#x6700;&#x5927;&#x503C;&#x4F5C;&#x4E3A;&#x9608;&#x503C;&#xFF0C;&#x4E0D;&#x518D;&#x6269;&#x5BB9;
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //&#x8BBE;&#x7F6E;&#x65B0;&#x9608;&#x503C;
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<k,v>[] newTab = (Node<k,v>[])new Node[newCap];
        //&#x521B;&#x5EFA;&#x65B0;&#x7684;&#x6570;&#x7EC4;&#xFF0C;&#x5E76;&#x5F15;&#x7528;
        table = newTab;
        //&#x5982;&#x679C;&#x8001;&#x7684;&#x6570;&#x7EC4;&#x6709;&#x6570;&#x636E;&#xFF0C;&#x4E5F;&#x5C31;&#x662F;&#x662F;&#x6269;&#x5BB9;&#x800C;&#x4E0D;&#x662F;&#x521D;&#x59CB;&#x5316;&#xFF0C;&#x624D;&#x6267;&#x884C;&#x4E0B;&#x9762;&#x7684;&#x4EE3;&#x7801;&#xFF0C;&#x5426;&#x5219;&#x521D;&#x59CB;&#x5316;&#x7684;&#x5230;&#x8FD9;&#x91CC;&#x5C31;&#x53EF;&#x4EE5;&#x7ED3;&#x675F;&#x4E86;
        if (oldTab != null) {
            //&#x8F6E;&#x8BE2;&#x8001;&#x6570;&#x7EC4;&#x6240;&#x6709;&#x6570;&#x636E;
            for (int j = 0; j < oldCap; ++j) {
                //&#x4EE5;&#x4E00;&#x4E2A;&#x65B0;&#x7684;&#x8282;&#x70B9;&#x5F15;&#x7528;&#x5F53;&#x524D;&#x8282;&#x70B9;&#xFF0C;&#x7136;&#x540E;&#x91CA;&#x653E;&#x539F;&#x6765;&#x7684;&#x8282;&#x70B9;&#x7684;&#x5F15;&#x7528;
                Node<k,v> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //&#x5982;&#x679C;e&#x6CA1;&#x6709;next&#x8282;&#x70B9;&#xFF0C;&#x8BC1;&#x660E;&#x8FD9;&#x4E2A;&#x8282;&#x70B9;&#x4E0A;&#x6CA1;&#x6709;hash&#x51B2;&#x7A81;&#xFF0C;&#x5219;&#x76F4;&#x63A5;&#x628A;e&#x7684;&#x5F15;&#x7528;&#x7ED9;&#x5230;&#x65B0;&#x7684;&#x6570;&#x7EC4;&#x4F4D;&#x7F6E;&#x4E0A;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        //&#xFF01;&#xFF01;&#xFF01;&#x5982;&#x679C;&#x662F;&#x7EA2;&#x9ED1;&#x6811;&#xFF0C;&#x5219;&#x8FDB;&#x884C;&#x5206;&#x88C2;
                        ((TreeNode<k,v>)e).split(this, newTab, j, oldCap);
                    else {
                        // &#x94FE;&#x8868;&#x4F18;&#x5316;&#x91CD;hash&#x7684;&#x4EE3;&#x7801;&#x5757;
                        Node<k,v> loHead = null, loTail = null;
                        Node<k,v> hiHead = null, hiTail = null;
                        Node<k,v> next;
                        //&#x4ECE;&#x8FD9;&#x6761;&#x94FE;&#x8868;&#x4E0A;&#x7B2C;&#x4E00;&#x4E2A;&#x5143;&#x7D20;&#x5F00;&#x59CB;&#x8F6E;&#x8BE2;&#xFF0C;&#x5982;&#x679C;&#x5F53;&#x524D;&#x5143;&#x7D20;&#x65B0;&#x589E;&#x7684;bit&#x662F;0&#xFF0C;&#x5219;&#x653E;&#x5728;&#x5F53;&#x524D;&#x8FD9;&#x6761;&#x94FE;&#x8868;&#x4E0A;&#xFF0C;&#x5982;&#x679C;&#x662F;1&#xFF0C;&#x5219;&#x653E;&#x5728;"j+oldcap"&#x8FD9;&#x4E2A;&#x4F4D;&#x7F6E;&#x4E0A;&#xFF0C;&#x751F;&#x6210;&#x201C;&#x4F4E;&#x4F4D;&#x201D;&#x548C;&#x201C;&#x9AD8;&#x4F4D;&#x201D;&#x4E24;&#x4E2A;&#x94FE;&#x8868;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    //&#x5143;&#x7D20;&#x662F;&#x4E0D;&#x65AD;&#x7684;&#x52A0;&#x5230;&#x5C3E;&#x90E8;&#x7684;&#xFF0C;&#x4E0D;&#x4F1A;&#x50CF;1.7&#x91CC;&#x9762;&#x4E00;&#x6837;&#x4F1A;&#x5012;&#x5E8F;
                                    loTail.next = e;
                                //&#x65B0;&#x589E;&#x7684;&#x5143;&#x7D20;&#x6C38;&#x8FDC;&#x662F;&#x5C3E;&#x5143;&#x7D20;
                                loTail = e;
                            }
                            else {
                                //&#x9AD8;&#x4F4D;&#x7684;&#x94FE;&#x8868;&#x4E0E;&#x4F4E;&#x4F4D;&#x7684;&#x94FE;&#x8868;&#x5904;&#x7406;&#x903B;&#x8F91;&#x4E00;&#x6837;&#xFF0C;&#x4E0D;&#x65AD;&#x7684;&#x628A;&#x5143;&#x7D20;&#x52A0;&#x5230;&#x94FE;&#x8868;&#x5C3E;&#x90E8;
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //&#x4F4E;&#x4F4D;&#x94FE;&#x8868;&#x653E;&#x5230;j&#x8FD9;&#x4E2A;&#x7D22;&#x5F15;&#x7684;&#x4F4D;&#x7F6E;&#x4E0A;
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //&#x9AD8;&#x4F4D;&#x94FE;&#x8868;&#x653E;&#x5230;(j+oldCap)&#x8FD9;&#x4E2A;&#x7D22;&#x5F15;&#x7684;&#x4F4D;&#x7F6E;&#x4E0A;
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
}
</k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v>

1.7与1.8处理逻辑大同小异,区别主要还是在树节点的分裂 ((TreeNode<k,v>)e).split() </k,v>这个方法上

/**
 * &#x7EA2;&#x9ED1;&#x6811;&#x5206;&#x88C2;&#x65B9;&#x6CD5;
 */
final void split(HashMap<k,v> map, Node<k,v>[] tab, int index, int bit) {
            //&#x5F53;&#x524D;&#x8FD9;&#x4E2A;&#x8282;&#x70B9;&#x7684;&#x5F15;&#x7528;&#xFF0C;&#x5373;&#x8FD9;&#x4E2A;&#x7D22;&#x5F15;&#x4E0A;&#x7684;&#x6811;&#x7684;&#x6839;&#x8282;&#x70B9;
            TreeNode<k,v> b = this;
            // Relink into lo and hi lists, preserving order
            TreeNode<k,v> loHead = null, loTail = null;
            TreeNode<k,v> hiHead = null, hiTail = null;
            //&#x9AD8;&#x4F4D;&#x4F4E;&#x4F4D;&#x7684;&#x521D;&#x59CB;&#x6811;&#x8282;&#x70B9;&#x4E2A;&#x6570;&#x90FD;&#x8BBE;&#x6210;0
            int lc = 0, hc = 0;
            for (TreeNode<k,v> e = b, next; e != null; e = next) {
                next = (TreeNode<k,v>)e.next;
                e.next = null;
                //bit=oldcap,&#x8FD9;&#x91CC;&#x5224;&#x65AD;&#x65B0;bit&#x4F4D;&#x662F;0&#x8FD8;&#x662F;1&#xFF0C;&#x5982;&#x679C;&#x662F;0&#x5C31;&#x653E;&#x5728;&#x4F4E;&#x4F4D;&#x6811;&#x4E0A;&#xFF0C;&#x5982;&#x679C;&#x662F;1&#x5C31;&#x653E;&#x5728;&#x9AD8;&#x4F4D;&#x6811;&#x4E0A;&#xFF0C;&#x8FD9;&#x91CC;&#x5148;&#x662F;&#x4E00;&#x4E2A;&#x53CC;&#x5411;&#x94FE;&#x8868;
                if ((e.hash & bit) == 0) {
                    if ((e.prev = loTail) == null)
                        loHead = e;
                    else
                        loTail.next = e;
                    loTail = e;
                    ++lc;
                }
                else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }

            if (loHead != null) {
                if (lc <= untreeify_threshold) !!!如果低位的链表长度小于阈值6,则把树变成链表,并放到新数组中j索引位置 tab[index]="loHead.untreeify(map);" else { 高位不为空,进行红黑树转换 if (hihead !="null)" (else is already treeified) lohead.treeify(tab); } (hc <="UNTREEIFY_THRESHOLD)" tab[index + bit]="hiHead.untreeify(map);" (lohead hihead.treeify(tab); code></=></k,v></k,v></k,v></k,v></k,v></k,v></k,v>

untreeify方法,将树转变为单向链表

/**
 * &#x5C06;&#x6811;&#x8F6C;&#x53D8;&#x4E3A;&#x5355;&#x5411;&#x94FE;&#x8868;
 */
final Node<k,v> untreeify(HashMap<k,v> map) {
            Node<k,v> hd = null, tl = null;
            for (Node<k,v> q = this; q != null; q = q.next) {
                Node<k,v> p = map.replacementNode(q, null);
                if (tl == null)
                    hd = p;
                else
                    tl.next = p;
                tl = p;
            }
            return hd;
}
</k,v></k,v></k,v></k,v></k,v>

treeify方法,将链表转换为红黑树,会根据红黑树特性进行颜色转换、左旋、右旋等

/**
 * &#x94FE;&#x8868;&#x8F6C;&#x6362;&#x4E3A;&#x7EA2;&#x9ED1;&#x6811;&#xFF0C;&#x4F1A;&#x6839;&#x636E;&#x7EA2;&#x9ED1;&#x6811;&#x7279;&#x6027;&#x8FDB;&#x884C;&#x989C;&#x8272;&#x8F6C;&#x6362;&#x3001;&#x5DE6;&#x65CB;&#x3001;&#x53F3;&#x65CB;&#x7B49;
 */
final void treeify(Node<k,v>[] tab) {
            TreeNode<k,v> root = null;
            for (TreeNode<k,v> x = this, next; x != null; x = next) {
                next = (TreeNode<k,v>)x.next;
                x.left = x.right = null;
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    for (TreeNode<k,v> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<k,v> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right)="=" null) { x.parent="xp;" if (dir <="0)" xp.left="x;" else xp.right="x;" 进行左旋、右旋调整 root="balanceInsertion(root," x); break; } moveroottofront(tab, root); code></=></k,v></k,v></k,v></k,v></k,v></k,v>

jdk1.8在进行重新扩容之后,会重新计算hash值,因为n变为2倍,假设初始 tableSize = 4 要扩容到 8 来说就是 0100 到 1000 的变化(左移一位就是 2 倍),在扩容中只用判断原来的 hash 值与左移动的一位(newtable 的值)按位与操作是 0 或 1 就行,0 的话索引就不变,1 的话索引变成原索引 + oldCap;

其实现如下流程图所示:

深入浅出分析 HashMap

可以看见,因为 hash 值本来就是随机性的,所以 hash 按位与上 newTable 得到的 0(扩容前的索引位置)和 1(扩容前索引位置加上扩容前数组长度的数值索引处)就是随机的,所以扩容的过程就能把之前哈希冲突的元素再随机的分布到不同的索引去,这算是 JDK1.8 的一个优化点。

此外,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是从上图可以看出,JDK1.8不会倒置。

同时,由于 JDK1.7 中发生哈希冲突时仅仅采用了链表结构存储冲突元素,所以扩容时仅仅是重新计算其存储位置而已。而 JDK1.8 中为了性能在同一索引处发生哈希冲突到一定程度时,链表结构会转换为红黑数结构存储冲突元素,故在扩容时如果当前索引中元素结构是红黑树且元素个数小于链表还原阈值时就会把树形结构缩小或直接还原为链表结构(其实现就是上面代码片段中的 split() 方法)。

3.4、get方法获取参数值

get(Object key)方法根据指定的key值返回对应的value, getNode(hash(key), key))得到相应的Node对象e,然后返回e.value。因此getNode()是算法的核心。

深入浅出分析 HashMap

get方法源码部分:

/**
  * JDK1.8 get&#x65B9;&#x6CD5;
  * &#x901A;&#x8FC7;key&#x83B7;&#x53D6;&#x53C2;&#x6570;&#x503C;
  */
public V get(Object key) {
        Node<k,v> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
}
</k,v>

通过hash值和key获取节点Node方法,源码部分:

final Node<k,v> getNode(int hash, Object key) {
        Node<k,v>[] tab; Node<k,v> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            //1&#x3001;&#x5224;&#x65AD;&#x7B2C;&#x4E00;&#x4E2A;&#x5143;&#x7D20;&#x662F;&#x5426;&#x4E0E;key&#x5339;&#x914D;
            if (first.hash == hash &&
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                //2&#x3001;&#x5224;&#x65AD;&#x94FE;&#x8868;&#x662F;&#x5426;&#x7EA2;&#x9ED1;&#x6811;&#x7ED3;&#x6784;
                if (first instanceof TreeNode)
                    return ((TreeNode<k,v>)first).getTreeNode(hash, key);
                //3&#x3001;&#x5982;&#x679C;&#x4E0D;&#x662F;&#x7EA2;&#x9ED1;&#x6811;&#x7ED3;&#x6784;&#xFF0C;&#x76F4;&#x63A5;&#x5FAA;&#x73AF;&#x5224;&#x65AD;
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
}
</k,v></k,v></k,v></k,v>

在红黑树中找到指定k的TreeNode,源码部分:

/**
  * &#x8FD9;&#x91CC;&#x9762;&#x60C5;&#x51B5;&#x5206;&#x5F88;&#x591A;&#x4E2D;&#xFF0C;&#x4E3B;&#x8981;&#x662F;&#x56E0;&#x4E3A;&#x8003;&#x8651;&#x4E86;hash&#x76F8;&#x540C;&#x4F46;&#x662F;key&#x503C;&#x4E0D;&#x540C;&#x7684;&#x60C5;&#x51B5;&#xFF0C;&#x67E5;&#x627E;&#x7684;&#x6700;&#x6838;&#x5FC3;&#x8FD8;&#x662F;&#x843D;&#x5728;key&#x503C;&#x4E0A;
  */
final TreeNode<k,v> find(int h, Object k, Class<?> kc) {
            TreeNode<k,v> p = this;
            do {
                int ph, dir; K pk;
                TreeNode<k,v> pl = p.left, pr = p.right, q;
                //&#x5224;&#x65AD;&#x8981;&#x67E5;&#x8BE2;&#x5143;&#x7D20;&#x7684;hash&#x662F;&#x5426;&#x5728;&#x6811;&#x7684;&#x5DE6;&#x8FB9;
                if ((ph = p.hash) > h)
                    p = pl;
                //&#x5224;&#x65AD;&#x8981;&#x67E5;&#x8BE2;&#x5143;&#x7D20;&#x7684;hash&#x662F;&#x5426;&#x5728;&#x6811;&#x7684;&#x53F3;&#x8FB9;
                else if (ph < h)
                    p = pr;
                //&#x67E5;&#x8BE2;&#x5143;&#x7D20;&#x7684;hash&#x4E0E;&#x5F53;&#x524D;&#x6811;&#x8282;&#x70B9;hash&#x76F8;&#x540C;&#x60C5;&#x51B5;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                //&#x4E0A;&#x9762;&#x7684;&#x4E09;&#x6B65;&#x90FD;&#x662F;&#x6B63;&#x5E38;&#x7684;&#x5728;&#x4E8C;&#x53C9;&#x67E5;&#x627E;&#x6811;&#x4E2D;&#x5BFB;&#x627E;&#x5BF9;&#x8C61;&#x7684;&#x65B9;&#x6CD5;
                //&#x5982;&#x679C;hash&#x76F8;&#x7B49;&#xFF0C;&#x4F46;&#x662F;&#x5185;&#x5BB9;&#x5374;&#x4E0D;&#x76F8;&#x7B49;
                else if (pl == null)
                    p = pr;
                else if (pr == null)
                    p = pl;
                 //&#x5982;&#x679C;&#x53EF;&#x4EE5;&#x6839;&#x636E;compareTo&#x8FDB;&#x884C;&#x6BD4;&#x8F83;&#x7684;&#x8BDD;&#x5C31;&#x6839;&#x636E;compareTo&#x8FDB;&#x884C;&#x6BD4;&#x8F83;
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                //&#x6839;&#x636E;compareTo&#x7684;&#x7ED3;&#x679C;&#x5728;&#x53F3;&#x5B69;&#x5B50;&#x4E0A;&#x7EE7;&#x7EED;&#x67E5;&#x8BE2;
                else if ((q = pr.find(h, k, kc)) != null)
                    return q;
                //&#x6839;&#x636E;compareTo&#x7684;&#x7ED3;&#x679C;&#x5728;&#x5DE6;&#x5B69;&#x5B50;&#x4E0A;&#x7EE7;&#x7EED;&#x67E5;&#x8BE2;
                else
                    p = pl;
            } while (p != null);
            return null;
}
</k,v></k,v></k,v>

get方法,首先通过hash()函数得到对应数组下标,然后依次判断。

  • 1、判断第一个元素与key是否匹配,如果匹配就返回参数值;
  • 2、判断链表是否红黑树,如果是红黑树,就进入红黑树方法获取参数值;
  • 3、如果不是红黑树结构,直接循环判断,直到获取参数为止;

3.5、remove删除元素

remove(Object key)的作用是删除key值对应的Node,该方法的具体逻辑是在 removeNode(hash(key), key, null, false, true)里实现的。

深入浅出分析 HashMap

remove方法,源码部分:

/**
  * JDK1.8 remove&#x65B9;&#x6CD5;
  * &#x901A;&#x8FC7;key&#x79FB;&#x9664;&#x5BF9;&#x8C61;
  */
public V remove(Object key) {
        Node<k,v> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
}
</k,v>

通过key移除Node节点方法,源码部分:

/**
  * &#x901A;&#x8FC7;key&#x79FB;&#x9664;Node&#x8282;&#x70B9;
  */
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;
        //1&#x3001;&#x5224;&#x65AD;&#x8981;&#x5220;&#x9664;&#x7684;&#x5143;&#x7D20;&#xFF0C;&#x662F;&#x5426;&#x5B58;&#x5728;
        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;
            //2&#x3001;&#x5224;&#x65AD;&#x7B2C;&#x4E00;&#x4E2A;&#x5143;&#x7D20;&#x662F;&#x4E0D;&#x662F;&#x6211;&#x4EEC;&#x8981;&#x627E;&#x7684;&#x5143;&#x7D20;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                //3&#x3001;&#x5224;&#x65AD;&#x5F53;&#x524D;&#x51B2;&#x7A81;&#x94FE;&#x8868;&#x662F;&#x5426;&#x7EA2;&#x9ED1;&#x6811;&#x7ED3;&#x6784;
                if (p instanceof TreeNode)
                    node = ((TreeNode<k,v>)p).getTreeNode(hash, key);
                else {
                    //4&#x3001;&#x5FAA;&#x73AF;&#x5728;&#x94FE;&#x8868;&#x4E2D;&#x627E;&#x5230;&#x9700;&#x8981;&#x5220;&#x9664;&#x7684;&#x5143;&#x7D20;
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //&#x4E0A;&#x9762;&#x7684;&#x903B;&#x8F91;&#xFF0C;&#x57FA;&#x672C;&#x90FD;&#x662F;&#x4E3A;&#x4E86;&#x627E;&#x5230;&#x8981;&#x5220;&#x9664;&#x5143;&#x7D20;&#x7684;&#x8282;&#x70B9;
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                //5&#x3001;&#x5982;&#x679C;&#x5F53;&#x524D;&#x51B2;&#x7A81;&#x94FE;&#x8868;&#x7ED3;&#x6784;&#x662F;&#x7EA2;&#x9ED1;&#x6811;&#xFF0C;&#x6267;&#x884C;&#x7EA2;&#x9ED1;&#x6811;&#x5220;&#x9664;&#x65B9;&#x6CD5;
                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;
}
</k,v></k,v></k,v></k,v></k,v></k,v>

removeTreeNode移除红黑树节点方法,源码部分:

final void removeTreeNode(HashMap<k,v> map, Node<k,v>[] tab,
                                  boolean movable) {
            int n;
            if (tab == null || (n = tab.length) == 0)
                return;
            int index = (n - 1) & hash;
            TreeNode<k,v> first = (TreeNode<k,v>)tab[index], root = first, rl;
            TreeNode<k,v> succ = (TreeNode<k,v>)next, pred = prev;
            if (pred == null)
                tab[index] = first = succ;
            else
                pred.next = succ;
            if (succ != null)
                succ.prev = pred;
            if (first == null)
                return;
            if (root.parent != null)
                root = root.root();
            if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
                tab[index] = first.untreeify(map);  // too small
                return;
            }
            TreeNode<k,v> p = this, pl = left, pr = right, replacement;
            if (pl != null && pr != null) {
                TreeNode<k,v> s = pr, sl;
                while ((sl = s.left) != null) // find successor
                    s = sl;
                boolean c = s.red; s.red = p.red; p.red = c; // swap colors
                TreeNode<k,v> sr = s.right;
                TreeNode<k,v> pp = p.parent;
                if (s == pr) { // p was s's direct parent
                    p.parent = s;
                    s.right = p;
                }
                else {
                    TreeNode<k,v> sp = s.parent;
                    if ((p.parent = sp) != null) {
                        if (s == sp.left)
                            sp.left = p;
                        else
                            sp.right = p;
                    }
                    if ((s.right = pr) != null)
                        pr.parent = s;
                }
                p.left = null;
                if ((p.right = sr) != null)
                    sr.parent = p;
                if ((s.left = pl) != null)
                    pl.parent = s;
                if ((s.parent = pp) == null)
                    root = s;
                else if (p == pp.left)
                    pp.left = s;
                else
                    pp.right = s;
                if (sr != null)
                    replacement = sr;
                else
                    replacement = p;
            }
            else if (pl != null)
                replacement = pl;
            else if (pr != null)
                replacement = pr;
            else
                replacement = p;
            if (replacement != p) {
                TreeNode<k,v> pp = replacement.parent = p.parent;
                if (pp == null)
                    root = replacement;
                else if (p == pp.left)
                    pp.left = replacement;
                else
                    pp.right = replacement;
                p.left = p.right = p.parent = null;
            }
            //&#x5224;&#x65AD;&#x662F;&#x5426;&#x9700;&#x8981;&#x8FDB;&#x884C;&#x7EA2;&#x9ED1;&#x6811;&#x7ED3;&#x6784;&#x8C03;&#x6574;
            TreeNode<k,v> r = p.red ? root : balanceDeletion(root, replacement);

            if (replacement == p) {  // detach
                TreeNode<k,v> pp = p.parent;
                p.parent = null;
                if (pp != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                }
            }
            if (movable)
                moveRootToFront(tab, r);
}
</k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v></k,v>

jdk1.8的删除逻辑实现比较复杂,相比jdk1.7而言,多了红黑树节点删除和调整:

  • 1、默认判断链表第一个元素是否是要删除的元素;
  • 2、如果第一个不是,就继续判断当前冲突链表是否是红黑树,如果是,就进入红黑树里面去找;
  • 3、如果当前冲突链表不是红黑树,就直接在链表中循环判断,直到找到为止;
  • 4、将找到的节点,删除掉,如果是红黑树结构,会进行颜色转换、左旋、右旋调整,直到满足红黑树特性为止;

四、总结

1、如果key是一个对象,记得在对象实体类里面,要重写equals和hashCode方法,不然在查询的时候,无法通过对象key来获取参数值!
2、相比JDK1.7,JDK1.8引入红黑树设计,当链表长度大于8的时候,链表会转化为红黑树结构,发生冲突的链表如果很长,红黑树的实现很大程度优化了HashMap的性能,使查询效率比JDK1.7要快一倍!
3、对于大数组的情况,可以提前给Map初始化一个容量,避免在插入的时候,频繁的扩容,因为扩容本身就比较消耗性能!

五、参考

1、JDK1.7&JDK1.8 源码

2、美团技术团队 – Java 8系列之重新认识HashMap

3、简书 – JDK1.8红黑树实现分析-此鱼不得水

4、简书 – JJDK 1.8 中 HashMap 扩容

5、Java HashMap 基础面试常见问题

Original: https://www.cnblogs.com/dxflqm/p/11994380.html
Author: 程序员志哥
Title: 深入浅出分析 HashMap

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/612916/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Ceph创建一个新集群 报错: File “/usr/bin/ceph-deploy”, line 18, in……….

    [root@ceph-node1 ceph]# ceph-deploy new node1 Traceback (most recent call last): File &quo…

    数据库 2023年6月14日
    082
  • 2022-08-16 数据库查询语言之——-DQL

    重点,DQL是我们每天都要接触编写最多也是最难的SQL,该语言用来查询记录,不会修改数据库和表结构。 构建数据库 创建一张student表: DROP TABLE IF EXIST…

    数据库 2023年6月14日
    089
  • Java面试题(十)–Spring Cloud

    1 基础知识篇 1、什么是微服务架构? 微服务架构是一种架构模式或者说是架构风格,它提倡将单一应用程序划分成一组小的服务。每个服务运行在其独立的自己的进程中服务之间相互配合、相互协…

    数据库 2023年6月16日
    0104
  • Dubbo源码(九)-服务调用过程

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 源码分析均基于官方Demo,路径:dubbo/dubbo-demo 如果没有…

    数据库 2023年6月11日
    0113
  • MySQL事务与锁

    在关系型数据库内,事务是由一个SQL或一组SQL语句组成的逻辑处理单元。也就是说事务就相当于一个盛放SQL的容器,事务中的SQL要么全部执行成功,要么所有已经修改的操作都回滚到原来…

    数据库 2023年5月24日
    0104
  • 23种设计模式之命令模式和策略模式的区别

    命令模式和 策略模式确实很相似,只是命令模式多了一个接收者(Receiver)角色。它们虽然同为行为类模式,但是两者的区别还是很明显的。策略模式的意图是封装算法,它认为&#8221…

    数据库 2023年6月6日
    090
  • 正则表达式=Regex=regular expression

    正则表达式=Regex=regular expression 反向引用*2 \index索引引用 \b(\w+)\b\s+\1\b \k \b(? 数量符/限定符62 贪婪Gree…

    数据库 2023年6月15日
    067
  • 3000帧动画图解MySQL为什么需要binlog、redo log和undo log

    全文建立在MySQL的存储引擎为InnoDB的基础上 先看一条SQL如何入库的: 这是一条很简单的更新SQL,从MySQL服务端接收到SQL到落盘,先后经过了MySQL Serve…

    数据库 2023年6月16日
    0123
  • MySQL备份迁移之mydumper

    简介 mydumper 是一款开源的 MySQL 逻辑备份工具,主要由 C 语言编写。与 MySQL 自带的 mysqldump 类似,但是 mydumper 更快更高效。mydu…

    数据库 2023年5月24日
    0120
  • Markdown语法浅学

    typora语法使用 1.字体 *斜体*,_斜体_ **粗体** ***加粗斜体*** ~~删除线~~ 下划线 ***分割线 , — 2.标题 一级标题 ## 二级标题 ###…

    数据库 2023年6月11日
    096
  • MySQL 基础

    MySQL 基础 SQL 介绍 SQL (Structured Query Language:结构化查询语言) 是用于管理关系数据库管理系统(RDBMS)。 SQL 的范围包括数据…

    数据库 2023年5月24日
    091
  • Nginx基础入门篇(1)—优势及安装

    一、Nginx 的优势 1.1发展趋势: 2016年: 1.2、简介 Nginx (engine x) 是一个高性能的HTTP(解决C10k的问题)和反向代理服务器,也是一个IMA…

    数据库 2023年6月14日
    078
  • 关系型、非关系型数据库存储选型盘点大全

    工作中总是遇到数据存储相关的 Bug 工单,新需求开发设计中也多多少少会有数据模型设计和存储相关的问题。经过几次存储方案设计选型和讨论后发现需要有更全面的思考框架。 日常开发中常用…

    数据库 2023年6月14日
    0126
  • 数字签名和数字证书是什么

    定义 数字签名和数字证书的区别是什么?数字证书是由权威机构CA证书授权中心发行的,能提供在Internet上进行身份验证的一种权威性电子文档。而数字签名是一种类似写在纸上的普通的物…

    数据库 2023年6月11日
    090
  • JUC的数据库连接池小练习

    JUC练习数据库连接池实现 通过一个连接数组来充当连接池 一个原子的标记数组 通过cas来保持多线程下的安全,用synchronized来进行暂停和唤醒 @Slf4j public…

    数据库 2023年6月11日
    090
  • 限流常见方案

    限流常见方案 我歌月徘徊,我舞影零乱。醒时相交欢,醉后各分散。 一、限流思路 常见的系统服务限流模式有:熔断、服务降级、延迟处理和特殊处理四种。 1、熔断 将熔断措施嵌入到系统设计…

    数据库 2023年6月14日
    093
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球