LFU(Least Frequently Used):淘汰最近访问频率最小的元素。
思路
设计思路:两个双向链表,横向的双向链表用来统计访问频率,纵向的双向链表,没出现过的元素出现在横向链表的头结点上,如果一直增加没有访问,则将元素全部挂到头结点的下方,纵向形成双向链表。
横向链表中统计访问的频率,如果增加某个节点的访问一次,则横向链表判断访问总次数是否存在,如果存在,则将节点放到该次数节点的下方,如果不存在则新建一个头结点,将该节点挂到下面。如果有一个次数下方不再保存节点,则将该次数在横向链表中删去。
代码
public static class Node{
public Integer key;
public Integer value;
public Integer times;
public Node up;
public Node down;
public Node(int key,int value,int times){
this.key = key;
this.value = value;
this.times = times;
}
}
public static class LFUCache{
//定义了头链表的整体结构,中间存放的内容为node类型,同时存在前后。
public static class NodeList{
public Node head;
public Node tail;
public NodeList last;
public NodeList next;
public NodeList(Node node){
head = node;
tail = node;
}
public void addNodeFromHead(Node newHead){
newHead.down = head;
head.up = newHead;
head = newHead;
}
public boolean isEmpty(){
return head ==null;
}
//删掉任何一个节点
public void deleteNode(Node node){
if(head == tail){
head =null;
tail = null;
}else{
if(node == head){
head = node.down;
head.up = null;
}else if(node == tail){
tail = node.up;
tail.down = null;
}else{
node.up.down = node.down;
node.down.up = node.up;
}
}
node.up = null;
node.down = null;
}
}
//容量
private int capacity;
private int size;
//key->node类型,不是次数
private HashMap<Integer,Node> records;
//对于任何node,都可以查到他属于哪个NodeList
private HashMap<Node,NodeList> heads;
//大结构是nodeList串起来的,现在给出双向链表的头部方便查找
private NodeList headList;
public LFUCache(int capacity){
this.capacity = capacity;
this.size = 0;
this.records = new HashMap<>();
this.heads = new HashMap<>();
headList = null;
}
public void set(int key,int value){
if(records.containsKey(key)){
Node node = records.get(key);
node.value = value;
node.times++;
//找到新set的值将当前的词频拿出来挂到新的词频上
NodeList curNodeList = heads.get(node);
move(node,curNodeList);
}else{
//如果此时已经到了阈值,则此时就需要进行数据删除,再放入新的值
if(size == capacity){
Node node = headList.tail;
headList.deleteNode(node);
//如果发现当前headlist内没有任何内容,则此时需要将当前词频删除。
modifyHeadList(headList);
records.remove(node.key);
heads.remove(node);
size--;
}
Node node = new Node(key,value,1);
//如果整个链表现在是空的,则新建一个nodelist
if(headList == null){
headList = new NodeList(node);
}else{
//最少的词频的times进行判断,
if(headList.head.times.equals(node.times)){
headList.addNodeFromHead(node);
}else{
//新建一个词频为1的链表,并且需要换头链表
NodeList newList = new NodeList(node);
newList.next = headList;
HeadList.last = newList;
headList = newList;
}
}
records.put(key,node);
heads.put(node.headList);
size++;
}
}
private void move(Node node,Nodelist oldNodeList){
oldNodelist.deleteNode(node);
//判断拿掉节点之后该链表是否为空,如果为空,则前一个链表是现在老链表的前一个,如果老链表自己还有内容,则新链表的前一个是老链表
NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.last : oldeNodeList;
//nextList是老链表的下一个list
NodeList nextList = oldNodeList.next;
//如果当前词频已经是最高了,则此时新建一个,直接跟前方的相连
if(nextList == null){
NodeList newList = new NodeList(node);
if(preList != null){
preList.next = newList;
}
newList.last = preList;
if(headList == null){
headList = newList;
}
head.put(node,newList);
}else{
//如果正好大一,则直接放到nextList上
if(nextList.head.times.equals(node.times)){
nextList.addNodeFromHead(node);
head.put(node,nextList);
}else{
//不是的话则新建一个链表
NodeList newList = new NodeList(node);
if(preList != null){
preList.next = newList;
}
newList.last = preList;
newList.next = nextList;
nextList.last = newList;
if(headList == nextList){
HeadList = newList;
}
head.put(node,newList);
}
}
}
//当链表中数据被拿出时,判断是否需要删除整个list
private boolean modifyHeadList(NodeList nodeList){
if(nodeList.isEmpty()){
//如果当前list时headList的话
if(headList == nodeList){
headList = nodeList.next;
if(headList != null){
headList.last = null;
}
}else{
//如果不是头的话,将中间该链表删除,形成新的链表
nodeList.last.next = nodeList.next;
if(nodeList.next != null){
nodeList.next.last = nodeList.last;
}
}
return true;
}
return false;
}
public int get(int key){
if(!records.containsKey(key)){
return -1;//不存在
}
Node node = records.get(key);
node.times++;
NodeList curNodeList = heads.get(node);
move(node,curNodeList);
return node.value;
}
}