前言
- 之前有写过一篇关于LRU淘汰算法的文章 LRU算法记录一下,lru算法思想是淘汰最近最少使用的元素,当一个元素在一段时间内没有访问过后,那么在之后的一段时间也极有可能不会被访问,然后当数据池满了后将其淘汰掉。
- 这篇文章打算写一下关于LFU算法思想和实现,lfu算法思想是淘汰掉最近时间使用频率最低的元素,如果有多个元素的的使用频率都是最低的,那么就淘汰掉在数据池里存在时间最短的元素。
- LRU算法和LFU算法相比,LRU是基于元素使用时间维度去淘汰数据,而LFU是基于使用频率维度去淘汰数据。一般来说lfu淘汰算法要优于lru淘汰算法,只要更适合业务场景就行。
- 之前有点懒所以就没有写lru算法的代码实现-_-。这次先把lfu算法的代码实现先弄出来,后面有机会找个时间再把lru算法的代码实现写出来吧,反正当了解其思想原理后实现起来其实都挺简单。
LFU代码实现之O(N)实现
public class LfuTest {
private final static Integer size = 3;
public static void main(String[] args) {
String[] data = {"a", "b", "c", "a", "a", "a", "a", "b", "c", "d"};
LinkedList list = new LinkedList<LfuNode>();
for (String param : data) {
test(list, param);
System.out.println("添加元素【" + param + "】,结果:" + list);
}
}
private static void test(LinkedList<LfuNode> list, String param) {
if (!list.isEmpty()) {
Iterator<LfuNode> iterator = list.iterator();
while (iterator.hasNext()) {
LfuNode next = iterator.next();
if (next.value.equals(param)) {
next.fre += 1;
iterator.remove();
for (int i = 0; i < list.size(); i++) {
if (i == list.size() - 1) {
list.addLast(next);
return;
} else if (list.get(i).fre <= next.fre
&& list.get(i + 1).fre > next.fre) {
list.add(i + 1, next);
return;
}
}
return;
}
}
if (list.size() == size) {
list.removeFirst();
}
}
list.addFirst(new LfuNode(param));
}
@Data
static class LfuNode {
private String value;
private Integer fre;
public LfuNode(String value) {
this.value = value;
this.fre = 1;
}
}
}
---------------------------------------
运行后打印结果:
添加元素【a】,结果:[LfuTest.LfuNode(value=a, fre=1)]
添加元素【b】,结果:[LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=1)]
添加元素【c】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=1)]
添加元素【a】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=2)]
添加元素【a】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=3)]
添加元素【a】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=4)]
添加元素【a】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=5)]
添加元素【b】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=2), LfuTest.LfuNode(value=a, fre=5)]
添加元素【c】,结果:[LfuTest.LfuNode(value=b, fre=2), LfuTest.LfuNode(value=c, fre=2), LfuTest.LfuNode(value=a, fre=5)]
添加元素【d】,结果:[LfuTest.LfuNode(value=d, fre=1), LfuTest.LfuNode(value=c, fre=2), LfuTest.LfuNode(value=a, fre=5)]
- 这里最后数据池(链表)内剩下的三个元素时【d,c,a】。而这里如果是使用lru算法淘汰数据,根据元素插入顺序后的结果是【b,c,d】。
LFU代码实现之O(1)实现
- 利用hashmap的快速查找和LinkedHashSet的快速添加删除元素,使其时间复杂度优化为O(1)。
public class LfuCache<K, V> {
private static Integer capacity;
private static Integer minFre;
private Map<K, LfuNode<K, V>> map1;
private Map<Integer, LinkedHashSet<LfuNode<K, V>>> map2;
public LfuCache (Integer capacity) {
this.capacity = capacity;
this.minFre = 1;
this.map1 = new HashMap<>();
this.map2 = new HashMap<>();
}
private void add (K k, V v) {
if (map1.isEmpty()) {
LfuNode node = new LfuNode(k, v);
map1.put(k, node);
LinkedHashSet set = new LinkedHashSet();
set.add(node);
map2.put(node.fre, set);
return;
}
LfuNode<K, V> node;
if ((node = map1.get(k)) != null) {
LinkedHashSet<LfuNode<K, V>> set1 = map2.get(node.fre);
set1.remove(node);
if (set1.isEmpty()) {
map2.remove(node.fre);
}
node.fre += 1;
LinkedHashSet<LfuNode<K, V>> set2 = map2.get(node.fre);
if (null == set2) {
set2 = new LinkedHashSet();
}
set2.add(node);
map2.put(node.fre, set2);
minFre = map2.keySet().stream().min(Integer::compare).get();
return;
}
node = new LfuNode(k, v);
if (map1.size() == capacity) {
LinkedHashSet<LfuNode<K, V>> set = map2.get(minFre);
final Iterator<LfuNode<K, V>> iterator = set.iterator();
LfuNode<K, V> oldNode = null;
while (iterator.hasNext()) {
oldNode = iterator.next();
iterator.remove();
break;
}
map1.remove(oldNode.key);
}
map1.put(k, node);
LinkedHashSet<LfuNode<K, V>> set1 = map2.get(node.fre);
if (set1 == null) {
set1 = new LinkedHashSet();
}
set1.add(node);
map2.put(node.fre, set1);
minFre = node.fre;
return;
}
@Data
static class LfuNode<K, V> {
private K key;
private V value;
private Integer fre;
public LfuNode (K key, V value) {
this.key = key;
this.value = value;
this.fre = 1;
}
}
public static void main (String[] args) {
String[] data = {"a", "b", "c", "a", "a", "a", "a","b", "c", "d"};
LfuCache cache = new LfuCache(3);
for (String param : data) {
cache.add(param, param);
System.out.println("-------------------------");
System.out.println("插入元素【" + param + "】,元素最小频率=" + minFre);
System.out.println(" map1 = " + JSON.toJSONString(cache.map1));
System.out.println(" map2 = " + JSON.toJSONString(cache.map2));
}
}
}
运行后打印结果:
-------------------------
插入元素【a】,元素最小频率=1
map1 = {"a":{"fre":1,"key":"a","value":"a"}}
map2 = {1:[{"fre":1,"key":"a","value":"a"}]}
-------------------------
插入元素【b】,元素最小频率=1
map1 = {"a":{"fre":1,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"}}
map2 = {1:[{"fre":1,"key":"a","value":"a"},{"fre":1,"key":"b","value":"b"}]}
-------------------------
插入元素【c】,元素最小频率=1
map1 = {"a":{"fre":1,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
map2 = {1:[{"fre":1,"key":"a","value":"a"},{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}]}
-------------------------
插入元素【a】,元素最小频率=1
map1 = {"a":{"fre":2,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
map2 = {1:[{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}],2:[{"fre":2,"key":"a","value":"a"}]}
-------------------------
插入元素【a】,元素最小频率=1
map1 = {"a":{"fre":3,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
map2 = {1:[{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}],3:[{"fre":3,"key":"a","value":"a"}]}
-------------------------
插入元素【a】,元素最小频率=1
map1 = {"a":{"fre":4,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
map2 = {1:[{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}],4:[{"fre":4,"key":"a","value":"a"}]}
-------------------------
插入元素【a】,元素最小频率=1
map1 = {"a":{"fre":5,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
map2 = {1:[{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}],5:[{"fre":5,"key":"a","value":"a"}]}
-------------------------
插入元素【b】,元素最小频率=1
map1 = {"a":{"fre":5,"key":"a","value":"a"},"b":{"fre":2,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
map2 = {1:[{"fre":1,"key":"c","value":"c"}],2:[{"fre":2,"key":"b","value":"b"}],5:[{"fre":5,"key":"a","value":"a"}]}
-------------------------
插入元素【c】,元素最小频率=2
map1 = {"a":{"fre":5,"key":"a","value":"a"},"b":{"fre":2,"key":"b","value":"b"},"c":{"fre":2,"key":"c","value":"c"}}
map2 = {2:[{"fre":2,"key":"b","value":"b"},{"fre":2,"key":"c","value":"c"}],5:[{"fre":5,"key":"a","value":"a"}]}
-------------------------
插入元素【d】,元素最小频率=1
map1 = {"a":{"fre":5,"key":"a","value":"a"},"c":{"fre":2,"key":"c","value":"c"},"d":{"fre":1,"key":"d","value":"d"}}
map2 = {1:[{"fre":1,"key":"d","value":"d"}],2:[{"fre":2,"key":"c","value":"c"}],5:[{"fre":5,"key":"a","value":"a"}]}
最后
- 在Redis的内存淘汰策略和dubbo缓存淘汰中都有支持lfu算法。
- 上述代码如果有什么错误欢迎指出,我会加以改正 -_-
- 参考 LFU算法详解