数据结构和算法 之 LinkedHashMap实现LRU算法

使用LinkedHashMap实现简单的LRU LRU在很多缓存失效策略中会涉及,本质是一种节约更公平的节约资源策略,最近最少使用 java中的LinkedHashMap就有自带的实现LRU 简单说一下LinkedHashMap这种数据结构,本质是HashMap+双向链表,不仅有HashMap的特性,还能维护元素的顺序,示意图如下 使用LinkedHashMap实现简单的LRU package cn.com.suntree.utils.myself; import java.util.LinkedHashMap; import java.util.M 阅读详情

​   目录

              1、背景介绍

              2、LinkHashMap实现

              3、LinkHashMap实现原理


一、背景介绍

LRU全称是Least Recently Used,即最近最久未使用的意思。LRU算法的主要思想:设置一个缓存大小阈值,超过阈值就去删除最老的数据。新添加数据或者访问数据的时候保证最老的数据总是在链表的头部,最新的数据总是在尾部,这样每次需要删除数据时把头部数据删除即可。

二、LinkHashMap实现

在Java当中,LinkHashMap已经实现了LRU的大部分功能,具体删除时机需要我们自己实现指定,废话不多说,直接看实例。

/**
 * @author: wenyixicodedog
 * @create: 2020-07-25
 * @description:
 */
public class LinkHashMapLRU extends LinkedHashMap<String, String> {
​
    public LinkHashMapLRU(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }
​
    public LinkHashMapLRU() {
​
    }
​
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
        //当map的容量大于等于5的时候,再插入新元素就移除旧的元素
        return this.size() >= 5;
    }
​
    public static void main(String[] args) {
        LinkHashMapLRU map = new LinkHashMapLRU(5, 0.75F, true);
        map.put("1", "one");
        map.put("2", "two");
        map.put("3", "three");
        map.put("4", "four");
        System.out.println("================原始数据===================");
        print(map);
        map.get("3");
        System.out.println("================访问一次3===================");
        print(map);
​
        map.get("1");
        map.get("2");
        System.out.println("===============访问一次1、2=====================");
        print(map);
        System.out.println("===============新添加元素5=====================");
        map.put("5", "five");
        print(map);
    }
​
    static void print(LinkedHashMap<String, String> map) {
        map.keySet().iterator().forEachRemaining(System.out::println);
    }
​
}

执行结果如下:

================原始数据===================
1
2
3
4
================访问一次3===================
1
2
4
3
===============访问一次1、2=====================
4
3
1
2
===============新添加元素5=====================
3
1
2
5

从实例中我们不难看出,初始状态1,2,3,4之后我们访问3之后,3就作为最新被访问的数据放到了最后,其他数据依次在前面,然后访问1,2,1,2被放在了最后,4,3依次在前,最后新添加5,被放在最后,最前面的4被删除。

三、LinkHashMap实现原理

在这个示例中主要进行了两个操作,添加和访问数据,我们首先看LinkHashMap添加数据的源码。

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
​
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 如果当前添加的元素已经存在在map中
            if (e != null) { 
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                //访问数据之后将该数据放置在链表尾部
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        // 如果当前添加的元素不存在在map中    
        // 添加元素之后判断是否需要删除头部元素,若需要,则删除。
        afterNodeInsertion(evict);
        return null;
    }
​
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        // 删除旧元素执行时机需要我们自己手动指定——removeEldestEntry
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

然后接下来看访问数据的时候源码执行逻辑。

 public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            // 将新访问的数据放置到链表尾部 
            afterNodeAccess(e);
        // 返回数据value值    
        return e.value;
    }
​
    void afterNodeAccess(Node<K,V> e) { // move node to last
        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.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

LinkHashMap源码当中其实是通过三个核心方法进行实现的,这三个核心方法是实现的HashMap的方法。

//把新访问元素放到链表尾部
 void afterNodeAccess(Node<K,V> p) { }
 //在插入新元素后,检查是否要删除最头部元素,需要则删除
 void afterNodeInsertion(boolean evict) { }
 //在删除元素之后调整头尾部元素
 void afterNodeRemoval(Node<K,V> p) { }

不仅仅在Java当中,在Redis、mybatis中,LRU实现原理与其类似。

以下是mybatis的实现:

public class LruCache implements Cache {
​
  private final Cache delegate;
  private Map<Object, Object> keyMap;
  private Object eldestKey;
​
  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }
​
  // 省略部分代码
​
  public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;
​
      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }
​
  // 省略部分代码

更多内容持续更新中,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......

公众号:wenyixicodedog

 

如何手写一个LRU算法 背景 在Redis的内存占用过多的时候,此时会进行内存淘汰,比较常用的就是基于LRU算法进行淘汰。那么什么是LRU算法呢? LRU算法概念 LRU 是Least Recently Used的缩写,简称最近最少使用。 也就是说在Redis中内存满了,会优先淘汰那些最近最不常访问的数据。那在Java中用什么数据结构去实现呢?一种的话是基于LinkedHashMap,一种是自己设计数据结构,使用链表... 阅读详情

相关推荐

java 中 LinkedHashMap使用总结

初识LinkedHashMap 上两篇文章讲了HashMap和HashMap在多线程下引发的问题,说明了,HashMap是一种非常常见、非常有用的集合,并且在多线程情况下使用不当会有线程安全问题。 大多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺

chen 1万+

LinkedHashMap

一、基础概念 下图很好地还原了LinkedHashMap的原貌:HashMap和双向链表的密切配合和分工合作造就了LinkedHashMap。特别需要注意的是,next用于维护HashMap各个桶中的Entry链,before、after用于维护LinkedHashMap的双向链表,虽然它们的作用对象都是Entry,但是各自分离,是两码事儿。 LinkedHashMap的继承结构如图: HashMap与LinkedHashMap的Entry结构示意图如下图所示 1.遍历 LinkedHashMap进行

sinat_34814635的博客 604

如何实现 LRU 缓存:基于LinkedHashMap?

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

喵手的博客 1128

LinkedHashMap实现LRU

前言: LinkedHashMap是HashMap的子类,在原有HashMap数据结构的基础上,它还维护着一个双向链表链接所有entry,这个链表定义了迭代顺序,通常是数据插入的顺序。 上图我只画了链表,其实红黑树节点也是一样的,只是节点类型不一样而已 也就是说我们遍历LinkedHashMap的时候,是从head指针指向的节点开始遍历,一直到tail指向的节点。 源码 public class LinkedHashMap<K,V> extends HashMap<K,V> imp

张井天的博客 2469

数值计算之 插值法(4)切比雪夫零点插值

数值计算之 插值法(4)切比雪夫零点插值前言插值点选取第一类切比雪夫多项式拉格朗日插值多项式的余项切比雪夫零点插值后记 前言 上篇插值法讨论了多项式插值的解,以及龙格现象。本篇将介绍一种在抽取节点时有效降低龙格现象的方法——切比雪夫零点插值。 插值点选取 插值多项式阶数较高时,在取值空间均匀取点,容易出现龙格现象。 即区间边缘的插值结果与原函数差异很大,而区间中央的插值结果相对较好。这表明,高阶多项式插值对区间中央的节点拟合好,而对两端节点拟合效果差。 自然而然会想到,在两端多采样一些节点,在中间少采样一

qq_41035283的博客 1万+

车道线检测(传统基于Opencv的方法)

传统车道线检测方法主要是使用颜色选择、高斯平滑、边缘检测、ROI区域选择、霍夫变换、标定车道边界等Opnecv图像处理操作去检测道路上的车道线,我自己对很多用这类处理对很多道路场景车道线进行过实验,这类算法往往需要人工根据不同街道场景手动去反复调整各算法参数,从而才能使检测效果最佳。

Yangxiaoaijiejie的博客 2073

使用LinkedHashMap实现LRU算法

LRU算法,最近最少使用原则,如果要实现该算法,可以借助LinkedHashMap数据结构,LinkedHashMap继承HashMap,底层使用哈希表和双向链表来保存所有元素,使用LinkedHashMap可以确保元素按照顺序进行存储。 默认情况下,LinkedHashMap是按照元素的添加顺序存储,也可以启用按照访问顺序存储,即最近读取的数据放在最前面,最早读取的数据放在最后面,然后它还有一个判断是否删除最老数据的方法,默认是返回false,即不删除数据。 下面就基于这两种存储方式,简单展示一下如何

张维鹏的博客 8173

comsol 多物理场仿真流程-以开关柜为例

1、主要方法 在SolidWorks中对于开关柜的内部与外部结构进行建模,生成能够导入软件计算的3D模型。将SolidWorks中的模型导入comsol软件中,设置参数进行多物理场耦合仿真。 1、具体步骤 删除高压开关柜中对其内部电磁环境无较大影响的结构,如低压测量柜、断路器内部结构等。 导入模型:将厂家提供的3D模型转化为solidworks结构模型。 修复模型:修复已转化模型中转化错误的部分、修复已转化模型中非实体部分、修复已转化模型中重叠实体部分 化简模型:去除过于...

健忘16的博客 7020

算法学习笔记——数据结构:LRU和LFU(有序字典LinkedHashMap和有序集LinkedHashSet的Python实现)

算法不能脱离数据结构,各种炫酷的算法本质上都是将基本的数据结构进行组合,本文研究链表、二叉树等基本的数据结构能“玩”出什么花样 HashMap:基于哈希表的 Map 接口的实现 HashMap=数组+链表,数组是主体,链表用于解决哈希冲突 不看底层实现,HashMap就是一个(用链表解决哈希冲突的)散列表,在Python中就是字典 哈希链表LinkedHashMap与LRU算法 LRU缓存淘汰算法,全称LeastRecentlyUsed,即认为最近使用的数据是最有用的,在生活中的例子就是手机后台任务:.

Insomnia_X的博客 2388

WSL2+Ubuntu20.04纯root环境搭建指南:告别权限烦恼的终极方案

本文提供了一份详细的WSL2+Ubuntu20.04纯root环境搭建指南,旨在为本地开发与学习打造一个免去权限干扰、极致流畅的Linux子系统环境。通过启用WSL2、安装Ubuntu 20.04,并执行核心的root用户设置,开发者可以彻底告别频繁的sudo操作,提升在文件操作、容器调试和系统配置等方面的效率。

time3的博客 474

计算机视觉教程1-1:透视空间与透视变换

在生活中,透视现象很常见,其物理本质是光的直线传播。由于人眼与相机在捕获图像时均存在透视现象,因此计算机几何基于透视空间进行研究。本节介绍透视变换原理

FRIGIDWINTER的博客 3092

【数据结构与算法】使用数组与有序集合LinkedHashMap(双向链表)实现一个简单的LRU算法(最近最少使用策略 LRU(Least Recently Used))

什么是LRU算法? redis大家都玩过吧,你们好奇redis内存数据存满之后会发生什么吗?抛出异常?禁止使用?还是删除数据?其实redis设计了一种内润淘汰机制。 noeviction(默认策略):屏蔽写操作,返回错误(特殊的写操作除外),但是支持删除操作 volatile-lru:使用LRU算法对设置了过期时间的key进行删除。 allkeys-lru:使用LRU算法对所有key进行删...

流星007 513

FLUENT Meshing Watertight Geometry工作流入门 - 9 生成体网格

总结一下我们学到的东西。讨论体积网格的重要性,并了解生成体积网格的不同方法。还了解体积网格质量的重要性,并学习如何改进它。

B_etter_Man的博客 1万+

Java集合详解5:深入理解LinkedHashMap和LRU缓存

本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下Star哈 文章首发于我的个人博客: www.how2playlife.com 本文是微信公众号【Java技术江湖】的《走进JavaWeb技术世界》其中一篇,本文部分内容来源于网络,为了把本文主题...

AliCoder的博客 1234

【AI智能体】全场景AI智能体工作台WorkBuddy实战操作详解

全场景AI智能体工作台WorkBuddy实战操作详解

congge 5522

SAP MD09 事务码中的物料需求追溯详解

物料需求追溯是指在物料需求计划中,追踪特定物料的需求来源和满足方式的过程。它帮助企业确定每个需求(如销售订单、独立需求、预留等)是由哪些供应元素(如库存、生产订单、采购订单等)来满足的。通过这种追溯,企业可以了解需求和供应之间的关系,从而优化生产和采购计划,减少库存积压,提升供应链效率。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1197

深入理解LinkedHashMap和LRU缓存

LinkedHashMap 概述 HashMap 是 Java Collection Framework 的重要成员,也是Map族中我们最为常用的一种。不过遗憾的是,HashMap是无序的,也就是说,迭代HashMap所得到的元素顺序并不是它们最初放置到HashMap的顺序。 HashMap的这一缺点往往会造成诸多不便,因为在有些场景中,我们确需要用到一个可以保持插入顺序的Map。庆幸的是,JDK为我们解决了这个问题,它为HashMap提供了一个子类 —— LinkedHashMap。虽然LinkedHas

qq_40959750的博客 453

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

基于LinkedHashMap实现LRU缓存策略

LinkedHashMap是Java提供的一个数据结构,是HashMap的一个子类。它在HashMap的基础上,增加了元素的顺序信息。LinkedHashMap内部通过维护一组双向链表,保证元素按照插入顺序或者访问顺序进行排序。这一特性为实现LRU策略提供了便利。本文我们探讨了如何基于Java的LinkedHashMap实现LRU缓存策略,通过LinkedHashMap内部的链表,我们可以轻松地将最近最少使用的元素淘汰,从而实现LRU策略。尽管这个例子很简单,但是这是一种强大的策略。

果酱 の 博客 3871

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

基于LinkedHashMap实现LRU缓存调度算法原理

基于LinkedHashMap实现LRU缓存调度算法原理Q:

Hello Java 712

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Java基础汇总(十六)——LinkedHashMap

JAVA基础 linkedlist lru

weixin_45864705的博客 4万+

彻底理解HashMap及LinkedHashMap

HashMap是Map族中最为常用的一种,也是 Java Collection Framework 的重要成员。HashMap和双向链表合二为一即是LinkedHashMap。所谓LinkedHashMap,其落脚点在HashMap,因此更准确地说,它是一个将所有Node节点链入一个双向链表的HashMap。ConcurrentHashMap是java.util.concurrent包的重要成员,...

Hopefully Sky的博客 5469

java集合详解_Java集合详解5:深入理解LinkedHashMap和LRU缓存

今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存。摘要:HashMap和双向链表合二为一即是LinkedHashMap。所谓LinkedHashMap,其落脚点在HashMap,因此更准确地说,它是一个将所有Entry节点链入一个双向链表的HashMap。由于LinkedHashMap是HashMap的子类,所以LinkedHashMa...

weixin_39714191的博客 142

Java集合------LinkedHashMap底层原理

Java集合------LinkedHashMap底层原理 前言 在集合中,除了常用的HashMap,还有今天我们要说的LinkedHashMap.为什么会有LinkedHashMap这个集合呢?因为我们在迭代HashMap的时候是无序的,我们希望有一个有序的map来方便我们的使用,这个时候就有了LinkedHashMap. 正文 首先,LinkedHashMap通过维护一个运行于所有条目的双向链...

剑小纯的夜话白鹭 2万+

LRU算法详解

LRU算法分步骤详解,带你吊打面试官

H_xxxxd的博客 1620

详解LRU(最近最少使用)算法及Java实现

1、基本介绍 LRU(Least Recently Used)即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。对于缓存来说,容量是有限的,当容量满时,就需要清理一些对于当前情景来说没用的内容,从而为新来的腾出位置,如何选择就对应了某种策略,而LRU采取的是一种选择最近最久未使用作为淘汰的策略。 最近即当前,最久未使用也即最少使用,要实现如此特征,我们有所规定,每当访问了缓存中存在的数据或向缓存中新增数据,该数据就得移至最前面(表头),如果新增数据时,缓存已满,就要删除当前位于最后

weixin_45685353的博客 5548

java中LRU算法

当我们处理大量数据时,经常需要使用缓存来提高性能。LRU(最近最少使用)算法是一种常用的缓存淘汰策略,用于在缓存空间不足时选择要删除的数据。首先,我们需要了解LRU算法的工作原理。LRU算法的核心思想是基于数据的访问模式:最近被访问的数据很可能在不久的将来再次被访问,而最久未被访问的数据可能很快就会被废弃。LRU算法基于这个思想,将最近访问的数据保留在缓存中,而淘汰最久未被访问的数据。除了使用来实现LRU算法外,我们还可以通过自定义数据结构来实现。

m0_68642284的博客 1489
上一篇: 数据结构和算法 之 两种方式实现TOP K
下一篇: Java多线程 之 CyclicBarrier源码分析
wenyixicodedog
博客等级 码龄8年 87粉丝 116原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值