服务器内存有限,不可能持续地往内存中存入数据,就需要对内存中的数据进行淘汰处理,通过制定淘汰策略(算法)以保证内存持续可用,使内存中的数据价值最大化。
应用层的缓存淘汰算法基本都是借用操作系统的内存管理算法,以称为内存页置换算法。
内存页置换算法
常见的内存页置换算法有以下几种:
| 算法 | 中文 | 注释 | 
|---|---|---|
| Optimal算法 | 最优算法 | 一种理论上的算法,无法实现,可作为其他算法性能的参照标准 | 
| FIFO算法 | 先时先出算法 | First-In First-Out,实现简单,效率低 可能会淘汰频繁访问的页面 | 
| Second Chance算法 | 第二次机会算法 | 基本思想是与FIFO相同的,但是有所改进。 | 
| Clock算法 | 时钟算法 | 是对第二次算法的改进,一种现实可行的算法 有简单的Clock置换算法,改进型Clock置换算法 | 
| LRU算法 | 最近最旧未使用算法 | Least Recent Used,基于时间维度 使用时间戳标记,遍历逐出最早的数据; 或按最近访问排序,最近访问在队头,逐出队尾 | 
| LFU算法 | 最近最少使用算法 | Least Frequently Used,基于次数维度 使用记数器,按次数排序或遍历,逐出次数最小的数据 | 
| PBA算法 | 页面缓冲算法 | Page Buffering Algorithm,降低了页面换进、换出的频率 使磁盘I/O的操作次数大大减少 | 
FIFO-先进先出
FIFO(First In First Out):先进先出,先进入的缓存数据先淘汰,使用队列来实现。核心思想是假定刚刚加入的数据总会被访问到。
FIFO 是典型的队列数据结构的特点,队头弹出,队尾插入。
特点:命中率低,复杂度相对比较简单,存储成本地,速度快,但使用价值不高,缓存设计不可能把不是本次想要的数据都从队头弹出。
Mybatis 的 FIFO 缓存策略(FifoCache)就是基于双端队列(Deque,底层是链表 LinkedList)实现的。
数组队列实现
/**
 * @author gxing
 * @desc
 * @date 2021/1/21
 */
public class ArrayQueue {
    /**
     * 最大容量
     */
    private int maxSize;
    /**
     * 数组
     */
    private Object[] array;
    /**
     * 头标记
     */
    private int front;
    /**
     * 尾索引
     */
    private int rear;
    //构建函数
    public ArrayQueue(int maxSize) {
        this.maxSize = maxSize;
        array = new Object[this.maxSize];
        front = -1;
        rear = -1;
    }
    /**
     * 判空
     *
     * @return
     */
    public boolean isEmpty() {
        return front == rear;
    }
    /**
     * 判满
     *
     * @return
     */
    public boolean isFull() {
        return rear == maxSize - 1;
    }
    /**
     * 队尾插入
     *
     * @param e
     */
    public void add(Object e) {
        if (isFull()) {
            throw new BusinessException("the array queue is full!");
        }
        rear++;
        array[rear] = e;
    }
    /**
     * 队头弹出
     *
     * @return
     */
    public Object getHead() {
        if (isEmpty()) {
            throw new RuntimeException("the array queue is empty!");
        }
        Object result = array[0];
        // 弹出头,后面元素全部前移
//        System.arraycopy(array, 1, array, 0, array.length - 1);
        for (int i = 0; i < array.length - 1; i++) {
            array[i] = array[i + 1];
        }
        // 最后元素置空
        array[rear] = null;
        // 最后元素索引前移一位
        rear--;
        return result;
    }
}
LRU-最近最久未使用
LRU(Least Recently User):最近最旧未使用(基于时间维度),是一种常用的页面置换算法,选择最近最少使用的页面予以淘汰。
该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
LRU 的核心思想:如果数据最近被访问过,那么将来被访问的几率也更高。
总体思路:
- 一种是添加被访问时的时间标记,以此时间排序或遍历所有,值越小表示越早,在删除操作时移除此元素。
- 采用链表来实现,新数据插入到链表头,被访问的移到链表头,在删除操作时删除链表尾。
单向链表实现
实现逻辑
- 插入:每次插入新数据时,将新数据插入到链表的头部
- 访问:每次将被访问(命中)的数据移到链表头部
- 移除:当链表满时,就将链表尾部的数据丢弃

优缺点
- 优点:非常适合热点数据,命中率高;插入数据快(总是插入到头),复杂度O(1)。
- 缺点:查询和删除慢慢(复杂度为 O(n)),需要遍历链表。
- 链表实现是将新数据插入到链表头,假如其数据大小接近缓存容量且是周期性数据,则会污染缓存(独占缓存,将有效缓存数据全部挤出)。
Java实现
节点:
/**
 * 单向链表节点
 */
public class Node<T> {
    String key;
    T value;
    Node<T> next;
    public Node(String key, T value) {
        this.key = key;
        this.value = value;
    }
}
单向链表:
/**
 * @desc 单向链表缓存
 * @date 2021/1/19
 */
public class LinkedListCache<T> {
    // 缓存大小,默认为8
    private int capacity = 8;
    // 链表长度
    private int size;
    // 头
    private Node<T> head;
    // 尾
    private Node<T> last;
    public LinkListCache() {
    }
    public LinkListCache(int capacity) {
        this.capacity = capacity;
    }
    /**
     * 插入
     * 最近使用插入到链表头
     *
     * @param key
     * @param value
     */
    public void insert(String key, T value) {
        Node<T> temp = head;
        if (size == 0) {
            // 缓存为空
            Node<T> node = new Node<>(key, value);
            head = node;
            last = node;
        } else if (size < capacity) {
            // 缓存还有空间
            head = new Node<>(key, value);
            head.next = temp;
        } else {
            // 移除链尾
            this.removeLast();
            // 插入链头
            head = new Node<>(key, value);
            head.next = temp;
        }
        size++;
    }
    /**
     * 查询
     *
     * @param key
     * @return
     */
    public T get(String key) {
        Node<T> preNode = null;
        Node<T> temp = head;
        Node<T> first = head;
        for (int i = 0; i < size; i++) {
            if (temp.key.equals(key)) {
                if (temp.equals(head)) {
                    // 链表头
                    return temp.value;
                } else if (temp.next == null) {
                    // 链表尾
                    head = temp;
                    head.next = first;
                    preNode.next = null;
                    last = preNode;
                } else {
                    // 链表中
                    Node<T> next = temp.next;
                    head = temp;
                    head.next = first;
                    preNode.next = next;
                }
                return temp.value;
            }
            preNode = temp;
            temp = temp.next;
        }
        return null;
    }
    /**
     * 删除链表尾(最近最少使用)
     */
    public void removeLast() {
        Node<T> temp = head;
        // 倒数第三个, next就是倒数第二个
        for (int i = 0; i < size - 2; i++) {
            temp = temp.next;
        }
        // 倒数第二个置为队尾, next置空
        temp.next = null;
        last = temp;
        size--;
    }
}
哈希+单向链表实现
实现逻辑
哈希 + 链表的实现逻辑:用哈希表来存储数据,用链表来记录最近使用的数据。
- 插入:每次插入时,判断 Hash 表中是否存在,不存在则直接存入Hash表并插入到链表头中;
 如果存在,则查询链表并将其移到链表头中。
- 访问:判断 Hash 表中是否存在,存在则取出值(节点),并将该节点移到链表头,返回值。
- 移除:当链表满时,就将丢弃链表尾的数据。
优缺点
- 优点:非常适合热点数据,命中率高;插入数据快(总是插入到头),复杂度O(1);查询快,直接从 Hash 表中取值。
- 缺点:查询和删除慢,因为还要操作链表,Hash表的查询优势就体现不出来,要到单向链表中查询和删除,还是需要遍历链表。
 该缺点可以通过双向链表来优化。双向链表的删除就不需要遍历链表。
Java实现
节点:
/**
 * 单向链表节点
 */
public class Node<T> {
    String key;
    T value;
    Node<T> next;
    public Node(String key, T value) {
        this.key = key;
        this.value = value;
    }
}
单向链表:
/**
 * @desc 单向链表
 * @date 2021/1/19
 */
public class LinkedList<T> {
    // 头
    private Node<T> head;
    // 尾
    private Node<T> last;
    /**
     * 插到链表头
     *
     * @param node
     */
    public void addToHead(Node<T> node) {
        if (head == null) {
            head = node;
            last = node;
        } else {
            Node<T> temp = head;
            head = node;
            head.next = temp;
        }
    }
    /**
     * 移到链表头
     *
     * @param node
     */
    public void moveToHead(Node<T> node) {
        Node<T> preNode = null;
        Node<T> temp = head;
        Node<T> first = head;
        for (; ; ) {
            // 遍历查找
            if (temp.equals(node)) {
                if (temp.equals(head)) {
                    // 头
                    break;
                } else if (temp.next == null) {
                    // 链表尾
                    head = temp;
                    head.next = first;
                    preNode.next = null;
                    last = preNode;
                    break;
                } else {
                    // 链表中
                    Node<T> next = temp.next;
                    head = temp;
                    head.next = first;
                    preNode.next = next;
                    break;
                }
            }
            preNode = temp;
            temp = temp.next;
            if (temp == null) {
                break;
            }
        }
    }
    /**
     * 删除链表尾(最近最少使用)
     */
    public void removeLast() {
        if (last == null) {
            return;
        }
        if (head == last) {
            head = null;
            last = null;
            return;
        }
        Node<T> temp = head;
        Node<T> preNode = head;
        // 倒数第三个, next就是倒数第二个
        for (; ; ) {
            if (temp.next == null) {
                last = preNode;
                preNode.next = null;
                break;
            }
            preNode = temp;
            temp = temp.next;
        }
    }
}
缓存实现:
/**
 * @desc 哈希表+链表
 * @date 2021/1/20
 */
public class HashLinkedListCache<T> {
    /**
     * 最大容量
     */
    private int capacity = 8;
    HashMap<String, Node<T>> map;
    LinkedList<T> list;
    public HashLinkedListCache() {
        this.list = new LinkedList<>();
        this.map = new HashMap<>();
    }
    public HashLinkedListCache(int capacity) {
        this.list = new LinkedList<>();
        this.map = new HashMap<>(capacity);
        this.capacity = capacity;
    }
    /**
     * 插入
     *
     * @param key
     * @param value
     */
    public void put(String key, T value) {
        // 存在
        if (map.containsKey(key)) {
            Node<T> node = map.get(key);
            node.value = value;
            list.moveToHead(node);
        } else {
            // 不存在
            Node<T> node = new Node<>(key, value);
            if (!(map.size() < capacity)) {
                list.removeLast();
            }
            map.put(key, node);
            list.addToHead(node);
        }
    }
    /**
     * 查询
     *
     * @param key
     * @return
     */
    public T get(String key) {
        Node<T> node = map.get(key);
        if (node != null) {
            list.moveToHead(node);
            return node.value;
        }
        return null;
    }
}
哈希+双向链表实现
实现逻辑
哈希 +双向链表的实现逻辑:用哈希表来存储数据,用链表来记录最近使用的数据。是在 哈希+单向链表的基础上。
借助双向链表的特性,优化移动链表中元素到头时的操作,不用再遍历链表查询。
- 插入:每次插入时,判断 Hash 表中是否存在,不存在则直接存入Hash表并插入到链表头中;
 如果存在,将其移到链表头中,设置前后节点的指针。
- 访问:判断 Hash 表中是否存在,存在则取出值(节点),并将该节点移到链表头,返回值。
- 移除:当链表满时,就将丢弃链表尾的数据。
优缺点
- 优点:非常适合热点数据,命中率高 - 插入(总是插入到头),复杂度 - O(1)- 查询快,直接从 Hash 表中取值 - 删除快,直接删除尾节点,重置上一节点为尾节点 
- 缺点:无 
Java实现
节点:
/**
 * 双向链表节点
 */
public class Node<T> {
    String key;
    T value;
    Node<T> preNode;
    Node<T> nextNode;
    public Node(String key, T value) {
        this.key = key;
        this.value = value;
    }
}
双向链表:
/**
 * @desc 双向链表
 * @date 2021/1/19
 */
public class DoubleLinkList<T> {
    // 头
    private Node<T> head;
    // 尾
    private Node<T> last;
    /**
     * 插到链表头
     *
     * @param node
     */
    public void addToHead(Node<T> node) {
        if (head == null) {
            head = node;
            last = node;
            last.preNode = head;
        } else {
            Node<T> temp = head;
            head = node;
            head.nextNode = temp;
            temp.preNode = head;
        }
    }
    /**
     * 移到链表头
     *
     * @param node
     */
    public void moveToHead(Node<T> node) {
        if (node.equals(head)) {
            return;
        }
        Node<T> temp = head;
        Node<T> preNode = node.preNode;
        Node<T> nextNode = node.nextNode;
        head = node;
        head.preNode = null;
        head.nextNode = temp;
        temp.preNode = head;
        preNode.nextNode = nextNode;
    }
    /**
     * 删除链表尾(最近最少使用)
     */
    public void removeLast() {
        if (last == null) {
            return;
        }
        if (head == last) {
            head = null;
            last = null;
            return;
        }
        last = last.preNode;
        last.nextNode = null;
    }
}
缓存实现:
/**
 * @desc 哈希表+链表
 * @date 2021/1/20
 */
public class HashLinkedListCache<T> {
    /**
     * 最大容量
     */
    private int capacity = 8;
    HashMap<String, Node<T>> map;
    DoubleLinkList<T> list;
    public HashLinkedListCache() {
        this.list = new DoubleLinkList<>();
        this.map = new HashMap<>();
    }
    public HashLinkedListCache(int capacity) {
        this.list = new DoubleLinkList<>();
        this.map = new HashMap<>(capacity);
        this.capacity = capacity;
    }
    /**
     * 插入
     *
     * @param key
     * @param value
     */
    public void put(String key, T value) {
        // 存在
        if (map.containsKey(key)) {
            Node<T> node = map.get(key);
            node.value = value;
            list.moveToHead(node);
        } else {
            // 不存在
            Node<T> node = new Node<>(key, value);
            if (!(map.size() < capacity)) {
                list.removeLast();
            }
            map.put(key, node);
            list.addToHead(node);
        }
    }
    /**
     * 查询
     *
     * @param key
     * @return
     */
    public T get(String key) {
        Node<T> node = map.get(key);
        if (node != null) {
            list.moveToHead(node);
            return node.value;
        }
        return null;
    }
}
LinkedHashMap实现
JDK 的 LinkedHashMap 继承自 HashMap,拥有 HashMap 的所有特性,同时 LinkedHashMap 给节点增加了 head  和 tail 指针,用于实现双向链表。
注意:LinkedHashMap 不是线程安全的,用作缓存的话需要加同步处理。最简单粗暴的操作是重写 HashMap 方法,所有方法上加锁。
    /**
     * The head (eldest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> head;
    /**
     * The tail (youngest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> tail;
最近访问排序
LinkedHashMap 默认是按插入的顺序保存数据,新的数据插入到双向链表尾部。
Mybatis 的 LRU 缓存策略(LruCache)就是基于 LinkedHashMap 实现的。
可能通过构造方法的 accessOrder 参数来控制是否开启访问排序;设置为 true 开启访问排序,则最新的数据放到双向链表尾部。
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}
// 使用
LinkedHashMap<String, Object> cache = new LinkedHashMap<>(6, 0.75f, true);
移除最旧元素
LinkedHashMap 提供了移除最旧元素方法,但默认是没有开启的,如果一直往 LinkedHashMap 中添加元素就会一直触发自动扩容,若启用了移除旧元素方法,map 满了就会移除旧元素,触发自动扩容则是有限的。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}
自定义一个 Map 继承 LinkedHashMap ,重写 removeEldestEntry 方法,示例:
// 维护100个元素,然后在每次添加新元素时删除最老的元素,从而保持100个元素的稳定状态。
private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_ENTRIES;
}
该方法由插入新元素的 put 和 putAll 方法调用。给实现者提供了在每次添加新元素时删除最旧元素的机会, 如果将此 map 用作缓存,这非常有用:它允许 map 通过删除最旧的元素来减少内存消耗。
public class LruLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
    private int size;
    public LruLinkedHashMap(int initialCapacity,
                            float loadFactor,
                            boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
        this.size = initialCapacity;
    }
    /**
     * 重写LinkedHashMap的removeEldestEntry方法
     * 移除最旧的元素(最近最少使用)
     *
     * @param eldest
     * @return
     */
    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        if (size() > size) {
            return true;
        }
        return false;
    }
}
HashMap+LinkedList
借助 JDK 自带的 HashMap 和 LinkedList 实现。
LinkedList
LinkedList 是 List 和 Deque 接口的双向链表实现,实现了 list 所有的可选操作。
LinkedList 定义一头节点,尾节点,新增头尾节点,查询头尾节点,移除头尾节点的操作。
JDK 自带 LinkedList
package java.util;
import java.util.function.Consumer;
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    transient int size = 0;
    /**
     * 头节点
     */
    transient Node<E> first;
    /**
     * 尾节点
     */
    transient Node<E> last;
    /**
     * 查询头节点
     */
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    /**
     * 查询尾结点
     */
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
    /**
     * 移除头节点
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    /**
     * 移除尾节点
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    //.......省略........
}
缓存实现
import java.util.HashMap;
import java.util.LinkedList;
/**
 * @desc
 * @date 2021/1/22
 */
public class LRUHashLinkedListCache<K, V> {
    private HashMap<K, V> map;
    private LinkedList<K> list;
    private int capacity;
    public LRUHashLinkedListCache(int capacity) {
        this.map = new HashMap<>(capacity);
        this.list = new LinkedList<>();
        this.capacity = capacity;
    }
    /**
     * 查询
     *
     * @param key
     * @return
     */
    public V get(K key) {
        if (map.containsKey(key)) {
            V value = map.get(key);
            list.remove(key);
            list.addLast(key);
            return value;
        }
        return null;
    }
    /**
     * 新增
     *
     * @param key
     * @param value
     */
    public void put(K key, V value) {
        if (map.containsKey(key)) {
            map.remove(key);
            list.remove(key);
        } else if (map.size() >= capacity) {
            K oldKey = list.removeFirst();
            map.remove(oldKey);
        }
        map.put(key, value);
        list.addLast(key);
    }
}
LFU-最近最少使用
Least Frequently Used:最近最少使用算法(基于使用频次),该算法淘汰最近时期内使用频次最小的数据。该算法为个数据增加个记数据,用于记录被访问的次数,当缓存满时,淘汰次数最小的数据。
实现方案
基于双向链表实现,在链表头插入元素,然后每插入一次计数一次;接着按照次数重新排序链表,如果次数相同的话,按照插入时间排序,然后淘汰链表尾部的数据。
实现示例
节点:
public class Node<T> {
    String key;
    T data;
    int times;
    Node<T> preNode;
    Node<T> nextNode;
    public Node(String key, T data) {
        this.key = key;
        this.data = data;
    }
}
双向链表缓存:
/**
 * @desc
 * @date 2021/1/22
 */
public class LFUDoubleLinkedListCache<T> {
    private static final int DEFAULT_SIZE = 8;
    private Node<T> head;
    private Node<T> tail;
    private int capacity = DEFAULT_SIZE;
    private int size = 0;
    public LFUDoubleLinkedListCache() {
    }
    public LFUDoubleLinkedListCache(int capacity) {
        this.capacity = capacity;
    }
    /**
     * 添加
     *
     * @param node
     */
    public void add(Node<T> node) {
        // 为空
        if (isEmpty()) {
            head = node;
            tail = node;
            size++;
            return;
        }
        // 只有一个
        if (size == 1) {
            if (head.key.equals(node.key)) {
                head.data = node.data;
                head.times++;
            } else if (head.times <= node.times) {
                Node<T> temp = head;
                head = node;
                head.preNode = null;
                head.nextNode = temp;
                temp.preNode = head;
                tail = temp;
                tail.nextNode = null;
            } else {
                head.nextNode = node;
                tail = node;
            }
            size++;
            return;
        }
        Node<T> node1 = get(node.key);
        if (node1 != null) {
            node1.data = node.data;
            return;
        }
        Node<T> target = getTargetNode(node);
        if (target == head) {
            Node<T> temp = head;
            head = node;
            head.preNode = null;
            head.nextNode = temp;
            temp.preNode = head;
        } else if (target == tail) {
            if (tail.times > node.times) {
                Node<T> temp = tail;
                tail = node;
                tail.preNode = temp;
                tail.nextNode = null;
                temp.nextNode = tail;
            } else {
                Node<T> preNode = tail.preNode;
                preNode.nextNode = node;
                node.preNode = preNode;
                node.nextNode = tail;
                tail.preNode = node;
                tail.nextNode = null;
            }
        } else {
            Node<T> preNode = target.preNode;
            Node<T> nextNode = target.nextNode;
            preNode.nextNode = node;
            node.preNode = preNode;
            node.nextNode = nextNode;
            nextNode.preNode = node;
        }
        if (isFull()) {
            Node<T> preNode = tail.preNode;
            tail = preNode;
            tail.nextNode = null;
            tail = preNode;
        } else {
            size++;
        }
    }
    /**
     * 查询并重排序
     *
     * @param key
     * @return
     */
    public Node<T> get(String key) {
        Node<T> temp = head;
        for (; ; ) {
            if (temp == null) {
                return null;
            }
            if (temp.key.equals(key)) {
                temp.times++;
                this.resetSort(temp);
                return temp;
            } else {
                temp = temp.nextNode;
            }
        }
    }
    /**
     * 按使用次数重排序
     *
     * @param node
     */
    private void resetSort(Node<T> node) {
        // 自己就是头,不变
        if (node == head) {
            return;
        }
        // 自己是尾,就近判断
        if (node == tail) {
            // 前一节点仍大于自己
            if (node.preNode.times > node.times) {
                return;
            }
            // 查找替换的目标节点
            Node<T> target = this.getTargetNode(node);
            if (target == head) {
                tail = node.preNode;
                tail.nextNode = null;
                Node<T> temp = head;
                head = node;
                head.preNode = null;
                head.nextNode = temp;
                temp.preNode = head;
            } else {
                tail = node.preNode;
                tail.nextNode = null;
                Node<T> preNode = target.preNode;
                preNode.nextNode = node;
                node.preNode = preNode;
                node.nextNode = target;
                target.preNode = node;
            }
        } else {
            // 非头非尾,就是中间节点
            // 前一节点仍大于自己
            if (node.preNode.times > node.times) {
                return;
            }
            // 查找替换的目标节点
            Node<T> target = this.getTargetNode(node);
            if (target == head) {
                Node<T> temp = head;
                Node<T> preNode = node.preNode;
                Node<T> nextNode = node.nextNode;
                head = node;
                head.preNode = null;
                head.nextNode = temp;
                temp.preNode = head;
                preNode.nextNode = nextNode;
                nextNode.preNode = preNode;
            } else {
                Node<T> preNode = node.preNode;
                Node<T> nextNode = node.nextNode;
                Node<T> targetPre = target.preNode;
                targetPre.nextNode = node;
                node.preNode = targetPre;
                node.nextNode = target;
                target.preNode = node;
                preNode.nextNode = nextNode;
            }
        }
    }
    /**
     * 获取要置换的节点
     *
     * @param node
     * @return
     */
    private Node<T> getTargetNode(Node<T> node) {
        Node<T> temp = head;
        for (; ; ) {
            if (temp == tail) {
                return tail;
            }
            if (temp.times <= node.times) {
                // 返回最近的次数小的节点
                return temp;
            } else {
                temp = temp.nextNode;
            }
        }
    }
    private boolean isEmpty() {
        return size == 0;
    }
    private boolean isFull() {
        return size >= capacity;
    }
}
参考资料
注意:本文归作者所有,未经作者允许,不得转载
 
 
            