LinkedHashMap源码解析和设计思路

一、类结构源码

HashMap 是无序的,TreeMap 可以按照 key 进行排序,LinkedHashMap 本身是继承 HashMap 的,所以它拥有 HashMap 的所有特性。
另外还提供二个特性

  • 按照插入顺序进行访问;
  • 实现了访问最少最先删除功能,其目的是把很久都没有访问的 key 自动删除
public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{
    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);
        }
    }

    private static final long serialVersionUID = 3801124242820219131L;

    transient LinkedHashMap.Entry<K,V> head;

    transient LinkedHashMap.Entry<K,V> tail;

    final boolean accessOrder;
  1. LinkedHashMap继承关系

    • LinkedHashMap继承自HashMap,同时实现了Map接口。这意味着它除了继承了HashMap的所有功能外,还额外添加了一些自身的特性。
  2. 内部Entry类

    • EntryLinkedHashMap的一个静态内部类,它继承自HashMap.Node
    • 相比于HashMap.Node,Entry多了两个额外的引用beforeafter,用于维护双向链表的结构。
  3. head和tail引用

    • head引用指向双向链表的表头(最老的Entry)。
    • tail引用指向双向链表的表尾(最新的Entry)。
    • 通过这两个引用,可以高效地访问链表的头部和尾部。
  4. accessOrder标志位

    • accessOrder是一个final的boolean类型变量。
    • accessOrder为true时,表示按访问顺序(access-order)维护Entry的顺序;当为false时,表示按插入顺序(insertion-order)维护Entry的顺序。
  5. 实现原理

    • LinkedHashMapHashMap的基础上,额外维护了一个双向链表,用来保存Entry的插入或访问顺序。
    • 当新的Entry被插入时,会被追加到链表的尾部;当某个Entry被访问时(get或put操作),会被移动到链表的尾部。
    • 这种方式使得LinkedHashMap可以高效地维护Entry的插入或访问顺序,而不需要额外的排序操作。

二、如何初始化

  1. LinkedHashMap(int initialCapacity)
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity); // 调用HashMap构造器指定初始容量
    accessOrder = false; // 默认按插入顺序维护Entry顺序
}

该构造函数创建一个按插入顺序维护Entry顺序的LinkedHashMap实例,初始容量由initialCapacity参数指定。

  1. LinkedHashMap()
public LinkedHashMap() {
    super(); // 调用HashMap无参构造器
    accessOrder = false; // 默认按插入顺序维护Entry顺序
}

该无参构造函数创建一个按插入顺序维护Entry顺序的LinkedHashMap实例,使用默认的初始容量(16)和加载因子(0.75)。

  1. LinkedHashMap(Map<? extends K, ? extends V> m)
public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super(); // 调用HashMap无参构造器
    accessOrder = false; // 默认按插入顺序维护Entry顺序
    putMapEntries(m, false); // 将给定Map的映射关系添加到当前Map
}

该构造函数创建一个按插入顺序维护Entry顺序的LinkedHashMap实例,并将给定Map中的所有映射关系添加到新创建的LinkedHashMap中。它使用默认的初始容量和加载因子。

  1. LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor); // 调用HashMap构造器指定初始容量和加载因子
    this.accessOrder = accessOrder; // 设置访问顺序标志
}

该构造函数创建一个LinkedHashMap实例,初始容量由initialCapacity指定,加载因子由loadFactor指定。accessOrder参数决定了是按插入顺序还是按访问顺序维护Entry的顺序。

无论使用哪个构造函数,LinkedHashMap都会继承HashMap的特性,如自动扩容、桶的链表存储等。

三、put新增源码

  1. 看如图put是调用HashMap的方法
    在这里插入图片描述
  2. HashMap中调用LinkedHashMap新增节点的newNode实现方法
    在这里插入图片描述
    3.ewNode/newTreeNode 方法,控制新增节点追加到链表的尾部,这样每次新节点都追加到尾部,即可保证插入顺序了
    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;
    }
    
    //新节点插入到双向链表的尾部,并更新相应的head和tail引用以及节点之间的before和after引用。
	private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    // 获取当前链表的尾节点
    LinkedHashMap.Entry<K,V> last = tail;

    // 将新节点设置为新的尾节点
    tail = p;

    // 如果原来的链表为空,将新节点也设置为头节点
    if (last == null)
        head = p;
    else {
        // 将新节点的before指针指向原来的尾节点
        p.before = last;
        // 将原来尾节点的after指针指向新节点
        last.after = p;
    }
}

四、get查询

就是经常访问的元素会被追加到队尾,这样不经常访问的数据自然就靠近队头。就可以通过设置删除策略,比如当 Map 元素个数大于多少时,把头节点删除

public V get(Object key) {
    Node<K,V> e;
    // 调用HashMap的getNode方法获取与给定键关联的节点
    if ((e = getNode(hash(key), key)) == null)
        return null; // 如果节点不存在,直接返回null

    // 如果设置了accessOrder为true,表示按访问顺序维护Entry顺序
    if (accessOrder)
        // 调用afterNodeAccess方法,将被访问的节点移动到链表尾部
        afterNodeAccess(e);

    return e.value; // 返回节点的值
}

//afterNodeAccess方法实现了将被访问的节点移动到双向链表尾部的功能,从而维护了Entry的访问顺序
void afterNodeAccess(Node<K,V> e) {
    LinkedHashMap.Entry<K,V> last;
    // 如果设置了accessOrder为true,并且被访问的节点不是尾节点
    if (accessOrder && (last = tail) != e) {
        // 将节点e转换为LinkedHashMap.Entry类型
        LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;
        // 记录被访问节点的前一个和后一个节点
        LinkedHashMap.Entry<K,V> b = p.before, a = p.after;

        // 1. 将被访问节点从双向链表中移除
        p.after = null;
        if (b == null)
            head = a; // 如果被访问节点是头节点,将head指向a
        else
            b.after = a; // 否则,将b的after指向a
        if (a != null)
            a.before = b; // 将a的before指向b
        else
            last = b; // 如果a为null,说明被访问节点是尾节点,将last指向b

        // 2. 将被访问节点重新插入到双向链表的尾部
        if (last == null)
            head = p; // 如果链表为空,将head和tail都指向p
        else {
            p.before = last;
            last.after = p;
        }
        tail = p; // 将tail指向被访问节点p

        // 增加modCount计数器,表示链表结构被修改
        ++modCount;
    }
}

get方法的主要逻辑是通过HashMap的getNode方法获取节点,而LinkedHashMap额外增加了对accessOrder的支持。当accessOrder为true时,每次访问一个节点,都会将该节点移动到双向链表的尾部,从而实现了按访问顺序维护Entry的功能。

五、最少访问数据删除策略如何实现?

  1. map 中元素个数大于 3 时,把队头的元素删除,当 put(1, 1) 执行的时候,正好把队头的 10 删除,这个体现了达到我们设定的删除策略时,会自动的删除头节点。
public class test {

    public static void main(String[] args) {
        testAccessOrder();
    }

    public static void testAccessOrder() {
        // 新建 LinkedHashMap
        LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(4, 0.75f, true) {
            {
                put(10, 10);
                put(9, 9);
                put(20, 20);
                put(1, 1);
            }

            @Override
            // 覆写了删除策略的方法,我们设定当节点个数大于 3 时,就开始删除头节点
            protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
                return size() > 3;
            }
        };
		//{9=9, 20=20, 1=1}
        System.out.println(map);

    }
}

在这里插入图片描述
2. 执行 put 方法时,队头元素被删除,LinkedHashMap 本身是没有 put 方法实现,调用的是 HashMap 的 put 方法,但 LinkedHashMap 实现了 put 方法中的调用 afterNodeInsertion 方法,实现了删除策略
在这里插入图片描述
在这里插入图片描述

// 删除很少被访问的元素,被 HashMap 的 put 方法所调用
void afterNodeInsertion(boolean evict) { 
    // 得到元素头节点
    LinkedHashMap.Entry<K,V> first;
    // removeEldestEntry 来控制删除策略,如果队列不为空,并且删除策略允许删除的情况下,删除头节点
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // removeNode 删除头节点
        removeNode(hash(key), key, null, false, true);
    }
}
  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java语录精选

你的鼓励是我坚持下去的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值