Skip to content

Commit 12ac098

Browse files
committed
feat(hikyuu/utilities): 添加LRU缓存实现及单元测试
1 parent 5e13316 commit 12ac098

File tree

2 files changed

+713
-0
lines changed

2 files changed

+713
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*
2+
* Copyright (c) 2026 hikyuu.org
3+
*
4+
* Created on: 2026-01-18
5+
* Author: fasiondog
6+
*
7+
* 注:该代码由 AI 生成
8+
*/
9+
10+
#pragma once
11+
#include <unordered_map>
12+
#include <list>
13+
#include <shared_mutex>
14+
#include <mutex>
15+
16+
namespace hku {
17+
18+
/**
19+
* @brief LRU (Least Recently Used) 缓存实现
20+
*
21+
* 该缓存实现基于哈希表和双向链表,能够在O(1)时间内完成插入、删除和访问操作。
22+
* 当缓存达到容量限制时,会自动淘汰最久未使用的元素。
23+
*
24+
* 特性:
25+
* - 线程安全:使用共享互斥锁支持并发读写
26+
* - 移动语义:支持值的移动插入,避免不必要的拷贝
27+
* - 溢出容量:允许缓存临时超出设定容量,仅当达到容量+溢出容量时才触发淘汰
28+
* - 动态调整:支持运行时调整容量和溢出容量
29+
*
30+
* @tparam KeyType 键的类型,必须支持哈希和相等比较
31+
* @tparam ValueType 值的类型,必须支持拷贝和移动操作
32+
*/
33+
template <typename KeyType, typename ValueType>
34+
class LruCache {
35+
public:
36+
using key_type = KeyType;
37+
using value_type = ValueType;
38+
using size_type = size_t;
39+
40+
/**
41+
* @brief 构造函数
42+
* @param capacity 缓存容量,默认为64,0表示无限制容量
43+
* @param overflow 溢出容量,默认为8,允许缓存临时超出设定容量
44+
* 仅当缓存大小 >= 容量+溢出容量时才触发淘汰机制
45+
*/
46+
explicit LruCache(size_type capacity = 64, size_type overflow = 8)
47+
: m_capacity(capacity), m_overflow(overflow) {}
48+
49+
~LruCache() = default;
50+
51+
/**
52+
* @brief 插入键值对
53+
* @param key 键
54+
* @param value 值
55+
*/
56+
void insert(const key_type& key, const value_type& value) {
57+
std::unique_lock<std::shared_mutex> lock(m_mutex);
58+
59+
auto it = m_cache.find(key);
60+
if (it != m_cache.end()) {
61+
// 更新已存在的键
62+
it->second->second = value;
63+
m_lru_list.splice(m_lru_list.begin(), m_lru_list, it->second);
64+
} else {
65+
// 先尝试清理空间
66+
_prune_if_needed();
67+
68+
m_lru_list.emplace_front(key, value);
69+
m_cache[key] = m_lru_list.begin();
70+
}
71+
}
72+
73+
/**
74+
* @brief 插入键值对(移动版本)
75+
* @param key 键
76+
* @param value 值(右值引用)
77+
*/
78+
void insert(const key_type& key, value_type&& value) {
79+
std::unique_lock<std::shared_mutex> lock(m_mutex);
80+
81+
auto it = m_cache.find(key);
82+
if (it != m_cache.end()) {
83+
// 更新已存在的键
84+
it->second->second = std::forward<value_type>(value);
85+
m_lru_list.splice(m_lru_list.begin(), m_lru_list, it->second);
86+
} else {
87+
// 先尝试清理空间
88+
_prune_if_needed();
89+
90+
m_lru_list.emplace_front(key, std::forward<value_type>(value));
91+
m_cache[key] = m_lru_list.begin();
92+
}
93+
}
94+
95+
/**
96+
* @brief 获取键对应的值
97+
* @param key 键
98+
* @return 存在则返回值,否则返回ValueType的默认构造值
99+
*/
100+
value_type get(const key_type& key) {
101+
std::shared_lock<std::shared_mutex> lock(m_mutex);
102+
103+
auto it = m_cache.find(key);
104+
if (it != m_cache.end()) {
105+
// 移动到列表头部(标记为最近使用)
106+
m_lru_list.splice(m_lru_list.begin(), m_lru_list, it->second);
107+
return it->second->second;
108+
}
109+
110+
return value_type{};
111+
}
112+
113+
/**
114+
* @brief 尝试获取键对应的值
115+
* @param key 键
116+
* @param value 用于接收值的引用参数
117+
* @return 如果键存在返回true,否则返回false
118+
*/
119+
bool tryGet(const key_type& key, value_type& value) {
120+
std::shared_lock<std::shared_mutex> lock(m_mutex);
121+
122+
auto it = m_cache.find(key);
123+
if (it != m_cache.end()) {
124+
// 移动到列表头部(标记为最近使用)
125+
m_lru_list.splice(m_lru_list.begin(), m_lru_list, it->second);
126+
value = it->second->second;
127+
return true;
128+
}
129+
130+
return false;
131+
}
132+
133+
/**
134+
* @brief 检查是否包含指定键
135+
* @param key 键
136+
* @return 存在返回true,否则返回false
137+
*/
138+
bool contains(const key_type& key) {
139+
std::shared_lock<std::shared_mutex> lock(m_mutex);
140+
return m_cache.find(key) != m_cache.end();
141+
}
142+
143+
/**
144+
* @brief 删除指定键
145+
* @param key 要删除的键
146+
* @return 成功删除返回true,不存在返回false
147+
*/
148+
bool remove(const key_type& key) {
149+
std::unique_lock<std::shared_mutex> lock(m_mutex);
150+
151+
auto it = m_cache.find(key);
152+
if (it != m_cache.end()) {
153+
m_lru_list.erase(it->second);
154+
m_cache.erase(it);
155+
return true;
156+
}
157+
return false;
158+
}
159+
160+
/**
161+
* @brief 清空缓存
162+
*/
163+
void clear() {
164+
std::unique_lock<std::shared_mutex> lock(m_mutex);
165+
m_cache.clear();
166+
m_lru_list.clear();
167+
}
168+
169+
/**
170+
* @brief 获取缓存当前大小
171+
* @return 当前缓存元素数量
172+
*/
173+
size_type size() const {
174+
std::shared_lock<std::shared_mutex> lock(m_mutex);
175+
return m_cache.size();
176+
}
177+
178+
/**
179+
* @brief 检查缓存是否为空
180+
* @return 空返回true,否则返回false
181+
*/
182+
bool empty() const {
183+
std::shared_lock<std::shared_mutex> lock(m_mutex);
184+
return m_cache.empty();
185+
}
186+
187+
/**
188+
* @brief 获取缓存容量
189+
* @return 缓存容量
190+
*/
191+
size_type capacity() const {
192+
return m_capacity;
193+
}
194+
195+
/**
196+
* @brief 获取缓存溢出容量
197+
* @return 缓存溢出容量
198+
*/
199+
size_type overflow() const {
200+
return m_overflow;
201+
}
202+
203+
/**
204+
* @brief 设置缓存容量
205+
* @param capacity 新的容量,0表示不限制容量
206+
*/
207+
void resize(size_type capacity) {
208+
std::unique_lock<std::shared_mutex> lock(m_mutex);
209+
m_capacity = capacity;
210+
211+
// 如果容量不为0且当前大小超过新容量加上溢出容量,需要移除多余的元素
212+
while (m_capacity != 0 && m_cache.size() > m_capacity + m_overflow) {
213+
auto last = m_lru_list.end();
214+
--last;
215+
m_cache.erase(last->first);
216+
m_lru_list.pop_back();
217+
}
218+
}
219+
220+
/**
221+
* @brief 设置缓存溢出容量
222+
* @param overflow 新的溢出容量
223+
*/
224+
void setOverflow(size_type overflow) {
225+
std::unique_lock<std::shared_mutex> lock(m_mutex);
226+
m_overflow = overflow;
227+
228+
// 如果容量不为0且当前大小超过新容量加上溢出容量,需要移除多余的元素
229+
while (m_capacity != 0 && m_cache.size() > m_capacity + m_overflow) {
230+
auto last = m_lru_list.end();
231+
--last;
232+
m_cache.erase(last->first);
233+
m_lru_list.pop_back();
234+
}
235+
}
236+
237+
private:
238+
// 如果缓存已满,移除最久未使用的项
239+
void _prune_if_needed() {
240+
if (m_capacity != 0 && m_cache.size() >= m_capacity + m_overflow) {
241+
// 移除最久未使用的项
242+
auto last = m_lru_list.end();
243+
--last;
244+
m_cache.erase(last->first);
245+
m_lru_list.pop_back();
246+
}
247+
}
248+
249+
private:
250+
// 存储键值对的双向链表,用于维护访问顺序
251+
std::list<std::pair<key_type, value_type>> m_lru_list;
252+
253+
// 哈希表,用于快速查找链表节点
254+
std::unordered_map<key_type, typename std::list<std::pair<key_type, value_type>>::iterator>
255+
m_cache;
256+
257+
// 读写锁,保护并发访问
258+
mutable std::shared_mutex m_mutex;
259+
260+
// 缓存容量,0表示不限制容量
261+
size_type m_capacity;
262+
263+
// 缓存溢出容量,在容量为0时不生效
264+
size_type m_overflow;
265+
};
266+
267+
} // namespace hku

0 commit comments

Comments
 (0)