HashMap 核心源码分析(二)
看完HashMap
内部的数据结构(数组+链表)和put get
的过程,就没再去关注太多。今天脑子里就突然冒出 map 遍历的代码:
for(Map.Entry<K, V> entry : map.entrySet()) {
}
以前一直以为entrySet()
方法就是 HashMap 内部维护了一个 Set
找到 entrySet() 代码
public Set<Map.Entry<K,V>> entrySet() {
return entrySet0();
}
private Set<Map.Entry<K,V>> entrySet0() {
Set<Map.Entry<K,V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}
返回了一个叫 entrySet 的东西,看看声明的位置
private transient Set<Map.Entry<K,V>> entrySet = null;
的确维护了一个用来”存储“ entry 的 set,但是接下来的事情就是我死活没找到往这个 entrySet 中塞 entry 的动作,莫名其妙。既然没有 add 操作,那就看看new EntrySet()
是个什么鬼
找到EntrySet
源码
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K,V> e = (Map.Entry<K,V>) o;
Entry<K,V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o) != null;
}
public int size() {
return size;
}
public void clear() {
HashMap.this.clear();
}
}
这个内部类中貌似也没太多东西,但是你看到iterator()
方法就应该知道遍历EntrySet
的关键就是返回的这个迭代器中(可以参考foreach 那点事),那就看看newEntryIterator()
方法:
Iterator<Map.Entry<K,V>> newEntryIterator() {
return new EntryIterator();
}
继续看EntryIterator
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
return nextEntry();
}
}
next()
方法是迭代器中获取数据的地方,那就看看nextEntry()
代码
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K,V> current; // current entry
HashIterator() {
expectedModCount = modCount;
// 这里是获取第一entry
if (size > 0) { // advance to first entry
Entry[] t = table;
// idnex 默认为0,也就是从数组下标0的位置开始
// (这里你要知道 HashMap 的数据结构,数组+链表)
// 如果 next = t[0] 不为null,那就找到了第一个entry,结束跳出循环
// 如果 next = t[0] 如果为null,开始找数组下标为1的位置
// 以此类推,直到找到数组上第一个entry
// 每循环一次,index++
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// next 就是当前entry
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
// 如果链表上当前entry的下一个entry为null,那么继续找下一个需要返回的entry
if ((next = e.next) == null) {
Entry[] t = table;
// 逻辑跟上面构造方法里一致
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
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;
}
}
这里我贴出了nextEntry()
所在类的所有代码,当然核心还是nextEntry()
和HashIterator
类的构造方法。代码里给出了简单的注释,大概过程就是循环数组每个索引位置上的链表。如果你清楚HashMap
的数据结构,看这里代码的时候脑子中会有很强的画面感。
到这里就很清楚了迭代entrySet
的具体实现,它其实操作的还是map中最原始的那个Entry<K,V>[] table
,再回头看看entrySet()
方法的声明:
/**
* Returns a {@link Set} view of the mappings contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa.
*
* @return a set view of the mappings contained in this map
*/
public Set<Map.Entry<K,V>> entrySet() {
return entrySet0();
}
注释中写的很清楚:
这个方法返回的 map 的一个视图,对 map 的改变都会反映到这个视图上面,反之亦然
既然知道了entrySet()
的流程,不妨再顺便看看keySet()
和values()
方法。其实想想,entry
已经获取到了,那么key
和value
不就是简单的调下entry.getKey()
和entry.getValue()
么,源码也确实如此:
private final class ValueIterator extends HashIterator<V> {
public V next() {
return nextEntry().value;
}
}
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
最后理一下这几个迭代器之间的关系,看图: