从零开始的Java容器学习(七):LinkedHashMap

433 阅读3分钟

LinkedHashMap的简要介绍

前面我们学了HashMap,考虑这么一种情况,在遍历Map的时候,我们希望遍历的顺序是插入的顺序,这时就要用到LinkedHashMap了。

LinkedHashMap继承自HashMap,其内部维护一个环形双向链表,底层是数组+链表/红黑树,其迭代顺序有插入顺序和访问顺序,默认是插入顺序,如果要使用访问顺序来遍历则需要重写LinkedHashMap的几个方法或者扩展成LRUMap来使用。

LinkedHashMap继承自HashMap,实现了Map接口。

从源码分析LinkedHashMap

成员变量

static class Entry<K,V> extends HashMap.Node<K,V> {//可以看到在HashMap的基础上,维护一个环形双向链表
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}
transient LinkedHashMap.Entry<K,V> head;//双向链表的头
transient LinkedHashMap.Entry<K,V> tail;//双向链表的尾
final boolean accessOrder;//这个是访问顺序,默认是false,即插入顺序,如果是true就是访问顺序

构造方法

public LinkedHashMap() {//看这个就够了,直接调用父类的构造方法,设置accessOrder为false,即默认是插入顺序
    super();
    accessOrder = false;
}
//同HashMap总共有4种构造方法,默认大小为16,负载因子为0.75,以下就不一一列举了

添加

//添加同HashMap的put()方法,但重写了newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {//添加的时候放到双向链表尾上
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}
//afterNodeInsertion也重写了
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

删除

//删除同HashMap,但重写了afterNodeRemoval方法
void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    p.before = p.after = null;
    if (b == null)//p的before为null说明p是头结点
        head = a;
    else
        b.after = a;
    if (a == null)//p的after为null说明p是尾结点
        tail = b;
    else
        a.before = b;
}

查询

public V get(Object key) {//基本同HashMap,但判定是否要accessOrder,如果要的话每次访问后要讲节点放到双向链表最后
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

遍历

public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
    return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
    public final int size()                 { return size; }
    public final void clear()               { LinkedHashMap.this.clear(); }
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new LinkedEntryIterator();
    }
    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 Spliterators.spliterator(this, Spliterator.SIZED |
                                        Spliterator.ORDERED |
                                        Spliterator.DISTINCT);
    }
    public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
        if (action == null)
            throw new NullPointerException();
        int mc = modCount;
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)//从链表的头开始foreach
            action.accept(e);
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }
}
//使用到的迭代器
final class LinkedEntryIterator extends LinkedHashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}

总结

这次的LinkedHashMap跟以前学的容器相比,最大的特点就是:很多方法都直接继承了父类,子类重写了父类的部分方法即可达到多态的效果。LinkedHashMap相比较于HashMap的优点是能保持插入顺序(或者访问顺序,实现LRU算法),接下来将要学习的容器是TreeMap,这个容器不需要占用额外的空间(相比较于LinkedHashMap,需要维护一个环形双向链表),但TreeMap的访问时间开销是(O(logn)),而LinkedHashMap的时间开销是(O(1))。