码迷,mamicode.com
首页 > 其他好文 > 详细

LRU缓存

时间:2020-07-27 09:38:03      阅读:65      评论:0      收藏:0      [点我收藏+]

标签:amp   第一个   cache   一个   日常   严格   als   hashset   array   

leetcode题目-16.25.LRU缓存

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。

它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lru-cache-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

即如果一组数字,最近使用的放在最左边,最近不用的放在最右边。因此如果新写入一个数字,如果内存满了,就把最右边的数字替换掉,新来的数字放在最左边。如果新获取一个数据,那么这个数据就是最新使用的了,就更新它的位置。

因此这组数据需要频繁地换位置,肯定是要使用链表的。

//LinkedHashMap实现
class
LRUCache { int capacity; Map<Integer, Integer> map; public LRUCache(int capacity) { this.capacity = capacity; map = new LinkedHashMap<>(); } public int get(int key) { //若密钥不存在缓存中,则返回-1 if(!map.containsKey(key)){ return -1; } //如果密钥存在缓存中,则获取密钥的值 Integer value = map.remove(key); map.put(key, value); return value; } public void put(int key, int value) { //如果密钥存在,删出原数值.即更新该数值的位置 if(map.containsKey(key)){ map.remove(key); map.put(key, value); return; } //如果密钥不存在,写入其数据值 map.put(key, value); //如果缓存容量达到上限,那么删出最近最少使用的数据 //利用迭代器,删出第一个 if(map.size()>capacity){
//map.entrySet():把HashMap类型的数据转换为集合类型,获取键值对的集合
       //iterator():获取这个集合的迭代器 map.remove(map.entrySet().iterator().next().getKey()); } } }
//双向链表+HashMap
public
class LRUCache{ //定义双向链表节点 private class ListNode{ int key; int value; ListNode pre; ListNode next; public ListNode(int key, int value){ this.key = key; this.value = value; pre = null; next = null; } } private int capacity; private Map<Integer, ListNode> map; //虚拟头节点 private ListNode head; //虚拟尾节点 private ListNode tail; //初始化 public LRUCache(int capacity){ this.capacity = capacity; map = new HashMap<>(); head = new ListNode(-1, -1); tail = new ListNode(-1, -1); //建立虚拟头节点和尾节点的关系 head.next = tail; tail.pre = head; } public int get(int key){ //若密钥不存在缓存中,则返回-1 if(!map.containsKey(key)){ return -1; } //如果密钥存在缓存中,则获取密钥的值 ListNode node = map.get(key); //更新密钥对应值的位置到尾部.即删出当前节点 node.pre.next = node.next; node.next.pre = node.pre; moveToTail(node); return node.value; } public void put(int key, int value){ //如果密钥存在,删出原数值.即更新该数值的位置 if(get(key) != -1){ map.get(key).value = value; return; } //如果密钥不存在,写入其数据值 ListNode node = new ListNode(key, value); map.put(key, node); moveToTail(node); //如果缓存容量达到上限,那么删出最近最少使用的数据。删除头(虚拟头节点后面的节点) if(map.size() > capacity){ map.remove(head.next.key); head.next = head.next.next; head.next.pre = head; } } //将节点移动到链表的末尾,虚拟尾节点的前面 private void moveToTail(ListNode node){ node.pre = tail.pre; tail.pre = node; node.pre.next = node; node.next = tail; } }

 

扩展:

LRU应用场景:

日常开发中,UI界面加载图片不可能每次都从网络上下载然后显示,因此Android提供了LruCache类,用于图片的内存缓存

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.
一个包含有限数量强引用(平常使用的对象引用方式)的缓存,每次访问一个值,它都会被移动到队列的头部,将一个新的值添加到已经满了的缓存队列时,该队列末尾的值将会被逐出,并且可能会被垃圾回收机制进行回收。

内部实现是通过LinkedHashMap维护一个缓存对象列表。参数分别为初始容量、加载因子、访问顺序(为true即集合的元素顺序是访问顺序,访问后会将该元素放到集合的最后面;为false即按照插入顺序)。

初始容量的设置:如初始大小小于1,那么map大小默认为1;否则不断*2直到大于设置的初始容量。

总缓存大小一般为可用内存的1/8

另一种采用LRU算法的缓存为DisLruCahce,用于实现硬盘缓存。

Java的集合:

  • Collection接口
    • set接口(集):唯一,无序。实现类都线程不安全
      • HashSet:底层是一个数组,适用于少量数据的插入操作
        • LinkedHashSet:继承了HashSet类,为了保持数据的先后添加顺序,又加了链表结构,但是效率低。若某个集合需要保证元素不重复&记录元素的添加顺序
      • TreeSet:也实现了SortSet接口,底层红黑树,只能存储相同类型对象的引用
    • list接口(列表):可重复,顺序与插入顺序一致
      • ArrayList:底层为数组结构,查询快,增删改慢
      • Vector:数组。比于ArrayList,由于每个方法都加上了synchronized,因此线程安全&效率低于ArrayList。由于增长率是目前数组长度的100%,ArrayList为50%,因此Vector适合存储数据量比较大的数据。
      • LinkedList:底层为链表结构,查询慢,增删改快
  • Map接口:键唯一,值不一定唯一
    • HashMap:无序
      • LinkedHashMap:HashMap+LinkedList。对读取顺序有严格要求时使用,继承HashMap,实现了Map接口。桶的链表是双向链表,并且可以控制存储顺序。“HashMap桶的链表产生是因为产生hash碰撞,所有数据形成链表 (红黑树) 存储在一个桶中,LinkedHashMap 中双向链表会串联所有的数据,也就是说有桶中的数据都是会被这个双向链表管理。”即桶里的链表也要实现双向链表的功能技术图片(图源于:https://www.cnblogs.com/xiaoxi/p/6170590.html)
    • TreeMap:基于红黑树实现,根据键的自然顺序进行排序
    • HashTable:无序,任何非空的对象都可作为key/value,线程安全

迭代器iterator

Java采用迭代器为各种容器提供公共的操作接口,使得对容器的遍历操作与具体的底层实现相隔离。

“Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。”

因此迭代器要实现两个方法:

hasNext():仍有元素可迭代,返回true;

next():返回迭代的下一个元素。

 

LRU缓存

标签:amp   第一个   cache   一个   日常   严格   als   hashset   array   

原文地址:https://www.cnblogs.com/lyeeer/p/13382153.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!