LeetCode 面试题 16.25. LRU缓存
设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
思路:使用双向链表实现,每个节点包括k,v数据以及前驱、后继节点。此外,链表中还有虚拟节点dummy,让每个节点的pre和next均不为空,简化操作。
为什么要保存key?
在删除链表末尾节点时,需要删除哈希表中的记录,需要查找末尾节点的key。
实现:
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
| class LRUCache { private static class Node { int key, value; Node pre, next;
Node(int k, int v) { key = k; value = v; } }
private final int capacity; private final Node dummy = new Node(0,0); private final Map<Integer,Node> keyToNode = new HashMap<>();
public LRUCache(int capacity) { this.capacity = capacity; dummy.pre = dummy; dummy.next = dummy; } public int get(int key) { Node node = getNode(key); return node != null ? node.value : -1; } public void put(int key, int value) { Node node = getNode(key); if (node != null) { node.value = value; return; } node = new Node(key, value); keyToNode.put(key,node); pushFront(node); if (keyToNode.size() > capacity) { Node backNode = dummy.pre; keyToNode.remove(backNode.key); remove(backNode); } }
private Node getNode(int key) { if (! keyToNode.containsKey(key)) return null; Node node = keyToNode.get(key); remove(node); pushFront(node); return node; }
private void remove(Node x) { x.pre.next = x.next; x.next.pre = x.pre; }
private void pushFront(Node x) { x.pre = dummy; x.next = dummy.next; x.pre.next = x; x.next.pre = x; } }
|
手写单例模式的实现
1.懒汉式
懒汉式单例是在第一次使用时创建实例,但这种方式在多线程环境下不安全,可能导致多个实例的创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Singleton { private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
1.1懒汉式的线程安全模式
通过在 getInstance() 方法加锁来实现线程安全,但由于每次都加锁,可能会影响性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Singleton { private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
1.2双重检查锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Singleton { private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
|
1.3静态内部类
这种方式是线程安全的,并且延迟加载,推荐使用。JVM 确保了静态内部类只会加载一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Singleton { private static class SingletonHelper { private static final Singleton instance = new Singleton(); }
private Singleton() {}
public static Singleton getInstance() { return SingletonHelper.instance; } }
|
2.饿汉式
这种方式在类加载时就创建实例,不会等到需要的时候才创建。
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton { private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return instance; } }
|
三个线程循环打印斐波那契数列
实现:
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
| public class FibonacciPrinter { private static final Object lock = new Object();
private static int fib1 = 0; private static int fib2 = 1; private static int count = 0;
public static void main(String[] args) { Thread thread1 = new Thread(new FibonacciTask(1)); Thread thread2 = new Thread(new FibonacciTask(2)); Thread thread3 = new Thread(new FibonacciTask(3));
thread1.start(); thread2.start(); thread3.start(); }
static class FibonacciTask implements Runnable { private int threadId;
public FibonacciTask(int threadId) { this.threadId = threadId; }
@Override public void run() { while (count < 30) { synchronized (lock) { if (count % 3 == threadId - 1) { int nextFib = fib1 + fib2; System.out.println("Thread " + threadId + " prints: " + fib1); fib1 = fib2; fib2 = nextFib; count++; lock.notifyAll(); } else { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } } } }
|