HashMap 核心源码分析(二)

看完HashMap内部的数据结构(数组+链表)和put get的过程,就没再去关注太多。今天脑子里就突然冒出 map 遍历的代码:


for(Map.Entry<K, V> entry : map.entrySet()) {
    
}

以前一直以为entrySet()方法就是 HashMap 内部维护了一个 Set,然后每次 put 的时候就往 set 中添加一个,遍历的 map 的时候就直接拿这个 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已经获取到了,那么keyvalue不就是简单的调下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();
    }
}

最后理一下这几个迭代器之间的关系,看图:

image

posted @ 2022-06-09 14:13  Tailife  阅读(24)  评论(0编辑  收藏  举报