vlambda博客
学习文章列表

对Java中HashMap的keySet()方法进行探索

文章收藏的好句子:一个人必须不停地写作,才能不被茫茫人海淹没。


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中的代码块来执行的


对Java中HashMap的keySet()方法进行探索

图2

看图2,当我们用增强 for 循环遍历数组的时候,最终会变成:从下标为0的索引进行输出元素,下标变量(var4)小于数组长度(var3)为条件,对下标变量的值进行加1,然后再用下标对数组元素一个个输出。


现在我们回过头来看一下注释1 中遍历的 map.entrySet() 是什么?我们打开 HashMap 的 entrySet 方法瞧一瞧;


对Java中HashMap的keySet()方法进行探索


entrySet 方法返回来的是一个 Set 集合;看这个3目运算符,entrySet 变量赋值给 es 变量,不管 es 是不是空,其实 entrySet 方法返回的 Set<Map.Entry<K,V>> 对象肯定不是空的;然后这个 entrySet 方法最终返回的是 EntrySet 对象对不对,因为 entrySet 变量也最终指向 EntrySet 对象;好,我们点击注释2 中的 EntrySet 类,看看它的具体实现;


对Java中HashMap的keySet()方法进行探索


看 EntrySet 类,它里面有一个 iterator 方法,返回的是 Iterator<Map.Entry<K,V>> 这个迭代器对象,我们看一下注释4 中这个迭代器的具体实现类 EntryIterator;


对Java中HashMap的keySet()方法进行探索


看,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 方法结合使用。