LRUCache缓存机制(Java实现及OC实现)

本文详细介绍了一种高效实现LRU(最近最少使用)缓存机制的方法,利用哈希表和双向链表结合,实现了get和put操作的O(1)时间复杂度,适用于需要快速数据访问和更新的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥已经存在,则变更其数据值;如果密钥不存在,则插入该组「密钥/数据值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?

/*思考过程
要存储key,value我们能想到的就是哈希表,HashMap<Integer, Integer> map, 那么在put的时候发现如果map.size() ==capacity,就要淘汰最近最少的key,那么如何记录淘汰的key呢,比如用List keys;List的keys就是用来存储最近比较频繁使用的key,现在假设访问了key,如果key是存在的,就将key插入到数组前面.
* 如果List为ArrayList,那么get的时候如果key是存在的也就是这个key现在被访问了,就要把key这个放到前面去,
但是数组先是要遍历查找key,然后删除,再插入到数组的第1个位置,这样就是O(n),同理如果List从ArrayList变为LinkedArray链表,但是这里也是虽然删除插入为O(N),但是在查找key的过程中要从头遍历到尾依然是O(n),所以List为ArrayList何LinkedList都不可以,所以我们要想get和put都是O(1),自己实现双向链表
整体框架如下
*
* LRUCache
*
* value为链表的节点
* map HASHMap{key: value} ---------------> Node:{key, value, prev, next} map中的value都是存储的Node且pre,next相互指向前面和后面的Node
* {key: value}
* {key: value}
* {key: value}
*
* capacity
* first // 头结点
* last // 尾结点

这里是引用* */

LRUCache缓存机制图

JAVA实现
import java.util.HashMap;

public class LRUCache {
    HashMap<Integer, Node> map;
    private int capacity;
    // 虚拟头尾结点
    private Node first;
    private Node last;

    public LRUCache(int capacity){
        map = new HashMap<>(capacity);
        first = new Node();// Node内部创建一个空的初始化方法
        last = new Node();
        first.next = last;
        last.pre = first;
        this.capacity = capacity;
    }

    public int get(int key) {
         Node node = map.get(key);
        
         if(node != null) {
             removeNode(node);
             addAfterFirst(node);
         }
        return  (node != null) ? node.value : -1;
    }

    private void removeNode(Node node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    private void addAfterFirst(Node node) {
        // node 与first.next 关系
        node.next = first.next;
        first.next.pre = node;

        // first 与 node 关系
        first.next = node;
        node.pre = first;

    }
    
    public  void put(int key, int value) {
        Node node = map.get(key);
        if (node != null) { // 新值覆盖旧值
            node.value = value;
            removeNode(node);
            addAfterFirst(node);
        }else {// 添加一对新的key value
            if (map.size() == capacity) {
                // 两件事情,第一件把key从map上删除, 第二件事情节点从双向链表中删掉
                map.remove(last.pre.key);
                removeNode(last.pre);
            }

            Node newNode = new  Node(key, value);
            map.put(key, newNode);
            addAfterFirst(newNode);
        }
    }

     private static class Node {
        public int key;
        public int value;
        public Node pre;
        public Node next;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
        public Node(){} // 保留空的构造方法给first和next使用
    }

}


OC实现

FLRUMutableDictionary.h

#import <Foundation/Foundation.h>

/**
 泛型占位名称之前的修饰符则可分为两种:__covariant(协变)和__contravariant(逆变)

 两者的区别如下:
 __covariant意为协变,意思是指子类可以强制转转换为(超类)父类,遵从的是SOLID中的L即里氏替换原则,大概可以描述为: 程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的[1]
 __contravariant意为逆变,意思是指父类可以强制转为子类。
 用我们上面自定义的泛型来解释

 */
@interface FLRUMutableDictionary<__covariant KeyType, __covariant ObjectType> : NSObject

///< 初始化最大的存储数量
- (instancetype)initWithMaxCountLRU:(NSUInteger)maxCountLRU;

#pragma mark ------- NSDictionary 方法
@property (readonly) NSUInteger count;

- (NSEnumerator*)keyEnumerator;

- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(KeyType key, ObjectType obj, BOOL *stop))block;
#pragma mark ------- NSMutableDictionary 方法


/// 根据key移除某个数据
/// @param aKey 键
- (void)removeObjectForKey:(KeyType)aKey;

/// 设置数据
/// @param anObject 值
/// @param aKey 键
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;

/// 移除所有数据
- (void)removeAllObjects;
/// 根据多个键移除数据
/// @param keyArray 键数组
- (void)removeObjectsForKeys:(NSArray<KeyType> *)keyArray;

#pragma mark ------- LRUMutableDictionary 方法
// 执行LRU算法,当访问的元素可能是被淘汰的时候,可以通过在block中返回需要访问的对象,会根据LRU机制自动添加到 dic 中
- (ObjectType)objectForKey:(KeyType)aKey returnEliminateObjectUsingBlock:(ObjectType (^)(BOOL maybeEliminate))block;

@end

FLRUMutableDictionary.m

#import "FLRUMutableDictionary.h"

#define LRU_RISK_COUNT 0 // 临界值

@interface FLRUMutableDictionary ()

/**
 * 数据存储
 */
@property (nonatomic, strong) NSMutableDictionary * dict;
/**
 * 记录对应的 key 顺序
 */
@property (nonatomic, strong) NSMutableArray * arrayForLRU;
/**
 * 设置最大内存数
 */
@property (nonatomic, assign) NSUInteger maxCountLRU;


@end

@implementation FLRUMutableDictionary

- (instancetype)initWithMaxCountLRU:(NSUInteger)maxCountLRU {
    
    if (self = [super init]) {
        _dict = [NSMutableDictionary dictionaryWithCapacity:maxCountLRU];
        _arrayForLRU = [NSMutableArray arrayWithCapacity:maxCountLRU];
        _maxCountLRU = maxCountLRU;
    }
    return self;
}
- (NSString *)description {
    
    return [NSString stringWithFormat:@"-----\n数据:%@\n键顺序:%@",self.dict,self.arrayForLRU];
}

#pragma mark - NSDictionary

- (NSUInteger)count {
    
    return [_dict count];
}
- (NSEnumerator *)keyEnumerator {
    
    return [_dict keyEnumerator];
}
- (id)objectForKey:(id)key {
    
    return [self objectForKey:key returnEliminateObjectUsingBlock:^id _Nonnull(BOOL maybeEliminate) {
        return nil;
    }];
}

- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id _Nonnull, id _Nonnull, BOOL * _Nonnull))block {
    
    [_dict enumerateKeysAndObjectsUsingBlock:block];
}
#pragma mark - NSMutableDictionary
- (void)removeObjectForKey:(id)aKey {
    
    [_dict removeObjectForKey:aKey];
    [self _removeObjectLRU:aKey];
}
- (void)removeAllObjects {
    [_dict removeAllObjects];
    [self _removeAllObjectLRU];
}
- (void)removeObjectsForKeys:(NSArray *)keyArray {
    if (keyArray.count > 0) {
        
        [_dict removeObjectsForKeys:keyArray];
        [self _removeObjectsLRU:keyArray];
    }
}
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey {
    
    BOOL isExist = ([_dict objectForKey:aKey] != nil);
    [_dict setObject:anObject forKey:aKey];
    
    if (isExist) { // 存在,调整位置顺序
        
        [self _adjustPositionLRU:aKey];
    }else { // 不存在,直接插入
        [self _addObjectLRU:aKey];
    }
}

#pragma mark - LRUMutableDictionary

- (id)objectForKey:(id)aKey returnEliminateObjectUsingBlock:(id  _Nonnull (^)(BOOL))block {
    
    id obj = [_dict objectForKey:aKey];
    if (obj) {
        [self _adjustPositionLRU:aKey];
    }
    if (block) {
        BOOL maybeElimiate = obj ? NO:YES;
        id newObj = block(maybeElimiate);
        if (newObj) {
            [self setObject:newObj forKey:aKey];
            return [_dict objectForKey:aKey];
        }
    }
    
    return nil;
}

#pragma mark - LRU

/// 判断是否需要开启RLU淘汰
/// @param count 当前存储数量
- (BOOL)_isNeedOpenLRU:(NSUInteger)count {
    NSInteger i = (_maxCountLRU - count);
    return (i < LRU_RISK_COUNT);
}
/// 添加
- (void)_addObjectLRU:(id)obj {
    
    // 添加记录新值
    if (_arrayForLRU.count == 0) {
        
        [_arrayForLRU addObject:obj];
    }else {
        [_arrayForLRU insertObject:obj atIndex:0];
    }
    // 超过了算法限制
    if ((_maxCountLRU > 0) && (_arrayForLRU.count > _maxCountLRU)) {
        [_dict removeObjectForKey:[_arrayForLRU lastObject]];
        [_arrayForLRU removeLastObject];
    }
}
/// 移动位置
- (void)_adjustPositionLRU:(id)obj {
    
    NSUInteger idx = [_arrayForLRU indexOfObject:obj];
    if (idx != NSNotFound) {
        [_arrayForLRU removeObjectAtIndex:idx];
        [_arrayForLRU insertObject:obj atIndex:0];
    }
}

- (void)_removeObjectLRU:(id)obj {
    
    [_arrayForLRU removeObject:obj];
}
- (void)_removeObjectsLRU:(NSArray *)objArr {
    
    [_arrayForLRU removeObjectsInArray:objArr];
}
- (void)_removeAllObjectLRU {
    
    [_arrayForLRU removeAllObjects];
}


@end


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值