概念

简单来说,由于我们的空间是有限的,所以发明了这个数据结构,当我们的空间不够添加新的元素时,就会删除最近最少使用的元素。

其底层逻辑通过哈希表和链表共同实现。哈希表中存储链表的每一个元素,方便进行元素的获取,而链表则是为了方便移动元素,减少时间复杂度。

当一个元素被添加或者被访问时,其会移动到链表的尾部。因此,越靠近链表尾部的元素,越是最近访问次数多的元素,反之,越靠近头部则是最近不访问的。因此当链表的长度达到我们预设的某一个值时,我们只需要将链表的头部的元素删除,再把哈希表中的元素删除即可。

LinkedHashMap

在JDK中,就有类似于LRUCache的数据结构——LinkedHashMap,下面来介绍一下使用方法

其构造方法有三个参数,第一个参数是初始容量,第二个参数是负载因子(HashMap在多大的时候扩容,默认是0.75f)而第三个参数则是一个布尔值。

当第三个参数为false时,我们的链表的顺序就是基于插入的顺序排列的,先插进来的靠近头,后插进来的靠近尾部。当我们删除时,就会直接删除最先插入的节点

而当第三个参数为true时,链表中元素的排序则是基于访问的顺序的,最近访问过的就放到链表的尾部,也就是我们LRUCache的概念,因此我们把这个值设置为true

LinkedHashMap<String, Integer> linkedHashMap = new java.util.LinkedHashMap<>(16,0.7f,true);

使用put可以插入键值对,使用get则可以通过键访问对应的值。可以看到,当我们获取hi对应的值时,我们的链表顺序变成hi这个键值对在最后了


linkedHashMap.put("hello",1);
linkedHashMap.put("hi",2);
linkedHashMap.put("world",1);System.out.println(linkedHashMap);
System.out.println(linkedHashMap.get("hi"));
System.out.println(linkedHashMap);

LRUCache

而基于JDK的LinkedHashMap,我们可以实现一个LRUCache

首先继承于LinkedHashMap

public class LRUCache extends LinkedHashMap<Integer,Integer>

定义一个容量,当我们的链表数据大于这个容量的个数,就删除头部的元素

public int capacity;

构造方法通过重写LinkedHashMap进行构造,然后对capacity进行赋值

LRUCache(int capacity){//基于访问顺序super(capacity,0.75f,true);this.capacity = capacity;
}

然后只需要重写put和get方法,使用默认的put和get方法即可

@Override
public Integer put(Integer key, Integer value) {return super.put(key, value);
}@Override
public Integer get(Object key) {return super.get(key);
}

最后只需要重写一下其中的removeEldestEntry,这个方法是为真时删除最老的元素,所以我们只需要写当size()大于capacity时即可

@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {return size() > capacity;
}

验证

public static void main(String[] args) {LRUCache lruCache = new LRUCache(3);lruCache.put(1,10);lruCache.put(2,20);lruCache.put(3,30);System.out.println(lruCache);System.out.println(lruCache.get(2));System.out.println(lruCache);lruCache.put(4,40);System.out.println(lruCache);
}


可以看到,当我们的容量为3时,添加三个元素后,获取2对应的val,那么2:20这个键值对就会移动到链表的尾部,接着,我们再插入4:40这个键值对,我们的空间不够了,就会删除头部的键值对——3:30

MyLRUCache

我们也可以不借助LinkedHashMap,自己通过链表和HashMap来写一个LRUCache

定义链表中的节点

我们的链表使用的是双向带头带尾链表,因此在定义时需要定义prev和next,并且其中存储的是键值对,因此需要定义key和val

static class DLinkNode {public int key;public int val;public DLinkNode prev;public DLinkNode next;public DLinkNode(int key, int val){this.key = key;this.val = val;}public DLinkNode(){}@Overridepublic String toString() {return "DLinkNode{" +"key=" + key +", val=" + val +'}';}
}

参数

MyLRUCache中需要存储链表的头尾节点,元素的个数,哈希表和容量

public DLinkNode head;
public DLinkNode tail;
public int usedSize;//双向链表中有效的数据个数
public Map<Integer, DLinkNode> cache;
public int capacity;//容量

构造方法

构造方法需要初始化这些参数,并且需要将头尾节点互相连接,防止后面的插入操作会出现空指针异常

public MyLRUCache(int capacity){this.head = new DLinkNode();this.tail = new DLinkNode();head.next = tail;tail.prev = head;this.cache = new HashMap<>();this.capacity = capacity;
}

put方法

put方法需要先在哈希表中看这个key是否存储过

如果没有存储过,那么需要先创建一个节点,赋值key和val,将这个节点存储在哈希表中,然后将这个节点存储到链表的尾部,然后让usedSize++,判定usedSize是否超过了capacity,如果超过了,那么需要将链表的头节点删除,并且在哈希表中删除,再让usedSize–

而如果存储过了这个值,那么只需要在哈希表中获取这个节点,更新一下key对应的val,然后将这个节点移动到尾部即可。

public void put(int key, int val){//查找当前key是否存储过DLinkNode node = cache.get(key);if(node == null){//当前key没有存储//实例化一个节点DLinkNode dLinkNode = new DLinkNode(key, val);//存储到map中cache.put(key,dLinkNode);//存储到链表的尾巴addTail(dLinkNode);usedSize++;//检查当前的链表数据个数是否达到capacityif(usedSize > capacity){//链表数据个数超过capacity,移除头部节点DLinkNode remNode = removeHead();cache.remove(remNode.key);usedSize--;}} else {//当前key存储过//更新key对应的valnode.val = val;//将该节点移动到尾部moveToTail(node);}printNodes("put");
}

moveToTail

先移除这个节点,然后将这个节点添加到链表尾部

/*** 将当前节点移动到尾部* @param node*/
private void moveToTail(DLinkNode node) {removeNode(node);addTail(node);
}private void removeNode(DLinkNode node) {node.prev.next = node.next;node.next.prev = node.prev;
}/*** 添加节点到链表尾部* @param node*/
private void addTail(DLinkNode node){tail.prev.next = node;node.prev = tail.prev;node.next = tail;tail.prev = node;
}

removeHead

将节点从头部删除,并返回该节点

public DLinkNode removeHead(){DLinkNode del = head.next;head.next = del.next;del.next.prev = head;return del;
}

get方法

get方法则比较简单,拿key到哈希表中找val,如果不存在则直接返回-1,如果存在,那么将这个节点放到链表的尾部,然后返回这个key对应的val

/*** 访问key对应的节点* @param key* @return*/
public int get(int key){DLinkNode dLinkNode = cache.get(key);if(dLinkNode == null){return -1;}//将节点放到链表尾部moveToTail(dLinkNode);printNodes("get");return dLinkNode.val;
}

print

这个方法是用来打印链表中所有的节点的

public void printNodes(String str){System.out.println(str + ": ");DLinkNode dLinkNode = head.next;while(dLinkNode != tail){System.out.print(dLinkNode);dLinkNode = dLinkNode.next;System.out.println();}
}

测试

还使用上一个测试使用的数据集,可以看到最终的结果是相同的

public static void main(String[] args) {MyLRUCache lruCache = new MyLRUCache(3);lruCache.put(1,10);lruCache.put(2,20);lruCache.put(3,30);lruCache.get(2);lruCache.put(4,40);
}

Java——LRUCache相关推荐

  1. java lrucache 使用_LRUCache 具体解释

    LRU的基本概念: LRU是Least Recently Used的缩写,最近最少使用算法. Java 实现LRUCache 1.基于LRU的基本概念,为了达到按最近最少使用排序.能够选择HashMa ...

  2. linux 7进入目录的命令,centos7目录统计之du命令

    CentOS下du查看计算目录大小的命令 用法实例: [root@localhost local]# du -hs smgpdfd 3.3G    smgpdfd [root@localhost lo ...

  3. apache atlas 案例_大数据元数据开源解决方案apache atlas

    [实例简介] 大数据元数据开源解决方案apache atlas,提供数据治理,元数据管理等功能 [实例截图] [核心代码] apache-atlas-1.1.0-sources.tar └── apa ...

  4. 芒果tvvip_那么,如何切芒果?

    芒果tvvip As title! The Small Person loves the slimy fruits, and I spent ten minutes this morning tryi ...

  5. java lrucache_Java LruCache 的使用及原理

    概述 LRU (Least Recently Used) 的意思就是近期最少使用算法,它的核心思想就是会优先淘汰那些近期最少使用的缓存对象. 在我们日常开发中,UI 界面进行网络图片加载是很正常的一件 ...

  6. java 数组越界异常_数组越界异常 求解决!!!

    源自:4-3 滚动状态判断与处理 数组越界异常 求解决!!! package com.example.imooc; import java.io.BufferedInputStream; import ...

  7. Android LruCache 压缩图片 有效避免程序OOM

    压缩加载大图片 我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状.不同的大小,但在大多数情况下,这些图片都会大于我们程序所需要的大小.比如说系统图片库里展示的图片大都是 ...

  8. LruCache缓存处理及异步加载图片类的封装

    Android中的缓存处理及异步加载图片类的封装   一.缓存介绍: (一).Android中缓存的必要性: 智能手机的缓存管理应用非常的普遍和需要,是提高用户体验的有效手段之一. 1.没有缓存的弊端 ...

  9. [置顶] 异步加载图片,使用LruCache和SD卡或手机缓存,效果非常的流畅

    转载请注明出处http://blog.csdn.net/xiaanming/article/details/9825113 异步加载图片的例子,网上也比较多,大部分用了HashMap<Strin ...

最新文章

  1. Linux shell脚本基础学习
  2. python该怎么自学-Python 应该怎么学?
  3. 【Android 逆向】Android 进程注入工具开发 ( Visual Studio 开发 Android NDK 应用 | 使用 Makefile 构建 Android 平台 NDK 应用 )
  4. python合并excel文件关键字_python合并多个excel文件的示例
  5. SQL中Case when 方法的使用
  6. pytorchyolov4训练_使用pytorch-yolov5 訓練自己的數據集-2020.6.15
  7. android图片压缩上传系列-基础篇
  8. delphi xe2 project菜单怎么没有加组件功能_交互设计:让人困惑的三大交互组件及用法...
  9. 三位bcd加法计数器_两个8位BCD编号的加法| 8085微处理器
  10. 开源社交系统ThinkSNS+和ThinkSNS V4区别在哪里
  11. asp.net实现bt和pt—tracker request
  12. jdbc连接oracle查询数据库,JDBC连接Oracle数据库,并操作数据库,查询表
  13. 怎么批量抠复杂的图_抠图怎么抠?一次教你五招!
  14. int和Integer的区别,哪个在定义变量好
  15. 显控触摸屏与STC51单片机modbus通讯程序,包含触摸屏程序与c51代码
  16. 手把手教你用小米手机OTG功能连接打印机
  17. 往后余生-程序员版,哈哈哈
  18. 水杨酸-FITC | salicylic acid-FITC|荧光素标记水杨酸
  19. 优雅地使用eruda在移动端上调试网页
  20. Go --- 使用各服务商的短信服务,实现短信验证等需求

热门文章

  1. 游戏角色中文名生成常用字段
  2. 数据结构4-----线性表的链式存储结构(2)
  3. Unixbench 测试工具分析
  4. 纸上来得终觉浅,绝知此事要躬行(二)
  5. 百度地图多途经点的线路导航——驾车篇
  6. 中国移动再现用户净流失,再次败给中国电信!
  7. 好玩的Deep Dream
  8. PostgreSQL citus python环境搭建
  9. (前端学习)寒假第四周周报
  10. 坚果课堂回顾:团队项目管理SOP打造顶尖执行力