C语言实现LRU缓存

*本文简述一个简单LRU缓存的c语言实现方式。

GitHub:https://github.com/Stand1210/c-LRU-

一·首先明确几个概念:
1.缓存的特点?
    1⃣️:能在某种程度上降低访问数据的成本
    2⃣️:其容量远小于外部存储的容量。
2.什么是LRU缓存?
     LRU缓存是一种以LRU策略为缓存策略的缓存。
     所谓缓存策略, 就是当缓存满了之后, 又有新数据需要加入内存时, 我们怎么从缓存中为新数据腾出空间的策略。这个权衡的过程就是所谓的缓存策略。
     

     LRU, Least Recently Used的简写, 即近期最少使用算法。 该算法依据与程序的局部性原理, 其淘汰旧数据的策略是, 距离当前最久没有被访问过的数据应该被淘汰。

二·设计及实现
1⃣️
LRUCache接口:
//
//  LRUCache.h
//  LRU缓存接口
//
//  Created by 宋珂琦 on 2017/4/13.
//  Copyright © 2017年 宋珂琦. All rights reserved.
//

#ifndef LRUCache_h
#define LRUCache_h

/*创建LRU缓存*/
int LRUCacheCreate(int capacity, void **lruCache);
/*销毁LRU缓存*/
int LRUCacheDestory(void *lruCache);
/*将数据放入LRU缓存中*/
int LRUCacheSet(void *lruCache, char key, char data);
/*从缓存中获取数据*/
char LRUCacheGet(void *lruCache, char key);
/*打印缓存中的数据, 按访问时间从新到旧的顺序输出*/
void LRUCachePrint(void *lruCache);


#endif /* LRUCache_h */

   上述接口中, 我们假设数据索引和LRU缓存的每个单位仅能存储一个char型的字符。

2⃣️:详细设计

  •   使用双向链表维护LRU特性。
              我们将在缓存中维护一个双向链表, 该链表奖缓存中的数据块按访问时间从新到旧排列起来:
              1.当我们需要访问一块数据时, 我们先调用接口LRUCacheGet尝试从缓存中获取数据
              2.如果缓存中刚好缓存了该数据, 那么我们将该数据从缓存的双向链表中摘除, 然后再重新放入到双向链表的表头
              3.如果缓存中没有我们需要的数据, 那么我们从外部取得数据, 然后调用LRUCacheSet接口将该数据放入缓存, 此过程, 会将新数据插入到双向链表的表头
  • 使用哈希表保证缓存中数据的访问速度
            如果我们仅将缓存中的数据维护在一个链表中, 那么当我们需要从缓存中查找数据时, 旧意味着我们需要遍历链表。 这样的设计, 其时间复杂度为O(n). 是一种比较低效率的做法。 因此, 我们除了将数据维护在双向链表中, 我们同时还将数据维护在一个哈希表中。 哈希表访问数据的时间复杂度为O(1)。
            //注意  在我们的哈希表的实现内部, 也维护了链表, 这是一种解决哈希冲突的方法。如图:
         
  • 定义LRU缓存内部数据结构
            依据上述设计, 一个LRU缓存包含了一个双向链表和一个哈希表, 因此我们可以这样定义我们LRU缓存的数据结构
            
/* 定义LRU缓存 */
typedef struct LRUCacheS{
    int cacheCapacity;  /*缓存的容量*/
    cacheEntryS **hashMap;  /*缓存的哈希表*/
    
    cacheEntryS *lruListHead;   /*缓存的双向链表表头*/
    cacheEntryS *lruListTail;   /*缓存的双向链表表尾*/
    int lruListSize;    /*缓存的双向链表节点个数*/
}LRUCacheS;

           缓存中的单元, 既是一个双向链表节点, 同时又是哈希表当中的一个节点, 因此我们可以这样定义我们缓存单元的数据结构:
/*定义LRU缓存的缓存单元*/
typedef struct cacheEntryS{
    char key;   /* 数据的key */
    char data;  /* 数据的data */
    
    struct cacheEntryS *hashListPrev;   /* 缓存哈希表指针, 指向哈希链表的前一个元素 */
    struct cacheEntryS *hashListNext;   /* 缓存哈希表指针, 指向哈希链表的后一个元素 */
    
    struct cacheEntryS *lruListPrev;    /* 缓存双向链表指针, 指向链表的前一个元素 */
    struct cacheEntryS *lruListNext;    /* 缓存双向链表指针, 指向链表后一个元素 */
}cacheEntryS;

           LRU缓存内部数据结构关系图如下:




三·测试程序
          我们以TDD(测试驱动开发)的方式来实现我们的LRU缓存, 在定义好接口后, 我们就可以先构造出我们的测试用例了:
//
//  main.c
//  LRU 缓存测试用例
//
//  Created by 宋珂琦 on 2017/4/13.
//  Copyright © 2017年 宋珂琦. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include "LRUCache.h"

/*错误处理宏*/
#define HANDLE_ERROR(msg) \
        do{ fprintf(stderr, "%s fail.\n", msg);exit(-1);}while(0);

/* 封装缓存数据存储接口, 此处我们让data同时充当key的角色 */
#define LRUCACHE_PUTDATA(cache, data) \
do {\
    if (0 != LRUCacheSet(cache, data, data)) {\
        fprintf(stderr, "put (%c %c) to cache fail. \n", data, data); \
    }else {\
        fprintf(stdout, "put (%c %c) to cache success.\n", data, data); \
    } \
} while (0);

/* 封装缓存数据存储接口 */
#define LRUCACHE_GETDATA(cache, key) \
do {\
    char data = LRUCacheGet(cache, key);\
    if ('\0' == data) \
        fprintf(stderr, "get data (Key: %c) from cache fail.\n", key);\
    else if (key == data) \
        fprintf(stdout, "get (%c, %c) from cache.\n", key, data);\
} while (0);


void testCase1(void)
{
    fprintf(stdout, "=================================\n");
    fprintf(stdout, "In testCase 1:\n");
    void *LruCache;
    if (0 != LRUCacheCreate(5, &LruCache)) {
        HANDLE_ERROR("LRUCacheCreate");
    }
    
    LRUCACHE_PUTDATA(LruCache, 'A');
    LRUCACHE_GETDATA(LruCache, 'A');
    
    LRUCACHE_PUTDATA(LruCache, 'B');
    LRUCACHE_GETDATA(LruCache, 'B');
    
    LRUCACHE_PUTDATA(LruCache, 'C');
    LRUCACHE_GETDATA(LruCache, 'C');
    LRUCachePrint(LruCache);
    
    LRUCACHE_PUTDATA(LruCache, 'D');
    LRUCACHE_GETDATA(LruCache, 'D');
    
    LRUCACHE_PUTDATA(LruCache, 'E');
    LRUCACHE_GETDATA(LruCache, 'E');
    
    LRUCACHE_PUTDATA(LruCache, 'A');
    LRUCACHE_GETDATA(LruCache, 'A');
    
    LRUCACHE_PUTDATA(LruCache, 'F');
    LRUCACHE_GETDATA(LruCache, 'F');
    
    LRUCachePrint(LruCache);
    
    LRUCACHE_PUTDATA(LruCache, 'F');
    LRUCACHE_GETDATA(LruCache, 'F');
    
    LRUCachePrint(LruCache);
    
    if (0 != LRUCacheDestory(LruCache))
        HANDLE_ERROR("LRUCacheDestory");
    
    fprintf(stdout, "In testCase 1 finished\n");
    fprintf(stdout, "=================================\n");
}

void testCase2()
{
    fprintf(stdout, "=================================\n");
    fprintf(stdout, "In testCase 2:\n");
    void *LruCache;
    if (0 != LRUCacheCreate(3, &LruCache)) {
        HANDLE_ERROR("LRUCacheCreate");
    }
    LRUCACHE_PUTDATA(LruCache, 'W');
    LRUCACHE_PUTDATA(LruCache, 'X');
    LRUCACHE_PUTDATA(LruCache, 'W');
    LRUCACHE_PUTDATA(LruCache, 'Y');
    LRUCACHE_PUTDATA(LruCache, 'Z');
    LRUCachePrint(LruCache);
    
    LRUCACHE_GETDATA(LruCache, 'Z');
    LRUCACHE_GETDATA(LruCache, 'Y');
    LRUCACHE_GETDATA(LruCache, 'W');
    LRUCACHE_GETDATA(LruCache, 'X');
    LRUCACHE_GETDATA(LruCache, 'W');
    LRUCachePrint(LruCache);
    
    /*YZWYX!*/
    LRUCACHE_PUTDATA(LruCache, 'Y');
    LRUCACHE_PUTDATA(LruCache, 'Z');
    LRUCACHE_PUTDATA(LruCache, 'W');
    LRUCACHE_PUTDATA(LruCache, 'Y');
    LRUCACHE_PUTDATA(LruCache, 'X');
    LRUCachePrint(LruCache);
    
    
    LRUCACHE_GETDATA(LruCache, 'X');
    LRUCACHE_GETDATA(LruCache, 'Y');
    LRUCACHE_GETDATA(LruCache, 'W');
    LRUCACHE_GETDATA(LruCache, 'Z');
    LRUCACHE_GETDATA(LruCache, 'Y');
    LRUCachePrint(LruCache); /*WYX*/
    
    /*XYXY!*/
    LRUCACHE_PUTDATA(LruCache, 'X');
    LRUCACHE_PUTDATA(LruCache, 'Y');
    LRUCACHE_PUTDATA(LruCache, 'X');
    LRUCACHE_PUTDATA(LruCache, 'Y');
    LRUCachePrint(LruCache);/*YX*/
    
    LRUCACHE_GETDATA(LruCache, 'Y');
    LRUCACHE_GETDATA(LruCache, 'X');
    LRUCACHE_GETDATA(LruCache, 'Y');
    LRUCACHE_GETDATA(LruCache, 'X');
    LRUCachePrint(LruCache); /*XY*/
    
    fprintf(stdout, "In testCase 2 finished\n");
    fprintf(stdout, "=================================\n");
}

void testCase3()
{
    fprintf(stdout, "=================================\n");
    fprintf(stdout, "In testCase 3:\n");
    void *LruCache;
    if (0 != LRUCacheCreate(5, &LruCache)) {
        HANDLE_ERROR("LRUCacheCreate");
    }
    LRUCACHE_PUTDATA(LruCache, 'E');
    LRUCACHE_PUTDATA(LruCache, 'I');
    LRUCACHE_PUTDATA(LruCache, 'E');
    LRUCACHE_PUTDATA(LruCache, 'I');
    LRUCACHE_PUTDATA(LruCache, 'O');
    LRUCachePrint(LruCache);
    
    LRUCACHE_GETDATA(LruCache, 'A');
    LRUCACHE_GETDATA(LruCache, 'I');
    LRUCACHE_GETDATA(LruCache, 'B');
    LRUCACHE_GETDATA(LruCache, 'O');
    LRUCACHE_GETDATA(LruCache, 'C');
    LRUCACHE_PUTDATA(LruCache, 'E');
    LRUCachePrint(LruCache);
    
    fprintf(stdout, "In testCase 3 finished\n");
    fprintf(stdout, "=================================\n");
}
int main()
{
    
    testCase1();
    testCase2();
    testCase3();
    return 0;
}


测试结果:
  • 测试用例1

  • 测试用例2

  • 测试用例3

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值