共计 2821 个字符,预计需要花费 8 分钟才能阅读完成。
PS:本文系转载文章,浏览原文可读性会更好,文章开端有原文链接
ps:本文是基于 JDK 1.8 的根底上对 HashMap 的局部源码进行解读的。
在咱们平时软件的开发中,用到的 Java 语言进行编写程序的读者都晓得,咱们有时候会用到 HashMap,特地是 HashMap 的 keySet() 办法,咱们先来一下一段代码;
Map<String,Integer> map = new HashMap<String,Integer>();
for (int i = 1; i < 30; i++) {map.put("key" + i,i);
}
//1、for (HashMap.Entry<String,Integer> entry : map.entrySet()) {String key = entry.getKey();
int value = entry.getValue();
System.out.println("key =" + key + ",value =" + value);
}
咱们平时应用的加强 for 循环,用得多的是遍历的指标是数组或者汇合;好,咱们先放一放加强 for 循环 map.entrySet() 的案例,先来一段加强 for 循环遍历数组的代码,如图 1 所示:
图片
图 1
当图 1 中的代码块真正要执行的时候,其实就会变成图 2 中的代码块来执行的
图片
图 2
看图 2,当咱们用加强 for 循环遍历数组的时候,最终会变成:从下标为 0 的索引进行输入元素,下标变量(var4)小于数组长度(var3)为条件,对下标变量的值进行加 1,而后再用下标对数组元素一个个输入。
当初咱们回过头来看一下正文 1 中遍历的 map.entrySet() 是什么?咱们关上 HashMap 的 entrySet 办法瞧一瞧;
图片
entrySet 办法返回来的是一个 Set 汇合;看这个 3 目运算符,entrySet 变量赋值给 es 变量,不论 es 是不是空,其实 entrySet 办法返回的 Set<Map.Entry<K,V>> 对象必定不是空的;而后这个 entrySet 办法最终返回的是 EntrySet 对象对不对,因为 entrySet 变量也最终指向 EntrySet 对象;好,咱们点击正文 2 中的 EntrySet 类,看看它的具体实现;
图片
看 EntrySet 类,它外面有一个 iterator 办法,返回的是 Iterator<Map.Entry<K,V>> 这个迭代器对象,咱们看一下正文 4 中这个迭代器的具体实现类 EntryIterator;
图片
看,EntryIterator 继承了 HashIterator,在 next 办法中还调用了在父类 HashIterator 实现的 nextNode 办法;
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {//5、} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {return next != null;}
final Node<K,V> nextNode() {Node<K,V>[] t;
//6、Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
//7、if ((next = (current = e).next) == null && (t = table) != null) {
do {//8、} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
......
}
从下面的剖析能够晓得,EntryIterator 是一个迭代器,如果遍历迭代器,那么重要的 2 个办法就是 next 办法和 hasNext 办法,EntryIterator 的 next 办法间接调用父类 HashIterator 的 nextNode 办法,而 EntryIterator 的 hasNext 办法却在 HashIterator 类中实现。
看正文 5,next 变量是在构造方法 HashIterator 中赋值的,只有 t 不为空,t 数组是保留有元素的,那么 next 变量肯定不是空值,那么正文 6 中的 e 变量第一次被赋值就不为空。
咱们晓得 t 是保留 Node<K,V> 类型的数组,t 中的某一个地位的元素有可能保留的是空值,有可能保留的是只有一个节点,有可能保留的是一条链表的头节点,有可能保留的是一棵红黑树的根节点;为来不便了解,我先画一张数组 t 保留元素的结构图,就假如 t 保留的元素如图 4 所示;
图片
假如 HashIterator 的 nextNode 办法中的 next 变量遍历的 t 数组是图 4 中的;当遍历迭代器的时候,必定是执行迭代器的 next 和 hasNext 办法对不对,那好,咱们看,当第一次执行 nextNode 办法的时候就返回 t[1] 外面的元素,而后再判断 t[1] 外面的元素的下一个节点是否为空(看正文 7 的代码),如果为空,就执行正文 8 中的 do while 语句,查看 t 数组以后索引(以后索引为 1)的下一个索引外面保留的元素是否为空,不为空就用 next 变量指向下一个索引保留的元素,而后返回的是以后索引(以后索引为 1)的元素;正文 7 的代码是为了遍历链表或者红黑树顺便取出链表或者红黑树的下一个元素,而正文 8 的代码为来遍历 t 数组的索引顺便取出 t‘数组下一个索引保留的元素。
好,咱们当初回过头来看下面加强 for 循环 map.entrySet() 的案例,map.entrySet() 拿到的是一个 EntryIterator 的迭代器,而后通过 EntryIterator 迭代器的 next 和 HasNext 办法联合应用,拿到 Hash-Map.Entry<String,Integer> 对象,通过 HashMap.Entry<Strin-g,Integer> 对象拿到 key 和 value,咱们看一看是否是这样呢,咱们通过开发工具(AndroidStudio)编译一下,看看它的字节码文件;
图片
我代码所在的包名是 com.xe.customfactory,哈哈,看到了没,果然 map.entrySet() 拿到的是一个 EntryIterator 的迭代器,而后通过 EntryIterator 迭代器的 next 和 HasNext 办法联合应用。