迭代&遍历
HashMap总共有3种遍历容器的方式:
1.使用Iterator迭代(推荐)
Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
2.将Key和Value分别转成Set和Collection遍历取值
for (String key : hashMap.keySet()){
System.out.println(key);
}
for (String value : hashMap.values()){
System.out.println(value);
}
3.get方式(一般不建议使用)
for (String key : hashMap.keySet()){
System.out.println(hashMap.get(key));
}
这里,我们主要讲使用Iterator迭代器方式,从上面的例子中可以看出,获取迭代器之前要先调用entrySet(),当然keySet()、values()同样可以拿到迭代器,我们只要把entrySet()的方式讲明白了,其他的也是一样的,举一反三嘛,言归正传,接下来开始正式讲解HashMap的迭代原理和源码分析。
让我从一段代码开始讲起,看如下代码:
HashMap<String,String> hashMap = new HashMap<>();
hashMap.put("aaa","aaa_v");
hashMap.put("bbb","bbb_v");
hashMap.put("ccc","ccc_v");
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
System.out.println(entries);
从上面代码的执行结果可以得到结果:[aaa=aaa_v, ccc=ccc_v, bbb=bbb_v],另外,当我们在idea/eclipse中debug代码时,entries也是有值的,但是从源码看,entrySet()方法并没有做任何操作,entries集合中的值怎么来的,我们表示怀疑,或者说entries是不是真的能存储值,接下来慢慢揭开它的面纱
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
//new EntrySet() 创建EntrySet,但是看EntrySet类并没有对应的构造方法,就算追溯到它的父类 AbstractSet<E> 和 AbstractCollection 里面,也只是一个空的构造方法,这就使EntrySet中有值这个事情不成立,那么EntrySet的值到底哪里来的?接着往下看
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
//没有构造方法
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
/**
* Sole constructor. (For invocation by subclass constructors, typically
* implicit.)
*/
protected AbstractSet() {
}
}
其实当我们在debug或System.out.println 打印集合时,会主动调用集合的toString()方法,这就找到突破口了,从EntrySet->AbstractSet->AbstractCollection一路往上找,只有AbstractCollection类中有toString()方法,并且这个方法调用了一个iterator(),这个方法就是入口了
接下来回到HashMpa.EntrySet类中该方法的实现
EntryIterator类代码如下,并没有构造方法,继续往上找到HashIterator
HashIterator类
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两者相同,这是为了使容器在被迭代时其他线程不要让容器的结构有所变化,只结构变了modeCount就会改变,也就不会等于expectedModCount了,就会抛出并发异常
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // 遍历到第1个有效的数组元素
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
//往下遍历节点
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;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
经过以上脉络分析,相信大家对entrySet()和keySet()为什么能拿到值就不奇怪了,实际这俩本身都没有值,只是在toString()方法的时候,通过调用iterator()迭代器来遍历值并输出,也就说从始至终,这两个Set都是空的。