说明:这是一种缓存剔除算法,在需要添加新元素而缓存满时,需要将最近最少访问的元素删除,然后将新元素添加进去
注意以下几点即可:
1、重新set一个已经存在的值时,要更新其值和使用频次,并重置新的访问时间
2、每访问一个值,如果该值存在,都要将其使用频次加1,并更新访问时间
3、缓存满时,最先剔除访问频次最低的元素,如果有多个相同的访问频次最低的元素,删除其最近访问时间最小的(最久没有访问它)
Java实现一:
常规遍历实现(每次set新值而缓存已满时,都进行一次全遍历,找到访问频次最低的元素删除),这种实现方式最简单,但性能较差
代码如下:
class LFUCache {
private class Tuple {
int value;
int use_count;//使用计数
long last_acess_time;//最近访问时间
Tuple(int value, int count, long time) {
this.value = value;
this.use_count = count;
this.last_acess_time = time;
}
}
private int capacity;
private Map<Integer, Tuple> lfumap;
/*
* @param capacity: An integer
*/
public LFUCache(int capacity) {
this.capacity = capacity;
lfumap = new HashMap<>(capacity, 1.0f);
}
/*
* @param key: An integer
* @param value: An integer
* @return: nothing
*/
public void set(int key, int value) {
if (lfumap.containsKey(key)) {
Tuple tuple = lfumap.get(key);
// 增加其使用计数值
tuple.use_count++;
tuple.value = value;
// 更新其最近访问时间
tuple.last_acess_time = System.nanoTime();
} else if (lfumap.size() >= capacity) {
int min_count = Integer.MAX_VALUE;
long min_time = Long.MAX_VALUE;
Integer key_to_remove = null;
Iterator<Entry<Integer, Tuple>> iterator = lfumap.entrySet().iterator();
while (iterator.hasNext()) {
Entry<Integer, Tuple> entry = iterator.next();
Tuple tuple = entry.getValue();
if (tuple.use_count < min_count || (tuple.use_count == min_count &&
tuple.last_acess_time < min_time)) {
key_to_remove = entry.getKey();
min_count = tuple.use_count;
min_time = tuple.last_acess_time;
}
}
lfumap.remove(key_to_remove);
lfumap.put(key, new Tuple(value, 0, System.nanoTime()));
} else {
lfumap.put(key, new Tuple(value, 0, System.nanoTime()));
}
}
/*
* @param key: An integer
* @return: An integer
*/
public int get(int key) {
Tuple tuple;
if (null != (tuple = lfumap.get(key))) {
tuple.use_count++;
tuple.last_acess_time = System.nanoTime();
System.out.println(tuple.value);
return tuple.value;
}
System.out.println(-1);
return -1;
}
}
Java实现二:
一个小顶堆作为缓存剔除的缓存池,另外通过一个哈希Map记录每个键在堆中的位置,这个实现就相对复杂很多,主要思想如下
1、访问或设置一个已存在的元素,就更新该元素的值、使用频次、访问之间,然后从该值开始向下修正
2、缓存满需要删除元素时直接用堆尾元素覆盖堆顶元素,再从堆顶向下修正
3、添加新元素都添加在堆尾,然后向上修正
4、每一次的交换、删除操作均要更新对应索引值
代码如下:
class Heap {
public class LfuNode implements Comparable<LfuNode> {
int key; // 键
int value; // 值
int use_count; //使用计数
long last_access_time; // 最近访问时间
public LfuNode(int key, int value, int count, long time) {
this.key = key;
this.value = value;
this.use_count = count;
this.last_access_time = time;
}
@Override
public int compareTo(LfuNode other) {
if (this.use_count < other.use_count) {
return -1;
} else if (this.use_count == other.use_count) {
return this.last_access_time < other.last_access_time ? -1 : (this.last_access_time == other.last_access_time ? 0 : 1);
} else {
return 1;
}
}
}
private int capacity;
private List<LfuNode> heap; // 用于存放总大小为capacity的元素,构建一个最小堆
private Map<Integer, Integer> map; // 用于存放key对应的值在list中的index
public Heap(int capacity) {
this.capacity = capacity;
this.heap = new ArrayList<>(capacity);
this.map = new HashMap<>(capacity, 1.0f);
}
public void set(int key, int value) {
Integer index;
// 已经存在该元素
if (null != (index = map.get(key))) {
LfuNode node = heap.get(index);
node.value = value;
node.use_count++;
node.last_access_time = System.nanoTime();
//调整之后要往后边放,所以向下修正堆
fixDown(index);
} else if (map.size() >= capacity) {
removeFirst();
addNew(key, value);
} else {
addNew(key, value);
}
}
public int get(int key) {
if (map.containsKey(key)) {
int index = map.get(key);
LfuNode node = heap.get(index);
node.use_count++;
node.last_access_time = System.nanoTime();
//调整之后要往后边放,所以向下修正堆
fixDown(index);
return node.value;
}
return -1;
}
public void addNew(int key, int value) {
// 放入新元素,向上修正
int size = heap.size();
heap.add(new LfuNode(key, value, 0, System.nanoTime()));
map.put(key, size);
fixUp(size);
}
/**
* 移除小顶堆的堆顶元素
*/
public void removeFirst() {
// 这里主要是针对容量为1时的特殊情况考虑
if (heap.size() == 1) {
LfuNode node = heap.remove(0);
map.remove(node.key);
return;
}
// 删除最不频繁使用的元素(堆顶)
LfuNode hnode = heap.get(0);
// 同时删除对应key的索引
map.remove(hnode.key);
// 队尾元素移至第一位并向下调整
int lastindex = heap.size() - 1;
LfuNode tnode = heap.get(lastindex);
heap.set(0, tnode);
map.put(tnode.key, 0);
// 删除堆尾元素
heap.remove(lastindex);
// 从堆顶向下修正
fixDown(0);
}
/**
* 主要是对堆底元素放入堆顶以及节点值更新时操作
* 跟其两个子节点比较,如果符合交换条件,跟其中较小的一个交换
* @param pindex
*/
public void fixDown(int pindex) {
LfuNode pNode = heap.get(pindex);
int son = 2 * pindex + 1;
int size = heap.size();
while (son < size) {
int right_son = son + 1;
// 存在右子节点,取左右较小节点
if (right_son < size && heap.get(son).compareTo(heap.get(right_son)) > 0)
son++;
LfuNode sNode = heap.get(son);
// 和父节点进行比较
if (pNode.compareTo(sNode) > 0) {
swap(pNode, sNode, pindex, son);
pindex = son;
pNode = heap.get(pindex);
son = 2 * son + 1;
} else {
break;
}
}
}
/**
* 针对新插入元素进行操作,往上修正
* 跟其父节点比较
* @param sindex
*/
public void fixUp(int sindex) {
int pindex = (sindex - 1) / 2;
while (sindex > 0) {
LfuNode pNode = heap.get(pindex);
LfuNode sNode = heap.get(sindex);
if (sNode.compareTo(pNode) < 0) {
swap(pNode, sNode, pindex, sindex);
sindex = pindex;
pindex = (sindex - 1) / 2;
} else {
break;
}
}
}
public void swap(LfuNode pNode, LfuNode sNode, int parent_index, int son_index) {
// 设置对应位置的值
heap.set(parent_index, sNode);
heap.set(son_index, pNode);
// 更新索引
map.put(pNode.key, son_index);
map.put(sNode.key, parent_index);
}
}