数据结构Map-----LinkedHashMap源码解析

基于LinkedHashMap实现FIFO(先进先出)淘汰算法 package com.bingo.frame; import java.util.LinkedHashMap; /** * b.基于LinkedHashMap实现FIFO(先进先出)淘汰算法 * @author bingo */ public class BingoTest { class SimpleFIFOCache extends LinkedHashMap<Object, Object> { private int maxCap; public Simpl 阅读详情

LinkedHashMap:重要的成员变量以及构造方法

public class LinkedHashMap<K, V> extends HashMap<K, V> {

    /**
     * 头节点,这是一个很重要的节点,用来维护一个双向循环链表
     */
    transient LinkedEntry<K, V> header;

    /**
     * True if access ordered, false if insertion ordered.
     * 关系到LinkedHashMap的排序方式,也决定了那个元素会是最近最少使用的元素。
     * 稍后我会详细的说一下true和false对于排序方式的区别。
     */
    private final boolean accessOrder;

    /**
     * 无参构造,注意accessOrder 默认是false
     */
    public LinkedHashMap() {
        init();
        accessOrder = false;
    }

    /**
     *最终调用了3个参数的构造方法
     */
    public LinkedHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     *最终调用了3个参数的构造方法
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, false);
    }

    /**
     *调用了super也就是父类HashMap的2参构造,同时跟无参构造一样调用了init方法和对于accessOrder的赋值方法
     */
    public LinkedHashMap(
            int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor);
        init();
        this.accessOrder = accessOrder;
    }

    //初始化了个LinkedEntry实例header
    @Override void init() {
        header = new LinkedEntry<K, V>();
    }  
}

LinkedEntry 和 HashMapEntry :

/**
 * LinkedEntry
 */
static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
    LinkedEntry<K, V> nxt;
    LinkedEntry<K, V> prv;

    /** Create the header entry */
    LinkedEntry() {
        super(null, null, 0, null);
        nxt = prv = this;
    }

    /** Create a normal entry */
    LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
                LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
        super(key, value, hash, next);
        this.nxt = nxt;
        this.prv = prv;
    }
}

/**
 * HashMapEntry
 */
static class HashMapEntry<K, V> implements Entry<K, V> {
       final K key;
       V value;
       final int hash;
       HashMapEntry<K, V> next;

       HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
           this.key = key;
           this.value = value;
           this.hash = hash;
           this.next = next;
       }
 }

通过对比我们发现LinkedEntry的构造方法中调用了super(key, value, hash, next); 同时还增加了两个新的特有属性 nxt 和 prv。如果来记得我之前关于LinkedList的源码解析的那篇文章的话,就大致可以猜到这两个属性是干什么的,没错就是引用元素前后其他元素的,也就是指针。

这里写图片描述

还记得上一篇中我使用★来包裹的函数吗,这里我也贴出来作比较,来看一看LinkedHashMap和HashMap的区别

/**
 * HashMap
 */
void preModify(HashMapEntry<K, V> e) { }


/**
 * LinkedHashMap
 */
@Override 
void preModify(HashMapEntry<K, V> e) {
    if (accessOrder) {
        makeTail((LinkedEntry<K, V>) e);
    }
}

这个方法是出现在put方法中的,LinedkHashMap并没有自己实现put方法,而是依然使用的父类HashMap的put方法,我们可以发现影响两者实现不同的关键在于accessOrder的值上面,如果是false那么两者的实现可以说是完全一样的,但是如果是true的话LinkedHashMap就会执makeTail函数。而我们知道使用无参构造创建LinkedHashMap的时候accessOrder默认值就是false,所以我们先暂时搁置不去看这个函数

/**
 * HashMap
 */
void postRemove(HashMapEntry<K, V> e) { }

/**
 * LinkedHashMap
 */
@Override 
void postRemove(HashMapEntry<K, V> e) {
    LinkedEntry<K, V> le = (LinkedEntry<K, V>) e;
    le.prv.nxt = le.nxt;
    le.nxt.prv = le.prv;
    le.nxt = le.prv = null; // Help the GC (for performance)
}

这个方法是出现在remove方法中的,LinedkHashMap也没有自己实现remove方法,而是依然使用的父类HashMap的remove方法,这是只是操纵了LinedEntry特有的两个属性nxt和prv的指针引用,学过链表结构的想必都知道这个几行代码就是将元素e的前后元素的指针重新进行了赋值,同时将自身的引用断开置空。基本过程就如下图

这里写图片描述

/**
 * HashMap
 */
void addNewEntry(K key, V value, int hash, int index) {
    table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}

/**
 * LinkedHashMap
 */
@Override 
void addNewEntry(K key, V value, int hash, int index) {
    LinkedEntry<K, V> header = this.header;

    // Remove eldest entry if instructed to do so.
    LinkedEntry<K, V> eldest = header.nxt;
    if (eldest != header && removeEldestEntry(eldest)) {
        remove(eldest.key);
    }

    // Create new entry, link it on to list, and put it into table
    LinkedEntry<K, V> oldTail = header.prv;
    LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
            key, value, hash, table[index], header, oldTail);
    table[index] = oldTail.nxt = header.prv = newTail;
}

这个方法也是出现在put方法中,如果大家细心的话,可以发现LinedkHashMap的addNewEntry做了和HashMap的addNewEntry相同的事情。同时还做了一些HashMap没有做的事情

/**
 * HashMap
 */
table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);

/**
 * LinkedHashMap
 */
newTail = new LinkedEntry<K,V>(key, value, hash, table[index], header, oldTail);
table[index] = oldTail.nxt = header.prv = newTail;

总结:刨除header, oldTail就发现其实逻辑是一样的,那么header, oldTail额外干了什么呢,还得记得LinkedEntry的构造吗,去看看你就会想起来了,通过代码可以看出,它维护了一个以header为头节点的双向循环链表,通过上面的几个个比较,到这里想必大家现在应该有点知道了为什么LinkedEntry比HashMapEntry多出的两个属性nxt和pre到底用来干什么的。也就是说LinkedHashMap不但完全跟HashMap一样维护了一个散列链表,同时跟LinkedList一样也维护了一个双向循环链表,所以LinkedHashMap真的是名副其实啊也就是说LinkedHashMap不但像HashMap那样维护了一个Entry[]数组,同时也像LinkedList那样有一个以header为头结点的链表。

Entry[] 数组 table

这里写图片描述

LinekedEntry header为头结点的链表
这里写图片描述

我们知道LinkedHashMap的增加和删除调用的是父类HashMap的方法,那么我们来看一下get方法有什么区别

  • 查找
/**
 * HashMap
 */
public V get(Object key) {
    if (key == null) {
        HashMapEntry<K, V> e = entryForNullKey;
        return e == null ? null : e.value;
    }

    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
            e != null; e = e.next) {
        K eKey = e.key;
        if (eKey == key || (e.hash == hash && key.equals(eKey))) {
            return e.value;
        }
    }
    return null;
}

/**
 * LinkedHashMap
 */
@Override 
public V get(Object key) {
    /*
     * This method is overridden to eliminate the need for a polymorphic
     * invocation in superclass at the expense of code duplication.
     */
    if (key == null) {
        HashMapEntry<K, V> e = entryForNullKey;
        if (e == null)
            return null;
        if (accessOrder)
            makeTail((LinkedEntry<K, V>) e);
        return e.value;
    }

    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
            e != null; e = e.next) {
        K eKey = e.key;
        if (eKey == key || (e.hash == hash && key.equals(eKey))) {
            if (accessOrder)
                makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }
    }
    return null;
}

对比发现如果accessOrder如果是false是完全没有区别的,而且我们通过之前对比也多次见到了accessOrder这个变量的身影,可以说这个属性的值决定了很多事情,那么如accessOrder为true的话它都干了些什么呢?那么我们来看makeTail函数究竟做了些什么?

/**
 * 是不是看起来不是那么复杂,而且十分眼熟,好像就是链表指针相关的各种操作,你链我啊 我链你的
 */
private void makeTail(LinkedEntry<K, V> e) {
    // Unlink e
    e.prv.nxt = e.nxt;
    e.nxt.prv = e.prv;

    // Relink e as tail
    LinkedEntry<K, V> header = this.header;
    LinkedEntry<K, V> oldTail = header.prv;
    e.nxt = header;
    e.prv = oldTail;
    oldTail.nxt = header.prv = e;
    modCount++;
}

实际上就是当我们get获取到元素的时候重新调整了该元素在链表中的位置。
相信还有一种数据结构大家不陌生吧就是队列,我们知道LinkedList也是实现Queue接口的

public class LinkedList<E> extends AbstractSequentialList<E> implements
        List<E>, Deque<E>, Queue<E>, Cloneable, Serializable {

所以LindedList其实就是队列的一种,而我们知道队列区别于栈结构的,队列是先进先出,队尾进入,队头出,而栈结构是先进后出的,栈顶进入,栈顶出。

看图我们就可以发现header头节点的nxt所指向的元素就是队头,而header头结点pre指向的元素就是队尾。

这里写图片描述

而当accessOrder为false的时候,是不执行makeTail函数的,LindedHashMap中的排序方式就是像队列一样先进先出,而且元素的顺序不会发生变化的(保持上图的LinedEntry1和LinedEntry2顺序),即使重新put了key值相同的元素,通过HashMap的源码可以发现他也只是执行了替换,没有执行addNewEntry,所以也就没有机会重新更改指针指向。而队列我们都知道队列满了的时候是从队头删除元素,腾出空间来使用的,所以这种形式下,最先进入队列或者链表中的就是最早的最老的元素,也就是header的nxt,

这里写图片描述

而当accessOrder为true的时候,是执行makeTail函数的,LindedHashMap中的排序方式就会随着访问和插入操作,元素的顺序就会发生变化的,重新更改指针指向(比如我们访问了LinkedEntry1,那么LinedEntry1就会放到LinkedEntry2的后面,如上图)。每次我们访问一个元素的时候都会改变元素的指针指向,将该元素放回队尾,重新调整header的pre指向,假如我们访问的正好是header.nxt指向的元素,那么header.nxt就会发生变化,那么上面提到的最老的元素就会发生了变化,而header.nxt永远指向的是最近最少使用的

想必看到这里大家对于Android缓存策略Lru到底是怎么实现的应该有了一定的了解了,那就是使用一个了accessOrder为true的LinkedHashMap。

下一篇我将简单带大家看一下LruCache的源码。

Java 集合框架:从基础到精通1 Java 集合框架(Java Collections Framework, JCF)是 Java 中用于存储和操作一组对象的统一架构,位于 包中。 1.2 核心接口详解 Iterable 接口 所有集合都实现 Iterable,因此都支持 for-each 循环。 List 接口 有序集合(序列),允许重复元素,可通过索引访问。 Set 接口 不允许重复元素的集合。 Queue 接口 设计用于在处理前保存元素的集合。 Map 接口 键值对映射,键唯一。 1.3 集合接口的两类操作模式 操作类型 阅读详情

相关推荐

大数据Spark “蘑菇云”行动前传第8课:零基础实战Scala最常用数据结构Map和Tuple及Spark源码解析

大数据Spark “蘑菇云”行动前传第8课:零基础实战Scala最常用数据结构Map和Tuple及Spark源码解析   1 Map 和Tuple 在spark源码中的鉴赏 2 Map 和Tuple 代码操作实战   map - tree - graph   map:无为天地之始 有为万物之母   java:  array list 不能放不同类型的数据 只容纳一种类型 Tup

大模型与Agent智能体 1426

HashMap、LinkedHashMap、ConcurrentHashMap的使用

HashMap、LinkedHashMap、ConcurrentHashMap的使用

会飞的鱼儿的博客 1331

Cursor集成Claude 4.6免费版:本地IDE合规调用指南

大语言模型编程助手(LLM Copilot)已成为现代开发标配,其核心能力依赖于上下文理解、代码推理与结构化输出等关键技术原理。Anthropic官方提供的Claude系列模型,尤其Claude 4.6,凭借200K超长上下文、tool use函数调用和严格可控的API设计,在工程实践中展现出显著技术价值。该能力可无缝嵌入本地IDE环境,支撑代码解释、跨文件分析、单元测试生成等高频开发场景。本文聚焦Cursor这一开源可审计IDE,详解如何合规复用Anthropic免费tier接口(/v1/messages

weixin_34095889的博客 537

LinkedHashMap如何保证顺序性

一. 前言 先看一个例子,我们想在页面展示一周内的消费变化情况,用echarts面积图进行展示。如下: 我们在后台将数据构造完成 HashMap<String, Integer> map = new HashMap<>(); map.put("星期一", 40); map.put("星期二", 43); map.put("星期三", 35); map.put("星期四",...

weixin_30315435的博客 2287

【TI毫米波雷达】IWR6843AOPEVM-G与DCA1000EVM实战:从硬件连接到数据流解析

本文详细解析了TI毫米波雷达开发套件IWR6843AOPEVM-G与DCA1000EVM的硬件连接、模式配置及数据流处理全流程。针对官方手册中的常见配置错误,提供了避坑指南和实测验证的正确设置方法,包括开关配置、网络参数等关键步骤。通过mmWave Studio软件配置雷达参数,并深入解析数据采集与处理流程,帮助开发者高效实现毫米波雷达应用开发。

weixin_30653023的博客 368

处理SAP资产折旧AFAB 过账报错:“科目 8019010100 要求一个成本会计分配”

本文介绍如何处理资产折旧报错 ““科目XXXXXXX 要求一个成本会计分配””

IT小职员 2775

LinkedHashMap 详解

LinkedHashMap 通过特有底层双向链表的支持,使得LinkedHashMap可以保存元素之间的顺序,例如插入顺序或者访问顺序,而HashMap因为没有双向链表的支持,所以就不能保持这种顺序,所以它的访问就是随机的了和HashMap一样,还是通过数组存储元素的这里的顺序指的是遍历的顺序,定义了头结点head,当我们调用迭代器进行遍历时,通过head开始遍历,通过after属性可以不断找到下一个,直到tail尾结点,从而实现顺序性。...

持续学习,持续更新 1万+

STM32 ES8266上阿里云IOT MQTT实践【第七章】:ESP8266+MQTT上阿里云物联网平台实践

本篇文章讲述了如何使用stm32接入阿里云IOT,WiFi模块为ESP8266,可以实现发布以及订阅。

超级大洋葱的博客 2万+

LinkedHashMap使用技巧

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> 的两个使用技巧 1.元素先进先出。 public LinkedHashMap(int initialCapacity) 这是linkedHashMap的一个构造方法,从名字上来看,是初始化长度。该构...

做你想做的人,没有时间的限制,只要愿意,什么时候都可以start。 551

FreeRTOS OTA实战:从Bootloader设计到安全升级的完整流程(附MQTT示例代码)

本文详细介绍了在FreeRTOS系统中实现安全OTA固件升级的完整流程。内容涵盖从Bootloader设计、存储分区规划到与MQTT协议集成的OTA Agent任务开发,并重点解析了固件签名验证、防回滚等关键安全机制,为嵌入式物联网设备提供了一套高可靠性的远程升级解决方案。

weixin_29248653的博客 159

23、资产分散化与有效前沿:投资组合优化的核心策略

本文深入探讨了现代投资组合理论的核心概念,包括分散投资的重要性、均值-方差优化框架以及有效前沿的构建与应用。通过案例分析和数学模型,阐述了如何通过科学方法降低投资组合风险并实现最优风险收益权衡。文章还介绍了实际应用中的求解方法与决策流程,为投资者提供系统的投资组合优化指导。

cc789的博客 114

集合四课之四 Map接口下,HashTable、LinkedHashMap、HashMap和TreeMap介绍与区别 附带速记卡

先来看看组织图

null 202

大模型_WIN10使用hyper-v安装Ubuntu24.04虚拟机_配置独立静态IP地址_连接外网_并与内网其他机器互通_通过容器安装Dify_超详细安装过程002--人工智能工作笔记0247

本文详细描述了在Hyper-V环境中配置网络、安装Ubuntu虚拟机、设置IP地址、安装Docker以及部署Dify应用的过程。首先,通过Hyper-V管理器创建外部虚拟交换机(ExternalSwitch),并配置本机、虚拟交换机和Ubuntu虚拟机的IP地址,确保网络互通。接着,在Ubuntu虚拟机中安装Open-VM-Tools和Docker,并配置Docker的国内镜像源。最后,通过Git克隆Dify源码,使用Docker Compose启动Dify应用,完成部署。整个过程涵盖了网络配置、虚拟机管理

添柴程序猿的专栏 1751

Map相关的基础知识

HashMap 原理图 主要源码解析 基于1.6 public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Objec

bluewaveandy的博客 179

【C++PCL】点云文件读写

本文介绍 PCL 中 PCD 与 PLY 点云文件的读取、保存和字段检查,覆盖 PointXYZI、PointXYZRGB、ASCII、二进制及压缩二进制,并给出 C++11 实操代码与参数调试建议。

weixin_45196569的博客 42

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

JAVA中对集合源码的理解

Collection和Map源码的理解

yd1472939313的博客 163

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

《java核心卷Ⅰ》—— Set集合类、Map集合

各位小伙伴们大家好,欢迎来到这个小扎扎的专栏,在这个系列专栏中我将记录浅学这本书所得收获,鉴于精神,这波简直就是血赚

扎哇太枣糕的笔记博客 586

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Java Map常用方法和实现类深度详解

本文深入解析Java Map接口及其主要实现类的核心原理。Map采用键值对存储结构,包含HashMap、LinkedHashMap、TreeMap等实现。重点剖析了HashMap的底层数据结构演进(JDK7数组+链表到JDK8数组+链表+红黑树)、关键设计决策(如0.75负载因子、2^n容量等),详细解读了put方法执行流程和扩容机制。文章还对比了不同Map实现的特点,帮助开发者根据场景选择合适实现,掌握Map的高效使用技巧。

百锦再的博客 2415

Java Day15

1,JDK8中,HashMap底层结构是 :数组+单向链表 + 红黑树, 无序、可以存放null值,null键,键不能重复2,HashMap的初始容量是16,负载因子默认是0.75,当容量达到16*0.75=12的时候,会扩容3,初始容量和负载因子都可以自己设置 ,负载因子设置的过大和过小都不太好,过大的话,会减少扩容,增加哈希冲突,过小可以减少冲突,扩容次数变多4,如果需要存入大量元素,那么可以指定一个特定的初始容量,HashMap的容量值都是2的n次方。

qq_51944132的博客 161

遍历map的四种方法及Map.entry详解

Map.entrySet() 这个方法返回的是一个Set<Map.Entry<K,V>>, Map.Entry 是Map中的一个接口,他的用途是表示一个映射项(里面有Key和Value), 而Set<Map.Entry<K,V>>表示一个映射项的Set。 Map.Entry里有相应的getKey和getValue方法,即JavaBean,让我们能够从一个项中取出Key和Value。 下面是遍历Map的四种方法: public static

wenchang-的博客 655

剑指offer.50.第一个只出现一次的字符之练习递归、标记或Map

剑指offer.50.打卡,第一次只出现一次字符。记录该题三种写法,分别体现三种思想,练习数据结构中的数据结构或是编程思想。

qq_43164662的博客 97

JavaSE基础_集合

一.Collection接口 1.Collection接口继承树 2.Map接口继承树

qq_17674357的博客 379

小滴课堂-学习笔记:面试BAT高级工程师/技术经理的java集合框架Map

愿景:"让编程不再难学,让技术与生活更加有趣" 更多架构课程请访问 xdclass.net 目录 第1集 编程语言面试题之新版javase集合框架Map篇<小试牛刀上> 第2集 编程语言面试题之对象底层HashCode和equals掌握了吗 第3集 编程语言面试题之新版javase集合框架Map篇<小试牛刀下> 第4集 编程语言面试题之新版javase集合框架Map篇<进阶> 第5集 编程语言面试题之新版javase集合框架Map高手篇<上&g..

dev666的博客 280

JavaSE:集合

JavaSE:集合概述集合框架Collection的使用Collection中的常见方法迭代器接口 IteratorCollection的子接口之一:List底层源码分析ArrayListLinkedListVectorList接口中的常用方法Collection子接口之二:SetLinkedHashSet的使用TreeSet的使用 概述         为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用数组存储对象方

m0_49825096的博客 445

Map源码解析之LinkedHashMap

Map源码解析之HashMap Map源码解析之HashMap红黑树 HashMap源码解析之补充说明:集合、迭代器、compute、merge、replace 在分析完了HashMap源码之后,我们接着看另一种形式的map :LinkedHashMap。 LinkedHashMap是HashMap的子类,相比HashMap而言,其数据结构在HashMap的基础上多了双向链表将所有节点连接起来,从...

翻身已碰头的博客 472

LinkedHashMap 全面深度解析

LinkedHashMap是HashMap的子类,在哈希表高效存取的基础上,通过双向链表维护元素顺序,支持插入顺序或访问顺序遍历。其核心在于Entry节点扩展了before/after指针,形成独立于哈希桶的双向链表。accessOrder参数控制两种模式:插入顺序保持put顺序,访问顺序则在get/put时移动元素至链表尾部实现LRU策略。通过重写removeEldestEntry方法可轻松实现LRU缓存,在O(1)时间复杂度下平衡了性能与有序性,适用于缓存、日志等场景。

csdn_silent的博客 2014
上一篇: 数据结构Map-----HashMap源码解析
下一篇: Android缓存策略LruCahe源码解析,Lru到底是怎么算的?
omyrobin
博客等级 码龄12年 65粉丝 58原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值