我正在参加「掘金·启航计划」
HashMap源码阅读
简介
HashMap存储的是以键值对(key-value)形式的集合,允许出现null键 和 null值,并且储存的元素是无序的。Hashmap线程不安全。
内部采用 数组 + 链表 + 红黑树 的数据格式, 这种数据结构拥有几乎常数级的 增删改查 效率。
继承图
HashMap的继承图比较简洁
- Serializable 可序列化
- Cloneable 可克隆
- 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 =01011;11011 & (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();
}
}
}