# LRU 缓存146.LRU 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache
类:
LRUCache(int capacity)
以 正整数 作为容量 capacity
初始化 LRU 缓存int get(int key)
如果关键字 key
存在于缓存中,则返回关键字的值,否则返回 -1
。void put(int key, int value)
如果关键字 key
已经存在,则变更其数据值 value
;如果不存在,则向缓存中插入该组 key-value
。如果插入操作导致关键字数量超过 capacity
,则应该 逐出 最久未使用的关键字。函数 get
和 put
必须以 O(1)
的平均时间复杂度运行。
一开始想到是使用双向链表 ,但是双向链表的查询是 O (n) 的,O (1) 的查询自然就是哈希表 了
PUT操作
:
添加元素时判断 key 是否存在(HashMap 查找)
存在
重新赋值 将节点从原有位置抽离 节点放到链表头部 不存在
判断容量已经满了 ?(用 count
变量记录 cache 中元素数量)
没满:新建一个节点 newNode
插入链表头部
满了:
将尾部节点删除 新建节点 newNode
插入头部 HashMap 存放 < key, newNode
>
GET操作
:
从 HashMap 取节点为 null
返回 - 1 不为 null
将节点从原有位置抽离 节点放到链表头部 AC 之后考虑并发问题,LRU 本身就是操作系统的内存淘汰机制,需要考虑并发问题:如果 get 的同时
在多线程的情况下, get()
移动节点操作和 put()
整个方法加锁来保证节点的顺序正常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import java.util.HashMap;import java.util.HashMap;import java.util.concurrent.locks.LockSupport;class LRUCache { private final int capacity; private final Node head; private final Node tail; private final HashMap<Integer, Node> hash; class Node { Node next, prev; int key, val; public Node () { } public Node (int key, int val, Node prev, Node next) { this .next = next; this .prev = prev; this .key = key; this .val = val; } public Node (int key, int val) { this .key = key; this .val = val; } } public LRUCache (int capacity) { this .capacity = capacity; this .hash = new HashMap <>(); head = new Node (); tail = new Node (); head.next = tail; tail.prev = head; } public int get (int key) { Node nNode = hash.get(key); if (nNode != null ) { unLinkedNode(nNode); moveToHead(nNode); return nNode.val; } return -1 ; } public synchronized void put (int key, int value) { Node nNode = hash.get(key); if (nNode == null ) { if (hash.size() == capacity) { hash.remove(tail.prev.key); removeTail(); } nNode = new Node (key, value); hash.put(key, nNode); } else { nNode.val = value; unLinkedNode(nNode); } moveToHead(nNode); } private void moveToHead (Node cur) { head.next.prev = cur; cur.next = head.next; head.next = cur; cur.prev = head; } private void unLinkedNode (Node cur) { cur.prev.next = cur.next; cur.next.prev = cur.prev; } private void removeTail () { unLinkedNode(tail.prev); } }
# 引申LRU 全程 Least Recently Used ,即最近最少使用的页面置换算法,是服务虚拟页式存储管理服务的,实现思想是读取的数据会更新到 LRU 列表最前端, 当容量满的时候会淘汰尾部数据,来达到一个最近最少使用的数据淘汰功能。
根据局部性原理 (包括空间局部性和时间局部性,一条指令执行后在将来的一段时间内可能会再次执行(热点代码),一个数据被访问后可能会再次访问,或者这个数据相邻的数据很大概率会再次访问到)。
CSAPP 关于局部性的论述:
在一个具有良好时间局部性的程序中,被引用过的内存位置很可能在不远的将来再被多次引用。 在一个具有良好空间局部性的程序中,一个内存位置被引用了一次,那么程序很可能在不远的将来引用附近的一个内存位置。 个人思考:局部性原理是无处不在的,LRU 就是基于局部性原理的一个体现。