LinkedHashMap源码剖析

1. 属性

package java.util;

import java.util.function.Consumer;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.io.IOException;

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
    //LinkedHashMap继承自HashMap,所以它已经从HashMap那里继承了与哈希表相关的操作了,那么在LinkedHashMap中可以专注于链表实现的那部分,所以与链表实现相关的属性如下.

    //头指针
    transient LinkedHashMap.Entry<K,V> head;

    //尾指针
    transient LinkedHashMap.Entry<K,V> tail;

    //当为true时,表示链表中键值对的顺序与每个键的插入顺序一致,也就是说重复插入键,也会更新顺序.
    //当为false时(默认值),就是上面所指的1、2、3、4的情况;为true时,就是1、3、4、2的情况.
    final boolean accessOrder;

    //LinkedHashMap的链表节点继承了HashMap的节点,而且每个节点都包含了前指针和后指针,所以这里可以看出它是一个双向链表.
    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);
        }
    }
}
  • LinkedHashMap 是通过哈希表和链表实现的,它通过维护一个链表来保证对哈希表迭代时的有序性,而这个有序是指键值对插入的顺序。另外,当向哈希表中重复插入某个键的时候,不会影响到原来的有序性。也就是说,假设你插入的键的顺序为 1、2、3、4,后来再次插入 2,迭代时的顺序还是 1、2、3、4,而不会因为后来插入的 2 变成 1、3、4、2(但其实我们可以改变它的规则,使它变成 1、3、4、2)。
  • LinkedHashMap 的实现主要分两部分,一部分是哈希表,另外一部分是链表。哈希表部分继承了 HashMap,拥有了 HashMap 那一套高效的操作,所以我们要看的就是 LinkedHashMap 中链表的部分,了解它是如何来维护有序性的。
  • LinkedHashMap 的大致实现如下图所示,当然链表和哈希表中相同的键值对都是指向同一个对象,这里把它们分开来画只是为了呈现出比较清晰的结构。

LinkedHashMap的结构

2. afterNodeAccess 方法

//HashMap中存在的三个空方法:
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
//这三个方法表示的是在访问、插入、删除某个节点之后,进行一些处理,它们在LinkedHashMap都有各自的实现.
//LinkedHashMap正是通过重写这三个方法来保证链表的插入、删除的有序性.
//afterNodeAccess方法的意思是把当前节点e移至链表的尾部.
//因为使用的是双向链表,所以在尾部插入可以以O(1)的时间复杂度来完成.
//并且只有当accessOrder设置为true时,才会执行这个操作.
//在HashMap的putVal方法中,就调用了这个方法.
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    //当accessOrder的值为true,且e不是尾节点.
    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;
    }
}

3. afterNodeInsertion 方法

//afterNodeInsertion方法是在哈希表中插入了一个新节点时调用的,它会把链表的头节点删除掉,删除的方式是通过调用HashMap的removeNode方法.
//通过afterNodeInsertion方法和afterNodeAccess方法,是不是就可以简单的实现一个基于最近最少使用(LRU)的淘汰策略了?当然,我们还要重写removeEldestEntry方法,因为它默认返回的是false.
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);
    }
}

4. afterNodeRemoval 方法

//这个方法是当HashMap删除一个键值对时调用的,它会把在HashMap中删除的那个键值对一并从链表中删除,保证了哈希表和链表的一致性.
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)
        head = a;
    else
        b.after = a;
    if (a == null)
        tail = b;
    else
        a.before = b;
}

5. get 方法

//LinkedHashMap的get方法很简单,因为它调用的是HashMap的getNode方法来获取结果的.
//如果你把accessOrder设置为true,那么在获取到值之后,还会调用afterNodeAccess方法.
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;
}

6. put 方法和 remove 方法

  • 在 LinkedHashMap 的源码中没有找到 put 方法,这就说明了它并没有重写 put 方法,所以我们调用的 put 方法其实是 HashMap 的 put 方法。因为 HashMap 的 put 方法中调用了 afterNodeAccess 方法和 afterNodeInsertion 方法,已经足够保证链表的有序性了,所以它也就没有重写 put 方法了。remove 方法也是如此。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值