(注意:本文源码基于JDK1.8)
遍历HashMap对象中的元素,三个方法都可以得到一个集合,一起看看他们的区别
entrySet()方法分析
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
用于获得由Map.Entry对象作为元素的Set集合,每个Entry对象即持有了Key对象、也持有了Value对象
1、定义Set局部变量
首先定义局部变量es,用于获得获取缓存的Set对象
2、判断缓存情况
若缓存了EntrySet对象,则直接返回HashMap缓存的对象,即返回局部变量es即可
若未缓存EntrySet对象,此时则创建一个EntrySet对象,并且赋值给HashMap对象持有的entrySet,然后再返回entrySet
只有Set是不行的,还需要调用iterator()方法,获取集合的迭代器对象才可以!
EntrySet中的iterator()方法分析
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
该方法位于EntrySet类中,用于返回集合的一个迭代器对象
EntryIterator的父类是HashIterator,我们再一起研究它的源码
HashIterator()构造方法分析
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
HashIterator是EntryIterator的父类
1、首先获得外部类HashMap对象持有的modCount,然后由自己的实例变量expectedModCount进行保管
2、接着获得外部类HashMap对象持有的底层数组对象table交给临时变量t持有
3、将自己持有的实例变量current、next均赋值为null、由index赋值为0。
4、在外部类HashMap对象的底层数组已经创建且元素数量大于0的情况下,执行了一段精彩的代码……,还可以这么写循环
do {} while (index < t.length && (next = t[index++]) == null);
这段代码的目的就是找到HashMap持有的底层数组对象中第一个不为空的桶,当我们遍历HashMap时,需要元素,有了迭代器对象,接下来就该调用的hasNext()方法
HashIterator中的hasNext()方法分析
public final boolean hasNext() {
return next != null;
}
你去EntryIterrator类中查找该方法,你会发现找不到,于是你需要向上查找,即去父类中查找该方法,最终在它的父类HashIterator找到了该方法
判断next是否空,next是EntryIterator对象持有的实例变量(父类HashIterator继承来的),next指向第一个包含元素的桶中的第一个元素,返回值表示是否存在元素
接下来我们按照遍历元素的操作,这时候该去调用迭代器对象的next()方法
EntryIterator类继承结构
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
继承自HashIterator
EntryIterator类中的next()方法分析
public final Map.Entry<K,V> next() { return nextNode(); }
该方法位于EntryIterator类中,该方法内部又调用一个nextNode方法(见8号知识点),你会发现它(见6号知识点)根本就没有这个方法啊,在哪里呢?我们去它的父类里面找找,果然找到了,从返回值类型上,我们也知道nextNode方法将返回一个Map.Entry对象
8、无参
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
该方法位于HashIterator类中,是由它的某个子类调用的,为什么这么说呢?是因为HashMap三个迭代器对象全部继承HashIterator
a、首先定义一个局部变量t(类型是Node数组)、接着从next变量(当前第一个有元素的桶的第一个Node对象)中取出Node对象赋值给一个局部变量e
b、modCount与expectedModCount进行判断,还记得expectedModCount变量是什么初始化的吗?它是在创建EntryIterator对象的时候初始化的,modCount是HashMap对象持有的一个标记值(每次增加、删除元素都会+1),这里每次检查modCount的值与最初EntryIterator对象获取的expectedModCount值是否相等,代表检查modCount是否发生发现,如果它变了,这里会抛出一个ConcurrentModificationException,那么什么情况下modCount有可能改变?那就是多个线程使用HashMap对象,所以抛出一个经典的异常对象,这就是官方称作fail-fast机制的容错机制,目的是告诉你HashMap在多线程下是不好使的
c、e==null,作者仍然又做了判断,其实e有可能为null吗?有的,一种是多线程下并发访问违规使用HashMap对象的下场,这里也有可能会抛出NoSuchElementException,另外一种是未经hasNext判断,连续的调用next方法(违规使用迭代器)的下场
d、经过前面两个容错,如果都通过并没有抛出异常,这一步是为你找到下一个HashMap存在的元素的桶,这里有两种情况
第一种是当前桶内还有元素,此时会把桶内下一个元素赋值给next,用于遍历的下一次访问使用
第二种是桶内已经没有更多的元素了,此时通过这样一句经典语句去帮你找下一个有元素的桶,取出它的第一个元素赋值给next
do {} while (index < t.length && (next = t[index++]) == null);
e、最后该方法返回访问到的Node对象e
思考:抛出一个疑问倘若桶内结构是红黑树,遍历时,靠Node对象持有的实例遍历next能行吗?红黑树结构也不是单链表那样啊?大佬牛比牛就牛比在它在构造红黑树的时候,保留了每一个Node对象的next变量,我算明白了,就算桶内是红黑树结构它也是按照单链表结构来进行遍历的,我想多了,因为要遍历,这么做也是合理的啊,毕竟每一个元素访问到就是了
9、调用ketSet方法返回一个KeySet对象,然后再调用它的iterator方法,最终返回的迭代器就是KeyIterator对象,同样继承自HashIterator
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
10、values方法最终返回的迭代器对象就是ValueIterator对象,继承自HashIterator
final class ValueIterator extends HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
总结
a、当你调用entrySet方法时,HashMap对象会持有并缓存一个EntrySet对象(由entrySet实例变量持有)
b、当你调用keySet方法时,HashMap对象会持有并缓存一个KeySet对象(由keySet实例变量持有)
c、当你调用values方法时,HashMap对象会持有并缓存下一个Values对象(由value实例变量持有)
以上是我们遍历HashMap时常用的三个方法,都可以得到一个集合对象
接下来你知道它们是集合对象,你自然想到它们都实现了Iterator接口,你调用iterator方法()就会得到一个迭代器对象
当你再利用迭代器对象,使用hasNext、next方法的时候,底层用的是同一个HashIterator内的实例方法nextNode……
d、当桶内是红黑树结构时,因为每个Node对象实例变量next都有保留,所以仍然是按照单链表的方式进行遍历的(没有用树的前根、中根、后根遍历)
e、迭代器类的抽象类HashIterator实现了最重要的查找结点工作
HashIterator完成了定位到第一个桶内有元素的位置工作(首次创建迭代器对象时,要找到距离底层数组左侧最近的有元素的桶)
HashIterator完成了每次访问桶内元素后,查找下一个元素的工作