zoukankan      html  css  js  c++  java
  • 如何设计一个LRU Cache?转

     如何设计一个LRU Cache?

    Google和百度的面试题都出现了设计一个Cache的题目,什么是Cache,如何设计简单的Cache,通过搜集资料,本文给出个总结。

     通常的问题描述可以是这样:

    Question:

    [1] Design a layer in front of a system which cache the last n requests and the responses to them from the system.

    在一个系统之上设计一个Cache,缓存最近的n个请求以及系统的响应。
    what data structure would you use to implement the cache in the later to support following operations.

    用什么样的数据结构设计这个Cache才能满足下面的操作呢?
    [a] When a request comes look it up in the cache and if it hits then return the response from here and do not pass the request to the system
    [b] If the request is not found in the cache then pass it on to the system
    [c] Since cache can only store the last n requests, Insert the n+1th request in the cache and delete one of the older requests from the cache

    因为Cache只缓存最新的n个请求,向Cache插入第n+1个请求时,从Cache中删除最旧的请求。

    [d]Design one cache such that all operations can be done in O(1) – lookup, delete and insert.

     Cache简介:

    Cache(高速缓存), 一个在计算机中几乎随时接触的概念。CPU中Cache能极大提高存取数据和指令的时间,让整个存储器(Cache+内存)既有Cache的高速度,又能有内存的大容量;操作系统中的内存page中使用的Cache能使得频繁读取的内存磁盘文件较少的被置换出内存,从而提高访问速度;数据库中数据查询也用到Cache来提高效率;即便是Powerbuilder的DataWindow数据处理也用到了Cache的类似设计。Cache的算法设计常见的有FIFO(first in first out)和LRU(least recently used)。根据题目的要求,显然是要设计一个LRU的Cache。

     解题思路:

    Cache中的存储空间往往是有限的,当Cache中的存储块被用完,而需要把新的数据Load进Cache的时候,我们就需要设计一种良好的算法来完成数据块的替换。LRU的思想是基于“最近用到的数据被重用的概率比较早用到的大的多”这个设计规则来实现的。

    为了能够快速删除最久没有访问的数据项和插入最新的数据项,我们双向链表连接Cache中的数据项,并且保证链表维持数据项从最近访问到最旧访问的顺序。每次数据项被查询到时,都将此数据项移动到链表头部(O(1)的时间复杂度)。这样,在进行过多次查找操作后,最近被使用过的内容就向链表的头移动,而没有被使用的内容就向链表的后面移动。当需要替换时,链表最后的位置就是最近最少被使用的数据项,我们只需要将最新的数据项放在链表头部,当Cache满时,淘汰链表最后的位置就是了。

      注: 对于双向链表的使用,基于两个考虑。首先是Cache中块的命中可能是随机的,和Load进来的顺序无关。其次,双向链表插入、删除很快,可以灵活的调整相互间的次序,时间复杂度为O(1)。

        查找一个链表中元素的时间复杂度是O(n),每次命中的时候,我们就需要花费O(n)的时间来进行查找,如果不添加其他的数据结构,这个就是我们能实现的最高效率了。目前看来,整个算法的瓶颈就是在查找这里了,怎么样才能提高查找的效率呢?Hash表,对,就是它,数据结构中之所以有它,就是因为它的查找时间复杂度是O(1)。

    梳理一下思路:对于Cache的每个数据块,我们设计一个数据结构来储存Cache块的内容,并实现一个双向链表,其中属性next和prev时双向链表的两个指针,key用于存储对象的键值,value用户存储要cache块对象本身。

     Cache的接口:

    查询:

    • 根据键值查询hashmap,若命中,则返回节点,否则返回null。
    • 从双向链表中删除命中的节点,将其重新插入到表头。
    • 所有操作的复杂度均为O(1)。

    插入:

    • 将新的节点关联到Hashmap
    • 如果Cache满了,删除双向链表的尾节点,同时删除Hashmap对应的记录
    • 将新的节点插入到双向链表中头部

    更新:

    • 和查询相似

    删除:

    • 从双向链表和Hashmap中同时删除对应的记录。

    LRU Cache的Java 实现:

      1 public interface Cache<K extends Comparable, V> {
      2 
      3    V get(K obj);  //查询
      4 
      5    void put(K key, V obj); //插入和更新
      6 
      7    void put(K key, V obj, long validTime);
      8 
      9    void remove(K key); //删除
     10 
     11    Pair[] getAll();
     12 
     13    int size();
     14 
     15 }
     16 
     17   public class Pair<K extends Comparable, V> implements Comparable<Pair> {
     18 
     19    public Pair(K key1, V value1) {
     20 
     21       this.key = key1;
     22 
     23       this.value = value1;
     24 
     25    }
     26 
     27    public K key;
     28 
     29    public V value;
     30 
     31    public boolean equals(Object obj) {
     32 
     33       if(obj instanceof Pair) {
     34 
     35          Pair p = (Pair)obj;
     36 
     37          return key.equals(p.key)&&value.equals(p.value);
     38 
     39       }
     40 
     41       return false;
     42 
     43    }
     44 
     45    @SuppressWarnings("unchecked")
     46 
     47    public int compareTo(Pair p) {
     48 
     49       int v = key.compareTo(p.key);
     50 
     51       if(v==0) {
     52 
     53          if(p.value instanceof Comparable) {
     54 
     55             return ((Comparable)value).compareTo(p.value);
     56 
     57          }
     58 
     59       }
     60 
     61       return v;
     62 
     63    }
     64 
     65    @Override
     66 
     67    public int hashCode() {
     68 
     69       return key.hashCode()^value.hashCode();
     70 
     71    }
     72 
     73    @Override
     74 
     75    public String toString() {
     76 
     77       return key+": "+value;
     78 
     79    }
     80 
     81 }
     82 
     83  public class LRUCache<K extends Comparable, V> implements Cache<K, V>,
     84 
     85       Serializable {
     86 
     87    private static final long serialVersionUID = 3674312987828041877L;
     88 
     89    Map<K, Item> m_map = Collections.synchronizedMap(new HashMap<K, Item>());
     90 
     91    Item m_start = new Item();      //表头
     92 
     93    Item m_end = new Item();        //表尾
     94 
     95    int m_maxSize;
     96 
     97    Object m_listLock = new Object();        //用于并发的锁
     98 
     99    static class Item {
    100 
    101       public Item(Comparable k, Object v, long e) {
    102 
    103          key = k;
    104 
    105          value = v;
    106 
    107          expires = e;
    108 
    109       }
    110 
    111       public Item() {}
    112 
    113       public Comparable key;        //键值
    114 
    115       public Object value;          //对象
    116 
    117        public long expires;          //有效期
    118 
    119       public Item previous;
    120 
    121       public Item next;
    122 
    123    }
    124 
    125    void removeItem(Item item) {
    126 
    127       synchronized(m_listLock) {
    128 
    129          item.previous.next = item.next;
    130 
    131          item.next.previous = item.previous;
    132 
    133       }
    134 
    135    }
    136 
    137    void insertHead(Item item) {
    138 
    139       synchronized(m_listLock) {
    140 
    141          item.previous = m_start;
    142 
    143          item.next = m_start.next;
    144 
    145          m_start.next.previous = item;
    146 
    147          m_start.next = item;
    148 
    149       }
    150 
    151    }
    152 
    153    void moveToHead(Item item) {
    154 
    155       synchronized(m_listLock) {
    156 
    157          item.previous.next = item.next;
    158 
    159          item.next.previous = item.previous;
    160 
    161          item.previous = m_start;
    162 
    163          item.next = m_start.next;
    164 
    165          m_start.next.previous = item;
    166 
    167          m_start.next = item;
    168 
    169       }
    170 
    171    }
    172 
    173    public LRUCache(int maxObjects) {
    174 
    175       m_maxSize = maxObjects;
    176 
    177       m_start.next = m_end;
    178 
    179       m_end.previous = m_start;
    180 
    181    }
    182 
    183    @SuppressWarnings("unchecked")
    184 
    185    public Pair[] getAll() {
    186 
    187       Pair p[] = new Pair[m_maxSize];
    188 
    189       int count = 0;
    190 
    191       synchronized(m_listLock) {
    192 
    193          Item cur = m_start.next;
    194 
    195          while(cur!=m_end) {
    196 
    197             p[count] = new Pair(cur.key, cur.value);
    198 
    199             ++count;
    200 
    201             cur = cur.next;
    202 
    203          }
    204 
    205       }
    206 
    207       Pair np[] = new Pair[count];
    208 
    209       System.arraycopy(p, 0, np, 0, count);
    210 
    211       return np;
    212 
    213    }
    214 
    215    @SuppressWarnings("unchecked")
    216 
    217    public V get(K key) {
    218 
    219       Item cur = m_map.get(key);
    220 
    221       if(cur==null) {
    222 
    223          return null;
    224 
    225       }
    226 
    227      //过期则删除对象
    228 
    229       if(System.currentTimeMillis()>cur.expires) {
    230 
    231          m_map.remove(cur.key);
    232 
    233          removeItem(cur);
    234 
    235          return null;
    236 
    237       }
    238 
    239       if(cur!=m_start.next) {
    240 
    241          moveToHead(cur);
    242 
    243       }
    244 
    245       return (V)cur.value;
    246 
    247    }
    248 
    249    public void put(K key, V obj) {
    250 
    251       put(key, obj, -1);
    252 
    253    }
    254 
    255    public void put(K key, V value, long validTime) {
    256 
    257       Item cur = m_map.get(key);
    258 
    259       if(cur!=null) {
    260 
    261          cur.value = value;
    262 
    263          if(validTime>0) {
    264 
    265             cur.expires = System.currentTimeMillis()+validTime;
    266 
    267          }
    268 
    269          else {
    270 
    271             cur.expires = Long.MAX_VALUE;
    272 
    273          }
    274 
    275          moveToHead(cur);  //成为最新的对象,移动到头部
    276 
    277          return;
    278 
    279       }
    280 
    281       if(m_map.size()>=m_maxSize) {
    282 
    283          cur = m_end.previous;
    284 
    285          m_map.remove(cur.key);
    286 
    287          removeItem(cur);
    288 
    289       }
    290 
    291       long expires=0;
    292 
    293       if(validTime>0) {
    294 
    295          expires = System.currentTimeMillis()+validTime;
    296 
    297       }
    298 
    299       else {
    300 
    301          expires = Long.MAX_VALUE;
    302 
    303       }
    304 
    305       Item item = new Item(key, value, expires);
    306 
    307       insertHead(item);
    308 
    309       m_map.put(key, item);
    310 
    311    }
    312 
    313    public void remove(K key) {
    314 
    315       Item cur = m_map.get(key);
    316 
    317       if(cur==null) {
    318 
    319          return;
    320 
    321       }
    322 
    323       m_map.remove(key);
    324 
    325       removeItem(cur);
    326 
    327    }
    328 
    329    public int size() {
    330 
    331       return m_map.size();
    332 
    333    }
    334 
    335 }

     转:http://blog.csdn.net/hexinuaa/article/details/6630384

  • 相关阅读:
    第九周
    第八周
    第七周
    代码复审核查表
    对软件开发的理解
    第六周
    网站流量分析架构及实现
    hive的sql语句
    精简客户端搭建Oracle数据库
    idaa搭建maven发布tomcat
  • 原文地址:https://www.cnblogs.com/WayneZeng/p/3037894.html
Copyright © 2011-2022 走看看