zoukankan      html  css  js  c++  java
  • LRU缓存设计

      LRU缓存设计是一个能够考察许多知识点以及实际编程能力的题目,因为我们在实际工作中是很有可能会去自己写一个LRU算法的简单缓存。本题是LeetCode的第 146 题。LRU——即 Least Recently Used,淘汰最近最少使用的元素的算法。考察的主要内容包括:

    • LRU算法的原理与思想
    • 具有实际开发意义的编程题
    • 线程安全的设计

    设计思路

      首先考虑简单的设计实现。一个LRU的缓存是基于队列实现的,Java中可以基于LinkedList或者Deque队列实现。需要实现两个基本操作get/put

    1. 数据结构上,缓存需要指定容量,不能随意扩展。
    2. 用一个线程安全的ConcurrentHashMap存储数据的键值对。
    3. 用一个Deque队列表征数据的最近使用频率。

    get操作

    • 当需要查询的数据已经在缓存中时,返回其值,并将其在队列中的位置重置。
    • 若数据不存在返回-1(假设需要缓存的数据均为正数)

    put操作

    • 当需要put的数据已经存在于缓存中时,更新其在队列的位置,将其放置到队列尾端,表示为最近使用;然后将新的数据put到缓存map中。
    • 当需要put的数据不存在于缓存中,且缓存容量达到指定上限时,从队列首移除一个元素,将新元素放置到队尾,再移除map中的数据,更新缓存。
     1 public class LRUCache {
     2 
     3     private final int capacity;
     4 
     5     private Map<Integer, Integer> map = new ConcurrentHashMap<>();
     6     private Deque<Integer> queue = new LinkedList<Integer>();
     7 
     8     public LRUCache(int capacity) {
     9         this.capacity = capacity;
    10     }
    11     
    12     public int get(int key) {
    13         Integer value = map.get(key);
    14         if (value != null) {
    15             
    16             this.queue.remove((Integer)key);
    17             this.queue.addLast(key);
    18             return value;
    19         }
    20         return -1;
    21     }
    22     
    23     public void put(int key, int value) {
    24         if (map.get(key) != null) {
    25             this.queue.remove((Integer)key);
    26             this.queue.addLast(key);
    27             map.put(key, value);
    28             return;
    29         }
    30 
    31         if (queue.size() >= capacity) {
    32             Integer lruKey = this.queue.pollFirst();
    33             map.remove(lruKey);
    34         }
    35         this.queue.addLast(key);
    36         map.put(key, value);
    37     }
    38 }
    39 
    40 /**
    41  * Your LRUCache object will be instantiated and called as such:
    42  * LRUCache obj = new LRUCache(capacity);
    43  * int param_1 = obj.get(key);
    44  * obj.put(key,value);
    45  */

      好了,这个题目现在已经完成了,然而光这些,在实际开发中缓存往往会被多线程同时访问,我们考虑的就需要更加周密,也更能体现我们的水平。那么线程安全的实现需要一些说明额外的东西呢?

      答案就是——加锁。因为对缓存队列和map的操作是可能多个线程同时进行的,所以我们用可重入锁 ReetrantLock 去保护这一过程即可。ReetrantLock 的原理这里就先不赘述。

    考虑线程安全的设计实现

    • 不论是get还是put方法,对LRU缓存的map和queue进行操作时必须加锁进行,无论操作是否成功,均需要解锁,所以我们用finally关键字包含unlock操作,代码如下。
     1 public class LRUCache {
     2 
     3     private final int capacity;
     4 
     5     private ReentrantLock lock = new ReentrantLock();
     6 
     7     private Map<Integer, Integer> map = new ConcurrentHashMap<>();
     8     private Deque<Integer> queue = new LinkedList<Integer>();
     9 
    10     public LRUCache(int capacity) {
    11         this.capacity = capacity;
    12     }
    13     
    14     public int get(int key) {
    15         Integer value = map.get(key);
    16         if (value != null) {
    17             lock.lock();
    18             try {
    19                 this.queue.remove((Integer)key);
    20                 this.queue.addLast(key);
    21                 return value;
    22             } finally {
    23                 lock.unlock();
    24             }
    25         }
    26         return -1;
    27     }
    28     
    29     public void put(int key, int value) {
    30         if (map.get(key) != null) {
    31             lock.lock();
    32             try {
    33                 this.queue.remove((Integer)key);
    34                 this.queue.addLast(key);
    35                 map.put(key, value);
    36             } finally {
    37                 lock.unlock();
    38             }
    39             return;
    40         }
    41 
    42         lock.lock();
    43         try {
    44             if (queue.size() >= capacity) {
    45                 Integer lruKey = this.queue.pollFirst();
    46                 map.remove(lruKey);
    47             }
    48             this.queue.addLast(key);
    49             map.put(key, value);
    50         } finally {
    51             lock.unlock();
    52         }
    53     }
    54 }
    55 
    56 /**
    57  * Your LRUCache object will be instantiated and called as such:
    58  * LRUCache obj = new LRUCache(capacity);
    59  * int param_1 = obj.get(key);
    60  * obj.put(key,value);
    61  */
  • 相关阅读:
    技巧篇:如何重写基类的事件
    技巧篇:结合反射技术实现多算法动态加密
    C语言关键字 专一王子:volatile
    第一章 C语言关键字 auto和register
    程序员的进化
    C语言关键字 铁布衫:const
    LINQ简记(3):子句
    windows phone开发之获取屏幕分辨率 和 系统版本
    windows phone开发之 listbox分页加载数据
    Windows phone 微博客户端 开发之项目总结
  • 原文地址:https://www.cnblogs.com/XiaoHDeBlog/p/13198998.html
Copyright © 2011-2022 走看看