设计一个LRU cache

文章1:

http://www.geeksforgeeks.org/implement-lru-cache/

Implement LRU Cache

How to implement LRU caching scheme? What data structures should be used?

We are given total possible page numbers that can be referred. We are also given cache (or memory) size (Number of page frames that cache can hold at a time). The LRU caching scheme is to remove the least recently used frame when the cache is full and a new page is referenced which is not there in cache. Please see the Galvin book for more details (see the LRU page replacement slidehere).

We use two data structures to implement an LRU Cache.

1. A Queue which is implemented using a doubly linked list. The maximum size of the queue will be equal to the total number of frames available (cache size).
The most recently used pages will be near front end and least recently pages will be near rear end.

2. A Hash with page number as key and address of the corresponding queue node as value.

When a page is referenced, the required page may be in the memory. If it is in the memory, we need to detach the node of the list and bring it to the front of the queue.
If the required page is not in the memory, we bring that in memory. In simple words, we add a new node to the front of the queue and update the corresponding node address in the hash. If the queue is full, i.e. all the frames are full, we remove a node from the rear of queue, and add the new node to the front of queue.

Note: Initially no page is in the memory.

Below is C implementation:

// A C program to show implementation of LRU cache
#include <stdio.h>
#include <stdlib.h>
 
// A Queue Node (Queue is implemented using Doubly Linked List)
typedef struct QNode
{
    struct QNode *prev, *next;
    unsigned pageNumber; // the page number stored in this QNode
} QNode;
 
// A Queue (A FIFO collection of Queue Nodes)
typedef struct Queue
{
    unsigned count; // Number of filled frames
    unsigned numberOfFrames;// total number of frames
    QNode *front, *rear;
} Queue;
 
// A hash (Collection of pointers to Queue Nodes)
typedef struct Hash
{
    int capacity; // how many pages can be there
    QNode* *array;// an array of queue nodes
} Hash;
 
// A utility function to create a new Queue Node. The queue Node
// will store the given 'pageNumber'
QNode* newQNode( unsigned pageNumber )
{
    // Allocate memory and assign 'pageNumber'
    QNode* temp = (QNode *)malloc(sizeof( QNode ) );
    temp->pageNumber = pageNumber;
 
    // Initialize prev and next as NULL
    temp->prev = temp->next = NULL;
 
    return temp;
}
 
// A utility function to create an empty Queue.
// The queue can have at most 'numberOfFrames' nodes
Queue* createQueue(int numberOfFrames )
{
    Queue* queue = (Queue *)malloc(sizeof( Queue ) );
 
    // The queue is empty
    queue->count = 0;
    queue->front = queue->rear = NULL;
 
    // Number of frames that can be stored in memory
    queue->numberOfFrames = numberOfFrames;
 
    return queue;
}
 
// A utility function to create an empty Hash of given capacity
Hash* createHash(int capacity )
{
    // Allocate memory for hash
    Hash* hash = (Hash *)malloc( sizeof( Hash ) );
    hash->capacity = capacity;
 
    // Create an array of pointers for refering queue nodes
    hash->array = (QNode **)malloc( hash->capacity *sizeof( QNode* ) );
 
    // Initialize all hash entries as empty
    int i;
    for( i = 0; i < hash->capacity; ++i )
        hash->array[i] = NULL;
 
    return hash;
}
 
// A function to check if there is slot available in memory
int AreAllFramesFull( Queue* queue )
{
    return queue->count == queue->numberOfFrames;
}
 
// A utility function to check if queue is empty
int isQueueEmpty( Queue* queue )
{
    return queue->rear == NULL;
}
 
// A utility function to delete a frame from queue
void deQueue( Queue* queue )
{
    if( isQueueEmpty( queue ) )
        return;
 
    // If this is the only node in list, then change front
    if(queue->front == queue->rear)
        queue->front = NULL;
 
    // Change rear and remove the previous rear
    QNode* temp = queue->rear;
    queue->rear = queue->rear->prev;
 
    if(queue->rear)
        queue->rear->next = NULL;
 
    free( temp );
 
    // decrement the number of full frames by 1
    queue->count--;
}
 
// A function to add a page with given 'pageNumber' to both queue
// and hash
void Enqueue( Queue* queue, Hash* hash, unsigned pageNumber )
{
    // If all frames are full, remove the page at the rear
    if( AreAllFramesFull ( queue ) )
    {
        // remove page from hash
        hash->array[ queue->rear->pageNumber ] = NULL;
        deQueue( queue );
    }
 
    // Create a new node with given page number,
    // And add the new node to the front of queue
    QNode* temp = newQNode( pageNumber );
    temp->next = queue->front;
 
    // If queue is empty, change both front and rear pointers
    if( isQueueEmpty( queue ) )
        queue->rear = queue->front = temp;
    else // Else change the front
    {
        queue->front->prev = temp;
        queue->front = temp;
    }
 
    // Add page entry to hash also
    hash->array[ pageNumber ] = temp;
 
    // increment number of full frames
    queue->count++;
}
 
// This function is called when a page with given 'pageNumber' is referenced
// from cache (or memory). There are two cases:
// 1. Frame is not there in memory, we bring it in memory and add to the front
//    of queue
// 2. Frame is there in memory, we move the frame to front of queue
void ReferencePage( Queue* queue, Hash* hash, unsigned pageNumber )
{
    QNode* reqPage = hash->array[ pageNumber ];
 
    // the page is not in cache, bring it
    if( reqPage == NULL )
        Enqueue( queue, hash, pageNumber );
 
    // page is there and not at front, change pointer
    else if (reqPage != queue->front)
    {
        // Unlink rquested page from its current location
        // in queue.
        reqPage->prev->next = reqPage->next;
        if(reqPage->next)
           reqPage->next->prev = reqPage->prev;
 
        // If the requested page is rear, then change rear
        // as this node will be moved to front
        if(reqPage == queue->rear)
        {
           queue->rear = reqPage->prev;
           queue->rear->next = NULL;
        }
 
        // Put the requested page before current front
        reqPage->next = queue->front;
        reqPage->prev = NULL;
 
        // Change prev of current front
        reqPage->next->prev = reqPage;
 
        // Change front to the requested page
        queue->front = reqPage;
    }
}
 
// Driver program to test above functions
int main()
{
    // Let cache can hold 4 pages
    Queue* q = createQueue( 4 );
 
    // Let 10 different pages can be requested (pages to be
    // referenced are numbered from 0 to 9
    Hash* hash = createHash( 10 );
 
    // Let us refer pages 1, 2, 3, 1, 4, 5
    ReferencePage( q, hash, 1);
    ReferencePage( q, hash, 2);
    ReferencePage( q, hash, 3);
    ReferencePage( q, hash, 1);
    ReferencePage( q, hash, 4);
    ReferencePage( q, hash, 5);
 
    // Let us print cache frames after the above referenced pages
    printf("%d ", q->front->pageNumber);
    printf("%d ", q->front->next->pageNumber);
    printf("%d ", q->front->next->next->pageNumber);
    printf("%d ", q->front->next->next->next->pageNumber);
 
    return 0;
}

Output:

5 4 1 3

This article is compiled by Aashish Barnwal and reviewed by GeeksforGeeks team. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.


文章2:

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


 如何设计一个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 实现:

 public interface Cache<K extends Comparable, V> {

   V get(K obj);  //查询

   void put(K key, V obj); //插入和更新

   void put(K key, V obj, long validTime);

   void remove(K key); //删除

   Pair[] getAll();

   int size();

}

  public class Pair<K extends Comparable, V> implements Comparable<Pair> {

   public Pair(K key1, V value1) {

      this.key = key1;

      this.value = value1;

   }

   public K key;

   public V value;

   public boolean equals(Object obj) {

      if(obj instanceof Pair) {

         Pair p = (Pair)obj;

         return key.equals(p.key)&&value.equals(p.value);

      }

      return false;

   }

   @SuppressWarnings("unchecked")

   public int compareTo(Pair p) {

      int v = key.compareTo(p.key);

      if(v==0) {

         if(p.value instanceof Comparable) {

            return ((Comparable)value).compareTo(p.value);

         }

      }

      return v;

   }

   @Override

   public int hashCode() {

      return key.hashCode()^value.hashCode();

   }

   @Override

   public String toString() {

      return key+": "+value;

   }

}

 public class LRUCache<K extends Comparable, V> implements Cache<K, V>,

      Serializable {

   private static final long serialVersionUID = 3674312987828041877L;

   Map<K, Item> m_map = Collections.synchronizedMap(new HashMap<K, Item>());

   Item m_start = new Item();      //表头

   Item m_end = new Item();        //表尾

   int m_maxSize;

   Object m_listLock = new Object();        //用于并发的锁

   static class Item {

      public Item(Comparable k, Object v, long e) {

         key = k;

         value = v;

         expires = e;

      }

      public Item() {}

      public Comparable key;        //键值

      public Object value;          //对象

       public long expires;          //有效期

      public Item previous;

      public Item next;

   }

   void removeItem(Item item) {

      synchronized(m_listLock) {

         item.previous.next = item.next;

         item.next.previous = item.previous;

      }

   }

   void insertHead(Item item) {

      synchronized(m_listLock) {

         item.previous = m_start;

         item.next = m_start.next;

         m_start.next.previous = item;

         m_start.next = item;

      }

   }

   void moveToHead(Item item) {

      synchronized(m_listLock) {

         item.previous.next = item.next;

         item.next.previous = item.previous;

         item.previous = m_start;

         item.next = m_start.next;

         m_start.next.previous = item;

         m_start.next = item;

      }

   }

   public LRUCache(int maxObjects) {

      m_maxSize = maxObjects;

      m_start.next = m_end;

      m_end.previous = m_start;

   }

   @SuppressWarnings("unchecked")

   public Pair[] getAll() {

      Pair p[] = new Pair[m_maxSize];

      int count = 0;

      synchronized(m_listLock) {

         Item cur = m_start.next;

         while(cur!=m_end) {

            p[count] = new Pair(cur.key, cur.value);

            ++count;

            cur = cur.next;

         }

      }

      Pair np[] = new Pair[count];

      System.arraycopy(p, 0, np, 0, count);

      return np;

   }

   @SuppressWarnings("unchecked")

   public V get(K key) {

      Item cur = m_map.get(key);

      if(cur==null) {

         return null;

      }

     //过期则删除对象

      if(System.currentTimeMillis()>cur.expires) {

         m_map.remove(cur.key);

         removeItem(cur);

         return null;

      }

      if(cur!=m_start.next) {

         moveToHead(cur);

      }

      return (V)cur.value;

   }

   public void put(K key, V obj) {

      put(key, obj, -1);

   }

   public void put(K key, V value, long validTime) {

      Item cur = m_map.get(key);

      if(cur!=null) {

         cur.value = value;

         if(validTime>0) {

            cur.expires = System.currentTimeMillis()+validTime;

         }

         else {

            cur.expires = Long.MAX_VALUE;

         }

         moveToHead(cur);  //成为最新的对象,移动到头部

         return;

      }

      if(m_map.size()>=m_maxSize) {

         cur = m_end.previous;

         m_map.remove(cur.key);

         removeItem(cur);

      }

      long expires=0;

      if(validTime>0) {

         expires = System.currentTimeMillis()+validTime;

      }

      else {

         expires = Long.MAX_VALUE;

      }

      Item item = new Item(key, value, expires);

      insertHead(item);

      m_map.put(key, item);

   }

   public void remove(K key) {

      Item cur = m_map.get(key);

      if(cur==null) {

         return;

      }

      m_map.remove(key);

      removeItem(cur);

   }

   public int size() {

      return m_map.size();

   }

}


===========总结============

LRU cache = 双向链表 + hashmap

由于cache中的移动操作频繁,因此使用双向链表。

为了弥补双向链表查找性能缺陷,引入hashmap。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值