写在前面的话
本文针对的是Java1.6进行的源码分析,与其他版本可能存在差异。
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循环,其实内部就是用迭代器来实现的。所以,第一种遍历方法,实际上用到的正是这个迭代器实现方法。下面我们在看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();
}
}