jdk1.8 LinkedHashMap源码分析

本文介绍了LinkedHashMap,它能保存数据添加顺序,继承自HashMap。未重写put方法,但重写了newNode和afterNodeInsertion方法,用before和after维护双向队列。还重写了get方法,若用其实现LRU,需重写removeEldestNode方法判断是否删除最老节点。

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

LinkedHashMap

LinkedHashMap相较于HashMap,其可以保存保存数据添加的顺序,底层实现十分简单,其本身也继承了HashMap

重要属性

/**
     * The head (eldest) of the doubly linked list.
     */
     // 最老的节点
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * The tail (youngest) of the doubly linked list.
     */
     // 最年轻的节点
    transient LinkedHashMap.Entry<K,V> tail;
    /**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     *
     * @serial
     */
     // 按照什么规则保存顺序,是访问顺序还是插入顺序
    final boolean accessOrder;

put

LinkedHashMap并没有重写put方法,而是重写了newNode以及afterNodeInsertion方法

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

newNode方法会在向table中添加新的节点时调用,这里返回的是LinkedHashMap内部的Entry对象
下面看一下内部的Entry
类很简单,继承自HashMap的Node,并且添加了两个属相before和after
这里需要注意区分LinkedHashMap中Entry的before和after不同于HashMap Node中的prev和next
before和after使用来维护LinkedHashMap内部的双向队列的,一个LinkedHashMap内部只有一个双向队列
prev和next是用来维护每个bin上链表的,每个bin上都可能有一个链表

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

下面继续看linkNodeLast

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
	// 首先获取LinkedHashMap内部维护的双向队列的尾节点
    LinkedHashMap.Entry<K,V> last = tail;
    // 将当前节点作为尾节点
    // 并且修改一些引用
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

也就是当调用LinkedHashMap的put方法时,当创建一个新的节点时,会将创建的新节点添加到LinkedHashMap内部的双向队列的尾节点后面
查看HashMap的put方法的源码可知,putVal方法的末尾会执行afterNodeInsertion

// evict代表如果为false那么HashMap处于创建模式,默认是true
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 会根据removeEldestEntry的返回值确定是否删除最老的元素
    // LinkedHashMap默认是不删除最老元素的
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

get

LinekdHashMap重写了HashMap的get方法

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 和HashMap的get方法相比,多了下面这部分代码
    // 如果LinkedHashMap内部的双向链表维护的顺序是节点的访问顺序的话会调用afterNodeAccess
    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) {
   		// p代表当前节点
   		// b代表当前节点在双向队列前面的节点
   		// a代表当前节点在双向队列后面的节点
       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;
   }
}

remove

void afterNodeRemoval(Node<K,V> e) { // unlink\
	// p代表待删除节点,b代表该节点在双向队列前面的节点,a代表该节点在双向队列后面的节点
    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;
}

实现lru

如果使用LinkedHashMap首先lru,那么我们就要重写removeEldestNode方法,在该方法中判断我们此时是否需要删除最老的节点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值