集合源码-LinkedHashMap

本文详细介绍了LinkedHashMap的数据结构和实现原理,包括其如何在HashMap的基础上通过双向链表保持元素的插入或访问顺序,以及如何通过覆盖removeEldestEntry方法实现LRU缓存策略。

1、简介

LinkedHashMap 继承自 HashMap,在 HashMap 基础上,内部维护了一个双向链表,
解决HashMap不能随时保持遍历顺序和插入顺序一致的问题。除此之外,LinkedHashMap 对访问顺序也提供了相关支持,可以用来实现LRU缓存策略。LinkedHashMap的实现很多方法直接继承自 HashMap,仅为维护双向链表覆写了部分方法。

2、继承关系

LinkedHashMap继承HashMap,拥有HashMap的所有特性,并且额外增加的按一定顺序访问的特性。LinkedHashMap 在HashMap结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。其结构可能如下图:

在这里插入图片描述
在这里插入图片描述

3、源码分析

3.1 属性

	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);
        }
    }
    
	// 双向链表头节点
  	transient LinkedHashMap.Entry<K,V> head;

   	// 双向链表尾节点
    transient LinkedHashMap.Entry<K,V> tail;

 	// 是否按访问顺序排序
 	// 如果为false则按插入顺序存储元素,如果是true则按访问顺序存储元素
    final boolean accessOrder;

3.2 构造方法

	// 调用父类HashMap,默认保持插入顺序
 	public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    public LinkedHashMap() {
        super();
        accessOrder = false;
    }
    
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

3.3 HashMap三个回调方法

	// HashMap
    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }

	// LinkedHashMap
	// HashMap在putVal()插入后调用,默认值为true 
	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);
        }
    }
    // 移除最近最少被访问条件之一,通过覆盖此方法可实现LRU 缓存
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
	
	// 调用这个方法把访问到的节点移动到双向链表的末尾
	void afterNodeAccess(Node<K,V> e) { 
        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 接在链表的最后
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }
    
    // 在节点被删除之后调用的方法
    // 把节点从双向链表中删除的方法
	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;
    }

4、总结

(1)LinkedHashMap继承自HashMap,具有HashMap的所有特性;
(2)LinkedHashMap内部维护了一个双向链表存储所有的元素;
(3)LinkedHashMap可以用来实现LRU缓存淘汰策略;

  • 例:
class LRU<K, V> extends LinkedHashMap<K, V> {

    // 保存缓存的容量
    private int capacity;

    public LRU(int capacity, float loadFactor) {
        super(capacity, loadFactor, true);
        this.capacity = capacity;
    }

    /**
    * 重写removeEldestEntry()方法设置何时移除旧元素
    * @param eldest
    * @return
    */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // 当元素个数大于了缓存的容量, 就移除元素
        return size() > this.capacity;
    }
}
<think>嗯,用户让我介绍一下Java集合中的LinkedHashMap,还要结合源码分析。首先,我得回忆一下LinkedHashMap的基本概念和它在Java集合框架中的位置。LinkedHashMapHashMap的子类,所以它应该具有HashMap的所有特性,比如允许null键值,非同步等等。但它的特殊之处在于维护了一个双向链表,用来保持插入顺序或者访问顺序。这可能就是用户想深入了解的地方。 接下来,我需要考虑用户的需求。他们可能已经对HashMap有一定的了解,现在想进一步学习LinkedHashMap的工作原理和内部实现。所以,我应该先对比HashMap和LinkedHashMap的区别,强调LinkedHashMap的有序性。然后,可能需要讲解LinkedHashMap的数据结构,比如Entry节点的结构,双向链表是如何维护的。 然后,源码分析部分需要深入LinkedHashMap的关键方法,比如put、get、remove,以及如何维护访问顺序。比如,在HashMap的put方法中,LinkedHashMap可能重写了某些方法,比如afterNodeAccess和afterNodeInsertion,这些钩子方法的作用是什么。另外,访问顺序模式下,最近访问的节点会被移动到链表末尾,这在实现LRU缓存时很有用,这个点可能也需要提到。 用户可能还想知道LinkedHashMap如何实现迭代顺序的有序性。这时候需要分析迭代器的实现,可能是通过遍历双向链表而不是像HashMap那样遍历桶数组。此外,构造方法中的accessOrder参数的作用,以及如何影响元素顺序,这也是关键点。 另外,要注意到LinkedHashMap在实现LRU缓存时的应用。当accessOrder设置为true时,每次访问元素都会调整顺序,当达到容量时,会移除最老的元素。这部分可能需要结合removeEldestEntry方法的源码,说明如何扩展这个方法来控制缓存策略。 在结构上,应该先介绍LinkedHashMap的基本概念,然后数据结构,接着关键源码分析,最后应用场景。可能还需要给出一个简单的例子,比如如何用LinkedHashMap实现LRU缓存,这样用户更容易理解。 需要确认是否覆盖了主要的方法和内部类,比如Entry的结构,是否有新增的字段如before和after。同时,要注意源码的版本是否正确,假设是Java 8的源码,因为不同版本可能有差异。例如,在Java 8中,HashMap引入了树化结构,但LinkedHashMap仍然使用双向链表,这部分是否需要提及? 在讲解源码时,可能需要用代码块展示关键部分,比如Entry类的定义,以及afterNodeAccess方法的具体实现。但要注意不要粘贴过多代码,而是选择关键部分进行解释。 另外,用户可能对LinkedHashMap的性能感兴趣,比如与HashMap相比,插入、删除、访问的时间复杂度是否有变化。虽然LinkedHashMap维护了双向链表,但大多数操作的时间复杂度仍然是O(1),因为链表操作是常量时间的。 最后,总结LinkedHashMap的特点和适用场景,帮助用户理解何时应该选择LinkedHashMap而不是其他集合类。例如,需要保持插入顺序或访问顺序,或者需要实现简单的LRU缓存时。 需要确保解释清晰,逻辑连贯,避免技术术语过多导致理解困难。同时,结合源码分析时要抓住重点,展示关键代码部分并解释其作用,帮助用户理解内部机制。</think>### Java 集合LinkedHashMap 解析与源码分析 `LinkedHashMap` 是 `HashMap` 的子类,在保留 `HashMap` 高效查询能力的基础上,**通过双向链表维护元素的插入顺序或访问顺序**。它是实现 `LRU` 缓存策略的理想数据结构。 -- #### 核心特性 1. **有序性**:默认按插入顺序迭代,可配置为按访问顺序(最近访问的元素排在末尾)。 2. **性能**:查询、插入、删除的时间复杂度为 `O(1)`,略慢于 `HashMap`(需维护链表)。 3. **继承结构**:继承 `HashMap`,复用哈希表逻辑,重写链表维护相关方法。 --- ### 数据结构与关键源码 #### 1. Entry 结构扩展 `LinkedHashMap.Entry` 继承 `HashMap.Node`,**新增 `before` 和 `after` 指针**,形成双向链表。 ```java 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); } } ``` -- #### 2. 关键属性 - **head/tail**:维护链表的头尾节点。 - **accessOrder**:`true` 表示按访问顺序排序,`false`(默认)表示按插入顺序。 ```java transient LinkedHashMap.Entry<K,V> head; // 链表头(最老元素) transient LinkedHashMap.Entry<K,V> tail; // 链表尾(最新元素) final boolean accessOrder; // 顺序控制标志 ``` --- ### 核心方法源码分析 #### 1. 插入与链表维护 `LinkedHashMap` **未重写 `put()`**,直接复用 `HashMap` 的逻辑,但通过重写以下钩子方法维护链表: ```java // 插入新节点后,维护链表结构(可能触发删除最老元素) void afterNodeInsertion(boolean evict) { LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { removeNode(first.key, null, false, true); // 删除头节点(LRU 逻辑) } } // 是否删除最老元素(默认返回 false,需子类重写实现 LRU) protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; } ``` --- #### 2. 访问顺序调整 当 `accessOrder=true` 时,调用 `get()` 或 `getOrDefault()` 会触发 `afterNodeAccess()`,将节点移到链表末尾。 ```java public V get(Object key) { Node<K,V> e; if ((e = getNode(key)) == null) return null; if (accessOrder) afterNodeAccess(e); // 调整链表顺序 return e.value; } // 将节点移动到链表尾部(最近访问) void afterNodeAccess(Node<K,V> e) { LinkedHashMap.Entry<K,V> last = tail; if (accessOrder && last != e) { LinkedHashMap.Entry<K,V> p = (Entry<K,V>)e; LinkedHashMap.Entry<K,V> 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. 迭代器实现 `LinkedHashMap` 的迭代器直接遍历双向链表,而非哈希桶数组,**保证顺序性**。 ```java abstract class LinkedHashIterator { LinkedHashMap.Entry<K,V> next = head; // 从头节点开始 LinkedHashMap.Entry<K,V> current; // ... public final boolean hasNext() { return next != null; } final LinkedHashMap.Entry<K,V> nextNode() { LinkedHashMap.Entry<K,V> e = next; // ... current = e; next = e.after; // 通过 after 指针遍历 return e; } } ``` -- ### 应用场景:实现 LRU 缓存 通过重写 `removeEldestEntry()`,限制最大容量并删除最久未使用的元素。 ```java public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int maxCapacity; public LRUCache(int maxCapacity) { super(maxCapacity, 0.75f, true); // accessOrder=true this.maxCapacity = maxCapacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > maxCapacity; // 容量超限时删除最老元素 } } ``` --- ### 总结 - **有序性**:双向链表维护插入/访问顺序。 - **扩展性**:通过钩子方法(如 `afterNodeAccess`)实现行为定制。 - **高效性**:在 `HashMap` 基础上以少量性能代价换取顺序控制。 - **应用场景**:需要顺序迭代或实现类似 LRU 的缓存策略时优先选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值