最详细的HashMap底层源码分析!看完不再害怕面试官问你HashMap底层实现原理了!

想深入了解HashMap的源码,相信大家干的第一件事一定是看源码,甭管三七二十一,先进去看,看了半天发现越看越懵,最后放弃了。说的是你吗?

陶渊明读书,不求甚解;诸葛亮读书,观其大略; 想要了解底层实现原理,一定要先知道它的轮廓,它的结构。如果你去爬山,上来就去研究它的纹理,那你这辈子都研究不明白。

研究底层源码也是一样,首先你要先去了解它的数据结构,只有脉络清楚了,你看着底层的代码才不费劲。

常见的数据结构

  • 数组
  • 链表
  • 树形
  • 等等

数组结构优缺点

image-20200729172811272

优点:
  • 查询速度快,每个值都有下标,可以通过下标快速找到相应的位置
缺点:
  • 插入、删除效率低,数组长度是固定的,每次新增元素都需要新建一个数组,将原来的数据copy到新数组,再将新值放到新数组中;删除也是一样,每次删除一个元素都需要移动后面元素的位置;

所以,数组这种数据结构适合查询,不适合新增、删除;

链表结构优缺点

​ 下图为单向链表,每个节点中包含数据域,指针域(指向下一个节点)

单向链表

优点:
  • 插入、删除速度快,当新增一个节点时,只需要将原链表中指向下一节点的引用清空,并将上一节点指向新节点,新节点指向下一节点即可;删除也是一样,只需要修改引用;

缺点:
  • 查询速度慢,如果查找的值在最末的节点,那么需要通过指针引用从头结点开始遍历,直到最后一个节点;

所以,链表这种数据结构适合新增、删除,不适合查询;

红黑树优缺点

因为hashmap和红黑树有关,其他树形结构这篇文章就不提了。红黑树会在后面专门一篇文章讲。

红黑树特性

  1. 每个节点只能是红色或者黑色。

  2. 根节点必须是黑色。

  3. 红色的节点,它的叶节点只能是黑色。

  4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

优点:
  • 红黑树能够以O(log2 (n)) 的时间复杂度进行搜索、插入、删除操作。
  • 黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
  • 增加和删除节点 恢复平衡性能好。
缺点:
  • 算法复杂

HashMap的数据结构

​ 在JDK1.8之前,HashMap采用数组+链表的数据结构,JDK1.8开始,采用数组+链表+红黑树数据结构。

###HashMap 结构图

原码分析

    /**
     * The default initial capacity - MUST be a power of two.
     * 默认的初始容量 必须是2的n次幂,如果不指定容量大小,默认为16
     */
		static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

默认的初始容量 必须是2的n次幂,如果不指定容量大小,默认为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的最大容量为2的30次幂
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

HashMap的最大容量为2的30次幂


		/**
     * 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;

桶的树化阈值,当链表长度为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;

桶的链表还原阈值,如果发现链表长度小于 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;

最小树形化容量阈值:即 当哈希表中的容量 > 64,才允许树形化链表 (即 将链表 转换成红黑树),否则,若桶内元素太多时,则直接扩容,而不是树形化,为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD


以上就是HashMap中用到的静态变量,下面我们接着分析HashMap中的方法。

HashMap的构造函数

		/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    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);
    }

    /**
     * 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);
    }

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

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

主要分析第一个,第二个是套用第一个方法,第三个只是对变量赋值。

		/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    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);
    }

前面三个主要是判断初始化容量如果<0则抛异常,如果>定义的最大容量,则容量为定义的最大容量,负载因子小于等于0或者为非数的话抛异常。

看一下this.threshold = tableSizeFor(initialCapacity);这个方法。

		/**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        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;
    }

这个方法是根据传进来的容量生成符合规则(2的n次幂)的容量大小:

假如传进来的容量cap为7,那么n = 7 - 1 = 6;

6的二进制为110,n >>> 1–> 011

n |= n >>> 1; --> n = 110 | 011 = 111

n = 111,n >>> 2 = 001

n |= n >>> 2; --> n = 111 | 001 = 111

n = 111,n >>> 4 = 000

n |= n >>> 4; --> n = 111 | 000 = 111

n = 111,n >>> 8 = 000

n |= n >>> 8; --> n = 111 | 000 = 111

n = 111,n >>> 16 = 000

n |= n >>> 16; --> n = 111 | 000 = 111

111转换成10进制为n = 7;

最后判断n < 0 ? 不小于0,判断n >= 2的30次幂?不大于等于,返回n + 1 = 8;

那传进来的值是7,为什么要减1?

如果不减1会出现什么样的结果?推算一下:

假如传进来的容量cap为8, n = 8。

8的二进制为:1000,n >>> 1 = 100

n |= n >>> 1; --> n = 1000 | 0100 = 1100

n = 1100,n >>> 2 = 0011

n |= n >>> 2; --> n = 1100 | 0011 = 1111

n = 1111,n >>> 4 = 0000

n |= n >>> 4; --> n = 1111 | 0000 = 1111

n = 1111,n >>> 8 = 0000

n |= n >>> 8; --> n = 1111 | 0000 = 1111

n = 1111,n >>> 16 = 0000

n |= n >>> 16; --> n = 1111 | 0000 = 1111

最后返回 15 + 1 = 16,传进来的是8,如果不减1,实际初始化的容量为16,翻了一倍。

HashMap的put方法

		/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

上面的代码中,接受两个参数,key,value,这两个参数就不多说了,它调用了一个putVal方法。看到这你可能会想,HashMap是由数组+链表+红黑树组成的,那我传一个key-value它怎么知道要存在哪个位置呢?

你可能注意到了,调用putVal方法的时候,对key进行了hash(key)操作,也就是计算出存放的位置下标。

什么是Hash算法?

HashMap与hash算法有着千丝万缕的联系,那么什么是hash算法呢?

hash算法就是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。

####hash算法的特点

  • 输入的微弱变化经过hash函数也会有不同的输出
  • 输出的结果不能推导出原文

再来看下hash(key)方法,我们称它为扰动函数,因为它对key的hashcode进行了其他运算

		/**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

如果key为null的话,返回0,也就是数组的第一个位置;

如果key不为null,那么用key的hashcode异或key的hashcode无符号右移16位,也就是让hashcode的低16位也参与运算。

假设:key的hashcode的二进制数为:0b01010010010010001010100010101100

无符号右移16位:0b00000000000000000101001001001000

0b01010010010010001010100010101100

0b00000000000000000101001001001000 ^

——————————————————————

0b01010010010010001111101011100100

这样做的目的减少hash冲突,让散列表填充的更均匀。

看一下putVal方法:

		/**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
      	// 当前散列表
        Node<K,V>[] tab; 
      	// 当前节点
      	Node<K,V> p; 
      	// n当前散列表长度
      	// i要放的位置索引
      	int n, i;
      	// 把当前散列表赋值给tab,把当前散列表的长度赋值给n,并判断散列表是否为null或者散列表长度为0
        if ((tab = table) == null || (n = tab.length) == 0)
          	// 如果散列表没有被初始化,调用resize()方法进行初始化,并把初始化后的散列表长度赋值给n
            n = (tab = resize()).length;
      	// 这里的算法很巧妙,用当前数组长度-1 与 对key的hash值扰动后的值 进行按位与操作,
      	//然后将得出的结果(下标)赋值给i,并判断当前位置是否为null,并把当前位置赋值给p
      	// 这里要说下(n - 1) & hash
      	// 因为散列表的长度一定是2的n次幂,默认是16
      	// 16 - 1 = 15,二进制数为:1111
      	// hash值为32位,假设为:01010101000001000001010101010101
      	// 这两个数进行与运算,0和任何数相与都为0,所以决定取值范围的一定是后四位,最小值为0000,最大值为1111
      	// 所以一定是在0到15之间
        if ((p = tab[i = (n - 1) & hash]) == null)
          	// 如果放值的位置为null,则直接组装个node放进去,并赋值给散列表	
            tab[i] = newNode(hash, key, value, null);
        else {
          	// 如果放值的位置不为null
          	// e临时节点
            Node<K,V> e; 
          	// 临时key
          	K k;
          	// 如果当前位置的hash值==该key的hash值 并且 当前位置的key等于传过来的key
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
              	// 将当前节点赋值给临时节点
                e = p;
          	// 如果当前要放值的节点是一个红黑树
            else if (p instanceof TreeNode)
              	// 把值放到红黑树中,并把节点赋值给e
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
              	// 说明要放值的位置是个链表,循环到最末节点,把新节点放到最末节点的后面
                for (int binCount = 0; ; ++binCount) {
                  	// 如果遍历到当前节点的下一个节点等于null说明是最末节点,并把下一节点赋值给e
                    if ((e = p.next) == null) {
                      	// 组装个node赋值给当前节点的下一节点
                        p.next = newNode(hash, key, value, null);
                      	// 如果链表的长度 >= 树化阈值 - 1,则把链表升级为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                  	// 如果遍历链表的过程中,如果没有到最末节点,判断每一个节点的hash和key是否和
                  	// 要存入的hash和key相等,如果相等,则跳出循环,不再遍历
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
          	// e不为空说明已经存在该key
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
              	// 如果onlyIfAbsent为false或者原key的值为null,则替换为新值,并返回旧值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
      	// 自增修改次数
        ++modCount;
      	// size是散列表的长度,每次新增一个key-value,size自增,当大于扩容值的时候,需要执行resize()方法进行扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

再看下resize()方法:

HashMap的resize方法

		/**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
      	// 原散列表
        Node<K,V>[] oldTab = table;
      	// 原散列表长度,如果原散列表是null,长度为0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
      	// 原扩容阈值
        int oldThr = threshold;
      	// newCap新散列表长度
      	// newThr新扩容阈值
        int newCap, newThr = 0;
      	// 如果原散列表长度大于0,说明已经初始化了
        if (oldCap > 0) {
          	// 如果原散列表长度 >= 2的30次幂
            if (oldCap >= MAXIMUM_CAPACITY) {
              	// 那么扩容阈值为int的最大值
                threshold = Integer.MAX_VALUE;
              	// 返回原散列表
                return oldTab;
            }
          	// 原散列表长度左移一位也就是新的散列表长度变为原来的两倍,如果扩容后的新散列表长度小于
          	// 2的30次幂 并且 原数组长度 大于等于 16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
              	// 新的扩容阈值变为原来的2倍
                newThr = oldThr << 1; // double threshold
        }
      	// 原散列表长度=0,原扩容阈值大于0
        else if (oldThr > 0) // initial capacity was placed in threshold
          	// 新的扩容阈值依然=原扩容阈值
            newCap = oldThr;
      	// 原散列表长度=0 原扩容阈值=0
        else {               // zero initial threshold signifies using defaults
          	// 新的散列表长度 = 16
            newCap = DEFAULT_INITIAL_CAPACITY;
          	// 新的散列表扩容阈值 = 0.75 * 16 = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
      	// 如果新的扩容阈值 = 0
        if (newThr == 0) {
          	// ft扩容阈值 = 新的散列表长度 * 负载因子
            float ft = (float)newCap * loadFactor;
          	// 如果新的散列表长度小于2的30次幂并且扩容阈值<2的30次幂,新扩容阈值=ft,否则=int最大值
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
      	// 将新的扩容阈值赋值给变量HashMap的threshold
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
      	
      	// 新建一个长度为newCap(扩容后)的散列表
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
      	// 新的散列表赋值给HashMap的table
        table = newTab;
      	// 如果原散列表不为null
        if (oldTab != null) {
          	// 遍历原散列表
            for (int j = 0; j < oldCap; ++j) {
              	// e记录当前节点
                Node<K,V> e;
              	// 如果当前节点不为null
                if ((e = oldTab[j]) != null) {
                  	// 将原散列表的当前节点置为null
                    oldTab[j] = null;
                  	// 如果当前节点的下一节点为null,未发生过hash冲突,也就是非链表
                    if (e.next == null)
                      	// 把当前元素的hash值和散列表长度-1重新进行与运算,得到的新下标就是元素的新位置
                        newTab[e.hash & (newCap - 1)] = e;
                  	// 如果遍历到当前节点是红黑树
                    else if (e instanceof TreeNode)
                      	// 红黑树这里下篇文章着重介绍
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                      	// 这里说明是链表,要对链表进行拆分
                      	// 这里定义了两个链表 lo链表 和 hi链表
                      	// loHead 和 loTail分别指向lo链表的头和尾
                        Node<K,V> loHead = null, loTail = null;
                      	// hiHead 和 hiTail分别指向hi链表的头和尾
                        Node<K,V> hiHead = null, hiTail = null;
                      	// 链表的下一节点
                        Node<K,V> next;
                      	// 遍历链表,直到最末节点
                        do {
                            next = e.next;
                          	// 如果当前节点的hash值 & 原散列表长度 == 0,
                          	// 如果原散列表长度为16 二进制数为:10000,如果要想与操作结果=0,
                          	// 则必须当前节点的hash值结尾为0****,只要倒数第5位为0
                            if ((e.hash & oldCap) == 0) {
                              	// 如果lo链的尾部为null
                                if (loTail == null)
                                  	// 将当前节点赋值给lo链的头
                                    loHead = e;
                                else
                                  	// lo链的尾部不为null,lo链的尾部追加一个节点,并将该节点的next指向新节点
                                    loTail.next = e;
                                loTail = e;
                            }
                          	// (e.hash & oldCap) != 0,说明当前节点的hash值倒数第5位为1
                            else {
                              	// 如果hi链的尾节点为null
                                if (hiTail == null)
                                  	// 将当前节点赋值给hi链的尾节点
                                    hiHead = e;
                                else
                                  	// hi链的尾节点不为null,hi链的尾部追加一个节点,
                                  	// 并将该节点的next指向新节点
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                      	// 如果lo链的尾节点不为null
                        if (loTail != null) {
                          	// 将lo链的尾节点的next节点置为null,因为在原链表中,
                          	// 该节点的next节点可能为hi链的元素,但在lo链中是最末节点,
                          	// 所以需要将下一节点指向置为null
                            loTail.next = null;
                          	// 将lo链的头节点放到扩容后的散列表
                            newTab[j] = loHead;
                        }
                      	// 如果hi链的尾节点不为null
                        if (hiTail != null) {
                          	// 同理,将hi链的尾节点的下一节点指向置为null
                            hiTail.next = null;
                          	// 将hi链的头结点放在扩容后散列表的j+oldCap位置,如下图
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

rezise

HashMap的get方法

		/**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
     * key.equals(k))}, then this method returns {@code v}; otherwise
     * it returns {@code null}.  (There can be at most one such mapping.)
     *
     * <p>A return value of {@code null} does not <i>necessarily</i>
     * indicate that the map contains no mapping for the key; it's also
     * possible that the map explicitly maps the key to {@code null}.
     * The {@link #containsKey containsKey} operation may be used to
     * distinguish these two cases.
     *
     * @see #put(Object, Object)
     */
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
		/**
     * Implements Map.get and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
      	// 定义tab默认为散列表
        Node<K,V>[] tab; 
      	// first散列表的第一个节点
      	// e当前节点
      	Node<K,V> first, e; 
      	// n散列表长度
      	// k当前节点key
      	int n; K k;
      	// 判断如果当前散列表不为null 并且 散列表的长度>0 并且 按照key的hash(经过扰动函数后的)值
      	// 与当前散列表长度-1进行与操作,得到的下标就是存入时的下标, 判断当前值是否为null
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
          	// 说明存在key,先不管这个节点是什么类型,先判断hash值和key,如果相等直接返回节点
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
          	// 如果hash值或者key不相等,则判断节点类型,是红黑树还是链表?
            if ((e = first.next) != null) {
              	// 节点是红黑树
                if (first instanceof TreeNode)
                  	// 直接去红黑树查找节点
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
              	// 节点是链表,遍历  
              	do {
                  	// 找到hash值和key相等的节点并返回
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

HashMap的remove方法

		/**
     * Removes the mapping for the specified key from this map if present.
     *
     * @param  key key whose mapping is to be removed from the map
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
		/**
     * Implements Map.remove and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to match if matchValue, else ignored
     * @param matchValue if true only remove if value is equal
     * @param movable if false do not move other nodes while removing
     * @return the node, or null if none
     */
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
      	// tab当前散列表
        Node<K,V>[] tab; 
      	// p当前节点
      	Node<K,V> p; 
      	// n当前散列表长度
      	// index为key的下标位置
      	int n, index;
      	// 如果当前散列表不为null 并且 散列表长度大于0 并且散列表中要删除的元素不为null
        if ((tab = table) != null && (n = tab.length) > 0 &&
            // 散列表长度-1 与 key的hash(经过扰动后的)值进行与运算,得到的下标就是put时的下标
            (p = tab[index = (n - 1) & hash]) != null) {
          	// node记录节点
          	// e当前节点的下一节点
            Node<K,V> node = null, e; 
          	K k; V v;
          	// 如果散列表中的节点的hash和key都相等,直接把该节点赋值给node
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
          	// 如果数组的当前节点hash值和key不一致,则判断是红黑树还是链表?
            else if ((e = p.next) != null) {
              	// 当前节点时红黑树
                if (p instanceof TreeNode)
                  	// 从红黑树中找到key的节点
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                // 当前节点时链表
              	else {
                  	// 遍历链表,找到hash值和key相同的节点
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                      	// 找到节点钱记录上一节点,用于更新上一节点的next节点
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
          	// 找到了hash值和key值的节点 matchValue默认传的是false,所以不校验value
            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)
                  	// 把该位置置为null
                    tab[index] = node.next;
              	// 说明该位置是链表
                else
                  	// 把当前位置的上一个节点的next指向当前位置的下一个节点
                    p.next = node.next;
              	// 修改次数+1
                ++modCount;
              	// 散列表的个数-1
                --size;
                afterNodeRemoval(node);
              	// 返回当前节点
                return node;
            }
        }
        return null;
    }

以上把HashMap比较重要的方法都分析一遍,除了红黑树的部分,如果有人问你HashMap的底层实现,你会了吗?

常见面试题

  • HashMap是什么时候进行初始化的?
  • put方法的操作流程?
  • HashMap是如何扩容的?
  • HashMap put的时候是如何路由的?
  • 等等…
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YoungJ5788

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值