Java源码阅读之LinkedHashMap

本文深入剖析了LinkedHashMap的数据结构和工作原理,介绍了它是如何基于链表实现有序的HashMap,并且能够保持元素的插入顺序或访问顺序。同时探讨了LinkedHashMap与HashMap的区别,包括构造函数、插入元素、访问元素等关键操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

顾名思义,LinkedHashMap是基于链表实现的有序的HashMap。LinkedHashMap内部维护了一个双向链表,链表定义了其元素迭代的顺序,通常的顺序就是元素被插入到LinkedHashMap的顺序。
LinkedHashMap摒弃了HashMap的乱序问题,同时也不像TreeMap一样有较高的开销。LinkedHashMap可以用于生成和原始Map顺序一样的拷贝,它提供了一个可以传入任意非空Map的构造函数。
LinkedHashMap提供两种迭代顺序:最后访问顺序和元素插入顺序(由LinkedHashMap内部属性accessOrder决定,为true时,迭代顺序为最后访问顺序,否则为元素插入顺序,默认为fasle)。
LinkedHashMap实例调用put()putIfAbsent()get()getOrDefault()compute()computeIfAbsent()computeIfPresent()merge()方法将导致访问相应的元素。 putAll()方法为指定Map中的每个KV对生成一次访问,访问顺序就是指定Map的原始的迭代器迭代顺序。除了上述方法,其他方法不会生成访问。
LinkedHashMap实现了Map接口的全部操作,允许null,由于需要维持链表顺序,其基本操作的性能相对HashMap稍差。并且如果定义了最后访问顺序排序的LinkedHashMap,其get()操作会对其结构作出修改。此外,LinkedHashMap是线程不安全的。

由于LinkedHashMap继承了HashMap,我们这里仅讲述一下不同的地方,相同的操作本文将略过。

构造函数

LinkedHashMap的Entry继承自HasHMap.Node<K,V>,在Entry中增加了到前后节点的reference,用来记录LinkedHashMap实例的迭代顺序。并且记录了双向链表的head和tail,用来按顺序迭代其元素。

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);
    }
}

除了HashMap指定的三个构造函数,LinkedHashMap提供了第四个构造函数,他可以用来指定LinkedHashMap的迭代顺序是按照访问顺序还是插入顺序。

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

不指定accessOrder时,默认其值为false,即按照插入顺序迭代。下面两节分别讲述一下插入元素和访问元素是的链表操作:

插入元素

LinkedHashMap覆盖了HashMap的newNode()方法,增加了将新节点link到双向链表的尾部的操作:

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;
    }
}

这个操作完成了元素插入顺序的记录。

同样,删除某个KV对时,LinkedHashMap覆盖的方法afterNodeRemoval(node)会在删除节点的removeNode()后调用,这个操作会生出迭代顺序链表中的相应节点。

void afterNodeRemoval(Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    p.before = p.after = null;
    if (b == null)
        head = a;
    else
        b.after = a;
    if (a == null)
        tail = b;
    else
        a.before = b;
}

此外插入一个映射后,如果这个Map被定义为创建模式(evictfalse),而且要求删除年龄最大的映射时,需要将将链表的头部节点删除。不过当前的实现里,evict始终为true,而且removeEldestEntry()始终返回fasle,即jdk的LinkedHashMap和HashMap实现中,Map实例始终都不会是创建模式而去不删除年龄最老的映射。

void afterNodeInsertion(boolean evict) {
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

访问元素

访问某个节点后,会回调afterNodeAccess()方法,将访问的节点移动到链表的尾部,访问某个节点的操作包括:put()replace()computeIfAbsent()computeIfPresent()compute()merge(),以及get()getOrDefault()

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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值