HashMap源码阅读

78 阅读16分钟

我正在参加「掘金·启航计划」

HashMap源码阅读

简介

HashMap存储的是以键值对(key-value)形式的集合,允许出现null键 和 null值,并且储存的元素是无序的。Hashmap线程不安全。

内部采用 数组 + 链表 + 红黑树 的数据格式, 这种数据结构拥有几乎常数级的 增删改查 效率。

继承图

HashMap的继承图比较简洁

  1. Serializable 可序列化
  2. Cloneable 可克隆
  3. AbstractMap AbstractMap 中实现了很多Map中的方法。

字段

 //默认初始化桶的个数 16
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 ​
 //HashMap的最大桶的个数
 static final int MAXIMUM_CAPACITY = 1 << 30;
 ​
 //默认加载因子 用于控制何时扩容
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 ​
 //树化的阈值,也就是当桶中的元素个数大于这个值时,才能被树化。
 static final int TREEIFY_THRESHOLD = 8;
 ​
 //逆树化的阈值,当桶中的元素个数小于这个值时,桶中的元素被转化成链表结构
 static final int UNTREEIFY_THRESHOLD = 6;
 ​
 // 最小树化容量,当HashMap的桶的个数达到这个值后,才会对符合条件的桶内元素进行树化
 // 否则, 会对hashMap扩容。
 static final int MIN_TREEIFY_CAPACITY = 64;
 ​
 /**
  * The table, initialized on first use, and resized as
  * necessary. When allocated, length is always a power of two.
  * (We also tolerate length zero in some operations to allow
  * bootstrapping mechanics that are currently not needed.)
  */
 // 这个数组就是 数组 + 链表 + 红黑树 中的数组,它所存储的的Node元素可以链接成链表或树
 // 所说的桶就是这个数组所存储的元素,每个下标对应一个桶。
 transient Node<K,V>[] table;
 ​
 // 用来获取keySet 和 values
 transient Set<Map.Entry<K,V>> entrySet;
 ​
 // HashMap中元素的个数
 transient int size;
 ​
 // 记录哈希表被修改的次数,只包括添加、删除等影响哈希表内元素数量的修改。
 transient int modCount;
 ​
 // 当Hash的数量达到这个值时扩容
 int threshold;
 ​
 // 默认加载因子 用于控制何时扩容
 final float loadFactor;

内部类

为了解决哈希冲突, Hash Map的每个桶中使用链表和红黑树实际存储键值对元素, 当桶内元素数量大于8时,会将链表结构转化为树结构,称为树化(treeify); 当元素数量少于6时,会将树形结构再转化为链表结构,称为逆树化(untreeify).

在jdk1.8之前,HashMap只采用链表来解决Hash冲突,但是当同一个桶中有过多元素时,链表结构严重影响了Hash Map的查找和添加效率。 在jdk1.8,HashMap有引入了红黑树结构来解决hash冲突;

为什么不完全采用红黑树结构呢?

因为红黑树的节点TreeNode的内存开销近乎时链表节点Node的近两倍,而在桶内元素的个数小于8个时,红黑树的查找效率与链表相比并没有太明显的提升。

 //链表节点
 static class Node<K,V> implements Map.Entry<K,V> {
     //节点的哈希值
     final int hash;
     //键
     final K key;
     //值
     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;
     }
 …………
 }

TreeNode--继承自-->LinkedHashMap.Entry<K,V> --继承自--> HashMap. Node<K,V>

TreeNode 除了拥有parent、left、right这些树形节点的指针,还拥有prev、next这两个链表结构的指针。TreeNode其实是红黑树+链表的双重结构。

 //红黑树节点
 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
     //指向父节点
     TreeNode<K,V> parent;  // red-black tree links
     //指向左子节点
     TreeNode<K,V> left;
     //指向右子节点
     TreeNode<K,V> right;
     //连接前一个节点
     TreeNode<K,V> prev;    // needed to unlink next upon deletion
     //红黑树的颜色属性,标记该节点是红色节点还是黑色节点
     boolean red;
     
     //构造方法
     TreeNode(int hash, K key, V val, Node<K,V> next) {
         super(hash, key, val, next);
     }
 ​
     //找到并返回根节点
     final TreeNode<K,V> root() ...
 ​
     //确保所给的root节点是根节点(链表结构)
     static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root)...
 ​
 ​
     //根据hash值和键查找元素节点
     final TreeNode<K,V> find(int h, Object k, Class<?> kc) ...
 ​
     //当a和b无法使用compareTo方法比较a和b时,使用此方法来确定a和b的顺序
     static int tieBreakOrder(Object a, Object b)...
 ​
     //将链表结构树华成为红黑树结构
     final void treeify(Node<K,V>[] tab)...
 ​
     //逆树化,将树结构转化为链表结构
     final Node<K,V> untreeify(HashMap<K,V> map)...
 ​
     //插入树节点
     final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v)...
 ​
     //删除树节点
     final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,boolean movable)...
 ​
     //当进行resize()操作时,重新分配桶内元素。
     final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) ...
 ​
     //红黑树的左旋操作,为保持红黑树平衡的必要操作。
     static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,TreeNode<K,V> p)...
 ​
     //右旋
     static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,TreeNode<K,V> p)...
     //平衡因插入操作引起的红黑树不平衡
     static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x)...
     //平衡因删除操作引起的红黑树不平衡
     static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,TreeNode<K,V> x)...
     //遍历检查红黑树结构是否正确
     static <K,V> boolean checkInvariants(TreeNode<K,V> t)...
 }

构造方法

 //指定初始化容量(数组的大小),和加载因子(控制扩容)
 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);
 }
 ​
 //指定初始化容量
 public HashMap(int initialCapacity) {
     //调用了上一个构造方法
     this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }
 ​
 //无参构造
 public HashMap() {
     this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
 }
 ​
 //根据一个已有的Map构造一个新的HashMap
 public HashMap(Map<? extends K, ? extends V> m) {
     this.loadFactor = DEFAULT_LOAD_FACTOR;
     putMapEntries(m, false);
 }

tableSizeFor()

这个方法可以将任意整数转化成2n ,且2n满足最小且大于cap

 /*  这个方法是确保table的长度始终是2的n次方,HashMap规定,table表的长度只能是2的n次方,
     这样设计的目的是在进行扩容操作的时候有较好的性能.
     返回值满足 大于等于cap,且是2的n次方。
 */
  static final int tableSizeFor(int cap) {
     //cap-1的目的是为了防止 cap本来就满足是2的n次方,再经过下面的操作后,就会扩大至原来的两倍
     int n = cap - 1;
     //以n = 01010 为例,走一下下面的流程
     n |= n >>> 1;   //  n>>>1 无符号右移一位 n>>>1 = 00101  将右移后的结果与n做与运算 01010 | 00101 =01110
     n |= n >>> 2;   //  经过上一步运算现在n的值 n=01110。n>>>2=00011,将右移后的结果与n做与运算01110|00011=01111
     n |= n >>> 4;   //......
     n |= n >>> 8;
     n |= n >>> 16;
     //  经过上面的运算,可以看到n已经变成头部为0,尾部全为1的整数,转化成十进制可以表示为2的n次方-1;
     //  返回的时候,将运算结果+1,刚好就等于2的n次方
     return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
 }

put()

 public V put(K key, V value) {
     return putVal(hash(key), key, value, false, true);
 }
 ​
 /**
 *   int hash key的hash值(经过扰动处理的hash值)
 *   K key 存贮的键
 *   V value 存储的值
 *   boolean onlyIfAbsent 如果为true,将不会替换已存在的值,而是保留旧值,丢弃新值。
 *   boolean evict 若为false,表示由构造函数调用
 */
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                boolean evict) {
     /*变量含义
     tab:table数组;如果需要扩容,引用的是扩容后的数组
     p:通过hash值找到将被插入元素的桶
     n:tab的长度
     i:p的下标
     
     */
     Node<K,V>[] tab; Node<K,V> p; int n, i;
     //如果table数组为null或者table数组的长度为零,对table数组进行第一次扩容。
     if ((tab = table) == null || (n = tab.length) == 0)
         //更新n和tab
         n = (tab = resize()).length;
     //hash值对tab数组的长度取余,就得到了该元素应该存储在hash数组的位置。如果该位置的元素为null,直接将新节点放在该位置上。
     if ((p = tab[i = (n - 1) & hash]) == null)
         tab[i] = newNode(hash, key, value, null);
     else {
         //如果p不为null,
         Node<K,V> e; K k;
         /*
         如果p不为null,就有可能出现key重复的情况,所以再插入前,就先要判断key是否重复,再进行插入或删除操作。
         我们都知道,hashMap中桶内的元素有两种结构,链表或红黑树。因此对这两种结构要进行不同的处理。
         */
         //先简单判断一下,桶内的第一个元素的键(key),与即将插入元素的键有没有重复
         if (p.hash == hash &&
             ((k = p.key) == key || (key != null && key.equals(k))))
             //记录下桶内与即将插入元素的键值重复的元素
             e = p;
         //分情况处理,如果是树形节点的的话,调用红黑树的putTreeVal()方法
         else if (p instanceof TreeNode)
             //如果有与即将插入元素的键值重复的元素,返回该元素
             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
         //如果p是链表节点的话
         else {
             //遍历整个链表,寻找是否有与即将插入元素的键值重复的元素。
             for (int binCount = 0; ; ++binCount) {
                 //如果p.next=null说明已经到链表末尾了,还没有找到与之键值重复的元素,那就直接插入元素即可
                 if ((e = p.next) == null) {
                     p.next = newNode(hash, key, value, null);
                     //如果链表的长度达到了树化的阈值,直接调用树化方法,将链表结构转化为树形结构
                     if (binCount >= TREEIFY_THRESHOLD - 1) //为什么要-1? 因为遍历其实是从第二个元素开始的
                         treeifyBin(tab, hash);
                     //跳出循环
                     break;
                 }
                 //如果找到了与之键值重复的元素,直接跳出。此时e就是找到的元素
                 if (e.hash == hash &&
                     ((k = e.key) == key || (key != null && key.equals(k))))
                     break;
                 //e = p.next,开始准备遍历下一个元素了
                 p = e;
             }
         }
         //e!=null的话,说明桶中存在与插入元素键值重复的元素,而这个元素正是e
         if (e != null) { // existing mapping for key
             V oldValue = e.value;
             //onlyIfAbsent 意为 仅当value值缺省时替换,如果onlyIfAbsent为true,被替换的元素e的值不为null时,会保留旧值,抛弃新值。
             if (!onlyIfAbsent || oldValue == null)
                 e.value = value;
             //回调函数,HashMap中这个方法是个空方法。并没有实际的作用
             afterNodeAccess(e);
             //如果是替换操作的话,会返回oldValue
             return oldValue;
         }
     }
     //操作数+1;
     ++modCount;
     //size+1,如果size > 扩容阈值,就扩容
     //这个size是HashMap中元素的个数
     if (++size > threshold)
         resize();
     //回调函数,HashMap中这个方法是个空方法。并没有实际的作用
     afterNodeInsertion(evict);
     return null;
 }
 ​

putAll()

 //将m中的元素全部添加到本集合中
 public void putAll(Map<? extends K, ? extends V> m) {
         //调用putMapEntries方法,参数true表示不是被构造方法调用
         putMapEntries(m, true);
     }
 ​
 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
     int s = m.size();
     if (s > 0) {
         //如果未初始化
         if (table == null) { // pre-size
             //利用 s / loadFactor 可以算出不大于m的扩容阈值的最小容量,因为结果是浮点数,容量要求是整数,所以需要取整,所以要+1.0F
             float ft = ((float)s / loadFactor) + 1.0F;
             // 控制t的范围,t就是要初始化的容量
             int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                      (int)ft : MAXIMUM_CAPACITY);
             //把t赋值给了threshold,HashMap第一次初始化,会把threshold作为他的初始化容量
             if (t > threshold)
                 threshold = tableSizeFor(t);
         }
         //如果hashMap已经初始化,且m.size>threshold 那么就扩容
         else if (s > threshold)
             resize();
         //将m中的元素一个个放入到本集合中
         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);
         }
     }
 }

hash()

//返回key的hash值
static final int hash(Object key) {
	int h;
    //key=null 返回0,否则调用key.hashCode()方法,再进行一次扰动处理,使hash值更加散列。
    //具体做法是,与自己的hash值无符号右移16位后,做异或运算。
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

risize()


/*
扩容操作,HashMap中元素个数大于扩容阈值threshold时,进行扩容
一般情况下都是扩容至原容量的两倍,除了第一次初始化和扩容后容量超过了最大限制容量这两种情况
 */
final Node<K,V>[] resize() {
    //原来的table数组记为oldTable
    Node<K,V>[] oldTab = table;
    //旧table表的容量,如果是null的话就记为0;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //保存旧的这个扩容阈值
    int oldThr = threshold;
    int newCap, newThr = 0;
    // 如果oldCap>0 说明这不是第一次扩容
    if (oldCap > 0) {
        //如果旧table的容量已经达到最大值了,就没有办法再扩容了
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //一般情况下,新table数组的容量是旧table数组的两倍,如果旧table的容量>=16 并且扩容后的table数组的容量<=最大容量的话,新的扩容阈值newThr也扩大到原来的两倍。
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            //threshold=Capacity*loadFactor, 所以table的容量扩大两倍,threshold自然也要扩大两倍。
            newThr = oldThr << 1; // double threshold
    }
    //这个分支也就是oldCap=0,也就是oldTable=null 或者 oldTable.length=0 的情况,也就是第一次初始化数组的情况。
    //情况一:调用带参构造方法,并指定了初始化容量
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    //情况二:调用无参构造方法
    else {               // zero initial threshold signifies using defaults
        //采用默认的配置
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //针对前面的特殊情况没有给newThr赋值的话,这一步需要给newThr赋值。
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        //如果newCap或ft超出最大容量,就给newThr赋值Integer.MAX_VALUE
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //更新threshold
    threshold = newThr;
    //创建newTable
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            //用e临时保存旧table数组内的元素
            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)         //当桶内元素是树结构时,调用split()方法,重新分配table元素的存储位置
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    //当数组内元素是链表结构的话
/*将链表分成了两部分,其中(e.hash & oldCap) = 0的部分加入到loHead~loTail中,其余加入到hiHead~hiTail中。
	最后newTab[j]=loHead, newTab[j + oldCap] = hiHead;
	为什么要这样划分呢?
	hashMap规定table的长度必须是2的次方幂,而2的次方幂有一个特点,即首位为1,其余二进制位为0,
	例如当前的容量为oldCap 16=10000,现在对HashMap扩容,table的容量扩容为原来的两倍,新的容量newCap=32=100000;
	现在有两个元素它们的hash值分别为 01011 和11011,它们在旧table中的位置 01011 & (oldCap-1)=01011 & 1111 =1011;
	11011 & (oldCap-1) = 11011 & 1111 = 1011, 它们在Table中的位置是相同的。
	但是,在新的table中 01011 & (newCap-1)=01011 & 11111 =0101111011 & (newCap-1) = 11011 & 11111 = 11011,
	可以看出,两个元素在新数组中的位置不同,仅仅是因为他们的最高位不同, 而通过e.hash & oldCap 就可以辨别出他们的最高位。
	
*/
                    //低位链表
                    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;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    //loTail!=null 说明低位链表不为空
                    if (loTail != null) {
                        //loTail.next要置空
                        loTail.next = null;
                        //加入到原来的位置
                        newTab[j] = loHead;
                    }
                    //hiTail!=null 说明高位链表不为空
                    if (hiTail != null) {
                        hiTail.next = null;
                        //将链表加入到j+oldCap的位置上
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

get()

/**
 * 通过key返回value, 如果HashMap中不存在该key,则返回null
 */
public V get(Object key) {
    Node<K,V> e;
    //HashMap通过hash值来找元素再table数组中的位置,再通过key找到对应元素
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

//通过hash值,和key寻找指定元素
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    //tab[(n - 1) & hash] hash值对table的长度取余,计算出该节点在table中的下标。
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        //检查第一个元素是不是要找的元素
        if (first.hash == hash && // always check first node
            ((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 {
                //找到了就返回
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    //没找到 返回null
    return null;
}

treeifyBin()

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    //当table的长度 大于等于 最小树化容量 时才会对桶内的元素树化,否则只会进行扩容操作
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        //遍历整个链表
        do {
            //将链表节点替换成红黑树节点
            TreeNode<K,V> p = replacementTreeNode(e, null);
            //用节点的prev属性和next属性将节点再连接起来,TreeNode除了拥有树节点的属性还有链表节点的属性
            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);
    }
}

remove()

//移除并返回指定元素的值
public V remove(Object key) {
    Node<K,V> e;
    //调用removeNode方法,如果集合中没有指定元素,就返回null
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

/**
 * @param hash key的hash值(经过hash(key)得到的值)
 * @param key 元素的键
 * @param value 要删除的元素的值,当matchValue为true时,这个值才有用
 * @param matchValue 如果为true,只有当集合中的元素的键和值同时匹配,才会被删除
 * @param movable 只删除指定元素,而不移动其他元素的位置。
 * @return 返回值为删除的节点或者null
 */
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不为空,且通过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
            node = p;
        //如果第一个元素不是要找的元素
        else if ((e = p.next) != null) {
            // 判断节点的类型,如果是树节点类型,就调用树节点的查找方法,如果是链表类型,就用链表的方法查找元素
            if (p instanceof TreeNode)
                // 如果是树节点类型,调用树节点的查找方法
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
                // 链表的查找方法,do-while遍历链表
                do {
                    //如果hash值和key都匹配,将找到的元素赋值给node,然后跳出循环
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        //node!=null 表示已经找到key值匹配的元素了,接下来要判断需不需要匹配值
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            //如果是树形节点,调用红黑树的removeTreeNode方法
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)	//如果node==p,说明要删除的元素是桶内的第一个元素,
                tab[index] = node.next;
            else// 否则p就是node的前一个元素
                p.next = node.next;
            //操作数++
            ++modCount;
            //元素总数--
            --size;
            //在hashMap中是一个空方法
            afterNodeRemoval(node);
            //返回删除的节点
            return node;
        }
    }
    //没有匹配的元素就返回null
    return null;
}

keySet()

/*
* 返回一个Set包含HashMap中所有的键key,这个Set中重写的方法,都是调用的HashMap中的方法,所以keySet和HashMap可以做到数据* 同步。
*/
public Set<K> keySet() {
    Set<K> ks = keySet;
    //仅当第一次调用时,会创建一个 KeySet对象,后续调用此方法,无需再次创建
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

final class KeySet extends AbstractSet<K> {
    // 返回HashMap的size
    public final int size()                 { return size; }
    // 调用HashMap的clear()方法
    public final void clear()               { HashMap.this.clear(); }
    // 创建并返回KeyItertor,
    public final Iterator<K> iterator()     { return new KeyIterator(); }
    public final boolean contains(Object o) { return containsKey(o); }
    public final boolean remove(Object key) {
        return removeNode(hash(key), key, null, false, true) != null;
    }
    public final Spliterator<K> spliterator() {
        return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    // 传入一个lambda表达式,可以对Set中的元素进行遍历操作
    public final void forEach(Consumer<? super K> action) {
        Node<K,V>[] tab;
        // 如果lambda为null 则抛出异常
        if (action == null)
            throw new NullPointerException();
        //如果table中的元素不为空,则遍历操作HashMap元素中的key
        if (size > 0 && (tab = table) != null) {
            //记录下开始对HashMap中的元素操作前的modCount
            int mc = modCount;
            // 对HashMap循环遍历
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    //将每个元素的key传给lanbda表达式,使对其操作
                    action.accept(e.key);
            }
            // 如果遍历完,HashMap的modCount发生改变,说明在操作的过程中Hash Map中的元素发生了改变。刚才遍历的结果可能不准确,所以要抛出异常
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}
// KeyItertor 继承自Hashitertor,只重写了 next() 方法。
final class KeyIterator extends HashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().key; }
}

values()

 /*
 * 返回HashMap中所有的值组成的集合
 */
 public Collection<V> values() {
     Collection<V> vs = values;
     // 只有在第一次调用该方法时创建Values对象
     if (vs == null) {
         vs = new Values();
         values = vs;
     }
     return vs;
 }
 ​
 //Values 中重写AbstractCollection中的方法大都是调用Hash Map中的方法
 final class Values extends AbstractCollection<V> {
     public final int size()                 { return size; }
     public final void clear()               { HashMap.this.clear(); }
     public final Iterator<V> iterator()     { return new ValueIterator(); }
     public final boolean contains(Object o) { return containsValue(o); }
     public final Spliterator<V> spliterator() {
         return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
     }
     public final void forEach(Consumer<? super V> action) {
         Node<K,V>[] tab;
         if (action == null)
             throw new NullPointerException();
         if (size > 0 && (tab = table) != null) {
             int mc = modCount;
             for (int i = 0; i < tab.length; ++i) {
                 for (Node<K,V> e = tab[i]; e != null; e = e.next)
                     action.accept(e.value);
             }
             if (modCount != mc)
                 throw new ConcurrentModificationException();
         }
     }
 }