在缓存这个问题上吃了大苦头,因此记录实现的方法。
LRU缓存 先说用到 Node 节点和 HashMap 的方法:
这种方法事实上自定义了一个链表。
定义的 Node 节点除了 key 和 value 之外,有指向前一个节点和后一个节点的指针 。其中 head 节点和 tail 节点不做数据存储,只是被指示为头和尾,而 HashMap 的作用是存储 key 和 Node 的键值对以使得查询 Node 的效率为 O(1) 。
当进行 get() 操作时,从 map 里面取出节点并且从链表里去除节点并再插入到 head 节点后面,保证链表尾部的节点一定是最久没有访问到的。
进行 put() 操作时会插入到 head 节点后面,如果已经有了这个节点那么也一样从链表里去除节点并再插入到 head 节点后面,视作对其更新。
这样一来时间复杂度都为 O(1) 。
直接上代码:
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 public class LRUCache { class DLinkedNode { int key; int value; DLinkedNode prev; DLinkedNode next; public DLinkedNode () {} public DLinkedNode (int _key, int _value) {key = _key; value = _value;} } private Map<Integer, DLinkedNode> cache = new HashMap <Integer, DLinkedNode>(); private int size; private int capacity; private DLinkedNode head, tail; public LRUCache (int capacity) { this .size = 0 ; this .capacity = capacity; head = new DLinkedNode (); tail = new DLinkedNode (); head.next = tail; tail.prev = head; } public int get (int key) { DLinkedNode node = cache.get(key); if (node == null ) { return -1 ; } moveToHead(node); return node.value; } public void put (int key, int value) { DLinkedNode node = cache.get(key); if (node == null ) { DLinkedNode newNode = new DLinkedNode (key, value); cache.put(key, newNode); addToHead(newNode); ++size; if (size > capacity) { DLinkedNode tail = removeTail(); cache.remove(tail.key); --size; } } else { node.value = value; moveToHead(node); } } private void addToHead (DLinkedNode node) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } private void removeNode (DLinkedNode node) { node.prev.next = node.next; node.next.prev = node.prev; } private void moveToHead (DLinkedNode node) { removeNode(node); addToHead(node); } private DLinkedNode removeTail () { DLinkedNode res = tail.prev; removeNode(res); return res; } }
LFU缓存 这个方法没有自定义链表了,Node 存的除了 key 和 value 就是访问的频次 freq。
定义了两个 HashMap,有一个对应 key 和 Node 的 cache ,还有一个对应频次 freq 和 LinkedHashSet 的 freqMap 。
首先 get() 的时候会从 cache 里面根据 key 直接拿到 Node,这时候还要更新其 freq 值以及 freqMap,也就是将这个节点从 freqMap 中对应的下标下的 set 里面去除,转而添加到 freq + 1 对应下标的 LinkedHashSet 中去。
而 put() 的时候若是之前没有这个 key 那么就加入 cache 以及 freqMap 下标 1 中的 set 中,并且如果超了容量那么要根据当前频次最小的去淘汰掉一个节点;如果说之前有这个 key 那么需要更新其 freq 值以及 freqMap,也就是将这个节点从 freqMap 中对应的下标下的 set 里面去除,转而添加到 freq + 1 对应下标的 LinkedHashSet 中去(和 get 的时候差不多)。
直接上代码:
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 class LFUCache { Map<Integer, Node> cache; Map<Integer, LinkedHashSet<Node>> freqMap; int size; int capacity; int min; public LFUCache (int capacity) { cache = new HashMap <> (capacity); freqMap = new HashMap <>(); this .capacity = capacity; } public int get (int key) { Node node = cache.get(key); if (node == null ) { return -1 ; } freqInc(node); return node.value; } public void put (int key, int value) { if (capacity == 0 ) { return ; } Node node = cache.get(key); if (node != null ) { node.value = value; freqInc(node); } else { if (size == capacity) { Node deadNode = removeNode(); cache.remove(deadNode.key); size--; } Node newNode = new Node (key, value); cache.put(key, newNode); addNode(newNode); size++; } } void freqInc (Node node) { int freq = node.freq; LinkedHashSet<Node> set = freqMap.get(freq); set.remove(node); if (freq == min && set.size() == 0 ) { min = freq + 1 ; } node.freq++; LinkedHashSet<Node> newSet = freqMap.get(freq + 1 ); if (newSet == null ) { newSet = new LinkedHashSet <>(); freqMap.put(freq + 1 , newSet); } newSet.add(node); } void addNode (Node node) { LinkedHashSet<Node> set = freqMap.get(1 ); if (set == null ) { set = new LinkedHashSet <>(); freqMap.put(1 , set); } set.add(node); min = 1 ; } Node removeNode () { LinkedHashSet<Node> set = freqMap.get(min); Node deadNode = set.iterator().next(); set.remove(deadNode); return deadNode; } } class Node { int key; int value; int freq = 1 ; public Node () {} public Node (int key, int value) { this .key = key; this .value = value; } }