Java集合源码分析之Map(六):LinkedHashMap_一点课堂(多岸学院)

LinkedHashMapHashMap的子类,因此也具有HashMap的诸多特性。不一样的是,LinkedHashMap还维护了一个双向链表,以保证经过Iterator遍历时顺序与插入顺序一致。除此以外,它还支持Access Order,即按照元素被访问的顺序来排序,咱们熟知的LRUCache底层就依赖于此。如下是文档中须要咱们注意的点:java

Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map.node

A special LinkedHashMap(int,float,boolean) constructor is provided to create a linked hash map whose order of iteration is the order in which its entries were last accessed, from least-recently accessed to most-recently (access-order). This kind of map is well-suited to building LRU caches.数组

The removeEldestEntry(Map.Entry) method may be overridden to impose a policy for removing stale mappings automatically when new mappings are added to the map.app

Note, however, that the penalty for choosing an excessively high value for initial capacity is less severe for this class than for HashMap, as iteration times for this class are unaffected by capacity.less

下面咱们就从构造函数和成员变量开始分析其具体实现。ide

构造函数与成员变量

成员变量

在分析成员变量前,咱们先看下其存储元素的结构。函数

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

这个EntryHashMap中被引用过,主要是为了能让LinkedHashMap也支持树化。在这里则是用来存储元素。学习

// 双向链表的头,用做AccessOrder时也是最老的元素
transient LinkedHashMap.Entry<K,V> head;

// 双向链表的尾,用做AccessOrder时也是最新的元素
transient LinkedHashMap.Entry<K,V> tail;

// true则为访问顺序,false则为插入顺序
final boolean accessOrder;

构造函数

关于LinkedHashMap的构造函数咱们只关注一个,其余的都和HashMap相似,只是把accessOrder设置为了false。在上边的文档说过,initialCapacity并无在HashMap中那般重要,由于链表不须要像数组那样必须先声明足够的空间。下面这个构造函数是支持访问顺序的。ui

public LinkedHashMap(int initialCapacity,
                    float loadFactor,
                    boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

重要方法

LinkedHashMap并无再实现一整套增删改查的方法,而是经过复写HashMap在此过程当中定义的几个方法来实现的。对此不熟悉的能够查看文末关于HashMap分析的文章,或者对照HashMap的源码来看。this

插入一个元素

HashMap在插入时,调用了newNode来新建一个节点,或者是经过replacementNode来替换值。在树化时也有两个对应的方法,分别是newTreeNodereplacementTreeNode。完成以后,还调用了afterNodeInsertion方法,这个方法容许咱们在插入完成后作些事情,默认是空实现。

为了方便分析,咱们会对比HashMap中的实现与LinkedHashMap的实现,来摸清它是如何作的。

// HashMap中的实现
Node<K, V> newNode(int hash, K key, V value, Node<K, V> next) {
    return new Node<>(hash, key, value, next);
}

// LinkedHashMap中的实现
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;
}

// HashMap中的实现
Node<K, V> replacementNode(Node<K, V> p, Node<K, V> next) {
    return new Node<>(p.hash, p.key, p.value, next);
}

// LinkedHashMap中的实现
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
    LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
    LinkedHashMap.Entry<K,V> t =
        new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
    transferLinks(q, t);
    return t;
}

// newTreeNode和replacementTreeNode和此相似

经过以上对比,能够发现,LinkedHashMap在新增时,调用了linkNodeLast,再替换时调用了transferLinks。如下是这两个方法的实现。

// 就是将元素挂在链尾
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;
    }
}

// 用dst替换src
private void transferLinks(LinkedHashMap.Entry<K,V> src,
                            LinkedHashMap.Entry<K,V> dst) {  
    LinkedHashMap.Entry<K,V> b = dst.before = src.before;
    LinkedHashMap.Entry<K,V> a = dst.after = src.after;
    if (b == null)
        head = dst;
    else
        b.after = dst;
    if (a == null)
        tail = dst;
    else
        a.before = dst;
}

最后咱们看下afterNodeInsertion作了哪些事情吧:

// evict在HashMap中说过,为false表示是建立阶段
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 不是建立阶段
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // 自动删除最老的元素,也就是head元素
        removeNode(hash(key), key, null, false, true);
    }
}

removeEldestEntry是当想要在插入元素时自动删除最老的元素时须要复写的方法。其默认实现以下:

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

查询

由于要支持访问顺序,因此获取元素的方法和HashMap也有所不一样。下面咱们看下其实现:

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        // 数据被访问,须要将其移动到末尾
        afterNodeAccess(e);
    return e.value;
}

getNode方法是在HashMap中实现的,因此这是包装了一下HashMap的方法,并添加了一个afterNodeAccess,其实现以下:

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // e元素不在末尾
    if (accessOrder && (last = tail) != e) {
        // p是e,b是前一个元素,a是后一个元素
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        // e要放在末尾,因此没有after
        p.after = null;

        // 把e去掉,把b和a接起来
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;

        //把e接在末尾
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

关于LinkedHashMap的分析就到这里了,其余关于Iterator的内容都和Collection是大同小异的,感兴趣的能够去查看相关源码。


【感谢您能看完,若是可以帮到您,麻烦点个赞~】

更多经验技术欢迎前来共同窗习交流: 一点课堂-为梦想而奋斗的在线学习平台 http://www.yidiankt.com/

![关注公众号,回复“1”免费领取-【java核心知识点】] file

QQ讨论群:616683098

QQ:3184402434

想要深刻学习的同窗们能够加我QQ一块儿学习讨论~还有全套资源分享,经验探讨,等你哦! 在这里插入图片描述

相关文章
相关标签/搜索