LintCode24:LFU Cache(Java的两种实现)

说明:这是一种缓存剔除算法,在需要添加新元素而缓存满时,需要将最近最少访问的元素删除,然后将新元素添加进去

注意以下几点即可:
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);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值