가장 최근에 사용되지 않은 항목을 먼저 제거하는 캐시를 구현하세요.
LRU 캐시는 제한된 용량 내에서 데이터를 캐싱하며, 용량이 가득 찼을 때 가장 오래 전에 사용된 항목을 제거합니다.
- 해시맵과 이중 연결 리스트의 조합
- O(1) 시간 복잡도의 get/put 구현
- 캐시 교체 정책 이해
- 실무에서의 캐시 활용
| 메서드 | 설명 | 시간복잡도 |
|---|---|---|
get(key) |
키에 해당하는 값 반환, 없으면 -1 (또는 null) | O(1) |
put(key, value) |
키-값 쌍 저장, 이미 있으면 업데이트 | O(1) |
remove(key) |
키에 해당하는 항목 제거 | O(1) |
size() |
현재 저장된 항목 개수 | O(1) |
capacity() |
최대 용량 | O(1) |
clear() |
모든 항목 제거 | O(1) |
- get(key): 키가 존재하면 해당 항목을 가장 최근 사용으로 표시
- put(key, value):
- 키가 이미 존재하면 값 업데이트 + 최근 사용으로 표시
- 새 키이고 용량이 가득 찼으면 LRU 항목 제거 후 삽입
- 용량은 생성 시 고정
| 메서드 | 설명 |
|---|---|
peek(key) |
값 조회만 (사용 기록 갱신 안 함) |
containsKey(key) |
키 존재 여부 확인 |
getOldest() |
가장 오래된 항목 조회 |
getNewest() |
가장 최근 항목 조회 |
keys() |
모든 키 반환 (최근 순서) |
values() |
모든 값 반환 (최근 순서) |
LRUCache cache = new LRUCache(3); // 용량 3
cache.put(1, 100);
cache.put(2, 200);
cache.put(3, 300);
// 현재 상태: [1, 2, 3] (1이 가장 오래됨)
cache.get(1); // 100 반환, 1을 최근으로 이동
// 현재 상태: [2, 3, 1] (2가 가장 오래됨)
cache.put(4, 400); // 용량 초과! 가장 오래된 2 제거
// 현재 상태: [3, 1, 4]
cache.get(2); // -1 반환 (제거됨)
cache.get(3); // 300 반환LRUCache cache = new LRUCache(2);
cache.put(1, 100);
cache.put(2, 200);
// 상태: [1, 2]
cache.put(1, 111); // 기존 키 업데이트 + 최근으로 이동
// 상태: [2, 1]
cache.put(3, 300); // 용량 초과, 2 제거
// 상태: [1, 3]
cache.get(1); // 111 반환 (업데이트된 값)
cache.get(2); // -1 반환 (제거됨)LRUCache<String, User> userCache = new LRUCache<>(100);
userCache.put("user:123", new User("Alice"));
userCache.put("user:456", new User("Bob"));
User alice = userCache.get("user:123"); // User 객체 반환
User unknown = userCache.get("user:999"); // null 반환- 용량은 1 이상
- 키는 null 허용하지 않음 (구현에 따라)
- 값은 null 허용 가능 (구현에 따라)
- 모든 연산은 O(1) 시간 복잡도
해시맵 (HashMap): key → Node 빠른 조회
이중 연결 리스트 (Doubly Linked List): 순서 관리, O(1) 이동/삭제
HashMap
┌───────┐
│ 1 → ●─┼──→ [Node: key=1, val=100]
│ 2 → ●─┼──→ [Node: key=2, val=200]
│ 3 → ●─┼──→ [Node: key=3, val=300]
└───────┘
이중 연결 리스트 (head = 최근, tail = 오래됨)
head ←→ [1,100] ←→ [2,200] ←→ [3,300] ←→ tail
(newest) (oldest/LRU)
class Node {
int key, value;
Node prev, next;
}// 노드를 맨 앞(head)으로 이동
void moveToHead(Node node) {
removeNode(node);
addToHead(node);
}
// 노드 제거 (O(1))
void removeNode(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
// 맨 앞에 추가 (O(1))
void addToHead(Node node) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
// LRU 항목 제거 (tail 바로 앞)
Node removeTail() {
Node lru = tail.prev;
removeNode(lru);
return lru;
}- 기본 get, put 구현 (O(1))
- 용량 초과 시 LRU 제거
- 값 업데이트 시 최근 사용으로 이동
- 더미 head/tail로 엣지 케이스 처리
- 제네릭 버전 구현
- Thread-safe 버전 (선택)
- LeetCode 146. LRU Cache
- Java의
LinkedHashMap의removeEldestEntry() - Redis의 maxmemory-policy
- CPU 캐시의 LRU 정책