Java容器HashMap遍历方法和源代码解析
来源:互联网 发布:8月份经济数据评论 编辑:程序博客网 时间:2024/04/29 11:06
写在前面的话
本文针对的是Java1.6进行的源码分析,与其他版本可能存在差异。关于HashMap的底层结构和基本方法的解析,请参阅Java容器HashMap源代码解析
HashMap遍历用法
HashMap有四种遍历方法:1.遍历keySets,把每个key对应的值再取出来,这种方法最常用到;2.遍历values,直接获取map中的值;3.用迭代器直接进行遍历;4.遍历entrySet,此方法比起第一种方法来说,少了根据key去取value的操作,所以当数据量比较大时,效率会比一种方法高,推荐使用此方法。
四种遍历方法的代码示例如下:
public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("k1", "v1"); map.put("k2", "v2"); map.put("k3", "v3"); //第一种遍历方法,遍历keySet,用每个key再把value取出来,这种方法比较常用 for(String key : map.keySet()) { System.out.println("key=" + key + ",value=" + map.get(key)); } //第二种遍历方法,直接遍历values,这种方法的缺点是只能得到value值 for(String value : map.values()) { System.out.println("value=" + value); } //第三种遍历方法,用迭代器 Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); while(iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); System.out.println("key=" + entry.getKey() + ",value=" + entry.getValue()); } //第四种遍历方法,遍历entrySet,数据量大时推荐使用此种方法 for(Map.Entry<String, String> entry : map.entrySet()) { System.out.println("key=" + entry.getKey() + ",value=" + entry.getValue()); } }
HashMap遍历源代码解析
在这里,我们以第一种遍历方式为例,来探讨一下HashMap遍历方法的实现。KeySet方法在HashMap中只有短短两行的代码,如下:
public Set<K> keySet() { //keySet是在AbstractMap中定义的一个set值 Set<K> ks = keySet; //如果keySet有值就返回,如果为null就返回KeySet的一个对象 return (ks != null ? ks : (keySet = new KeySet())); }
这两行代码很简洁,逻辑也很好懂,keySet是在AbstractMap中定义的一个set值。如果keySet不为null,就返回keySet;如果keySet的值为null,就生成KeySet类的一个对象,把这个对象赋值给keySet,并作为结果返回。
刚开始看这段代码时,我也是很疑惑。我们都知道,HashMap的底层结构,实际上就是一个Entry类型的数组table。在没看源码前,我还以为keySet的实现是自己定义了一个数组,然后当每次往table中添加数据时,会显式的把key值同步添加到keySet的数组中。然而事实是,只用了这么短短的两行代码,就实现了这个功能。那究竟是怎么实现的呢?我们接着看KeySet这个类,答案就在这个类里。KeySet的源代码如下:
private final class KeySet extends AbstractSet<K> { //实现迭代器 public Iterator<K> iterator() { return newKeyIterator(); } //实现size()方法,直接返回HashMap的大小 public int size() { return size; } //实现contains方法,直接调用HashMap的containsKey方法 public boolean contains(Object o) { return containsKey(o); } //实现remove方法,直接调用HashMap的removeEntryForKey方法 public boolean remove(Object o) { return HashMap.this.removeEntryForKey(o) != null; } //实现clear方法,直接调用HashMap的clear方法 public void clear() { HashMap.this.clear(); } }
KeySet类的代码也比较简单易懂,它继承了AbstractSet类,并实现了五个方法。后四个方法都是调用的HashMap中的方法,不再详述,我们主要看第一个方法,关于迭代器的实现。我们知道,增强型for循环,其实内部就是用迭代器来实现的,具体的可以参考这篇博文:java中增强for循环的原理。所以,第一种遍历方法,实际上用到的正是这个迭代器实现方法。下面我们在看newKeyIterator()这个方法源代码:
Iterator<K> newKeyIterator() { return new KeyIterator(); }
这个方法只是new了一个KeyIterator的对象,并把它返回。我们再往下继续找,看下KeyIterator的源代码:
private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } }
KeyIterator 继承了HashIterator,而且只有一个简单的next方法。我们都知道,迭代器接口定义了三个方法,即next(),hasNext()和remove()。next()方法是得到容器里的下一个数据,hasNext()是判断容器里是否还有数据,remove()是删除容器中的数据。KeyIterator 只实现了next()方法,说明其他的方法肯定是在HashIterator中实现的。我们先看HashIterator的实现代码:
private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; //下一个Entry int expectedModCount; //用于多线程中 int index; // 索引 Entry<K,V> current; // 当前entry HashIterator() { expectedModCount = modCount; //构造函数,得到第一个Entry if (size > 0) { //table就是HashMap的底层数组 Entry[] t = table; //找到table中的第一个Entry并赋值给next while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { //实现接口中的hasNext()方法,直接判断next是否等于null return next != null; } //寻找下一个Entry final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); //在构造函数中next已经指向第一个Entry,所以最开始e就是第一个Entry Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); //寻找到下一个Entry并赋值给next if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; //把e返回,如果一直调用此方法,相当于从第一个Entry开始,逐个返回 return e; } //实现接口中的remove()方法,调用HashMap中的removeEntryForKey方法 public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } }
HashIterator定义了四个字段,expectedModCount忽略不管,next存储的是下一个Entry,index储存的是next值在table中索引的下一个值,current是当前Entry。remove和next方法比较简单,就不再详述了,主要是看构造函数和nextEntry方法。构造函数的作用就是找到table中存储的第一个Entry,并赋值给next。因为HashMap往table中存储是不连续的,所以找到table中第一个不为null的值,即是我们要找的。nextEntry()的作用是寻找下一个Entry,寻找方法同构造函数,并且会把当前的Entry返回。
理解了HashIterator之后,我们再回过头来看KeyIterator,它只实现了next方法,next返回的即是当前Entry的key。所以,当我们用增强型for循环,去遍历KeySet的时候,就会调用hasNext()方法和next方法,去遍历table中的所有key。分析到这里,终于弄明白HashMap是如何实现遍历的了。
用values和entrySet遍历的方法与keySet遍历的原理是一样的,只是它们实现迭代器接口的next()方法有区别而已,代码如下:
private final class ValueIterator extends HashIterator<V> { public V next() { //返回的是当前entry的value值 return nextEntry().value; } }
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { //返回的是当前entry return nextEntry(); } }
- Java容器HashMap遍历方法和源代码解析
- Java容器HashMap源代码解析
- Java容器:HashMap和HashSet解析
- Java容器HashSet和LinkedHashSet源代码解析
- java hashMap 遍历方法
- Java容器ArrayList源代码解析
- Java容器LinkedHashMap源代码解析
- Java容器LinkedList源代码解析
- java中HashMap遍历方法
- java中的hashmap遍历方法
- Java的HashMap遍历方法
- java中HashMap遍历方法
- java 遍历hashMap的方法
- java遍历HashMap的方法
- java中HashMap遍历方法
- Java容器-HashMap和HashTable
- Java和JSTL标签中遍历HashMap的方法
- HashMap源代码解析
- kotlin
- 51 nod 1080
- docker环境搭建-啪啪one
- 计算字符个数
- ssh-server服务在修改了端口的情况下,怎么git clone 代码
- Java容器HashMap遍历方法和源代码解析
- 实验吧 简单的sql注入
- Java8 实战学习 方法引用
- Json.net实现方便的Json转C#(dynamic动态类型)对象
- [shell]shell位置变量
- 深入理解Java:注解(Annotation)自定义注解入门
- Java基础编程1—随机生成字母数字组合
- php操作mysql
- 在java后台判断两个文件是否是同一文件