LinkedHashMap源码和实现LRU算法

今天药忘吃喽~ 2024-04-19 16:32 126阅读 0赞

LinkedHashMap特别有意思,它不仅仅是在HashMap上增加Entry的双向链接,它更能借助此特性实现保证Iterator迭代按照插入顺序(以insert模式创建LinkedHashMap)或者实现LRU(Least Recently Used最近最少算法,以access模式创建LinkedHashMap)。

下面是LinkedHashMap的get方法的代码

  1. public V get(Object key) {
  2. Entry<K,V> e = (Entry<K,V>)getEntry(key);
  3. if (e == null)
  4. return null;
  5. e.recordAccess(this);
  6. return e.value;
  7. }

其中有一段:e.recordAccess(this)。下面我们进入Entry的定义

  1. void recordAccess(HashMap<K,V> m) {
  2. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
  3. if (lm.accessOrder) {
  4. lm.modCount++;
  5. remove();
  6. addBefore(lm.header);
  7. }
  8. }

这里的addBefore(lm.header)是做什么呢?再看

  1. private void addBefore(Entry<K,V> existingEntry) {
  2. after = existingEntry;
  3. before = existingEntry.before;
  4. before.after = this;
  5. after.before = this;
  6. }

从这里可以看到了,addBefore(lm.header)是把当前访问的元素挪到head的前面,即最近访问的元素被放到了链表头,如此要实现LRU算法只需要从链表末尾往前删除就可以了,多么巧妙的方法。

在看到LinkedHashMap之前,我以为实现LRU算法是在每个元素内部维护一个计数器,访问一次自增一次,计数器最小的会被移除。但是要想到,每次add的时候都需要做这么一次遍历循环,并取出最小的抛弃,在HashMap较大的时候效率很差。当然也有其他方法来改进,比如建立<访问次数,LinkedHashMap元素的key>这样的TreeMap,在add的时候往TreeMap里也插入一份,删除的时候取最小的即可,改进了效率但没有LinkedHashMap内部的默认实现来的简捷。

LinkedHashMap是什么时候删除的呢?

  1. void addEntry(int hash, K key, V value, int bucketIndex) {
  2. super.addEntry(hash, key, value, bucketIndex);
  3. // Remove eldest entry if instructed
  4. Entry<K,V> eldest = header.after;
  5. if (removeEldestEntry(eldest)) {
  6. removeEntryForKey(eldest.key);
  7. }
  8. }

在增加Entry的时候,通过removeEldestEntry(eldest)判断是否需要删除最老的Entry,如果需要则remove。注意看这里Entry eldest=header.after,记得我们前面提过LinkedHashMap还维护一个双向链表,这里的header.after就是链表尾部最后一个元素(头部元素是head.before)。
LinkedHashMap默认的removeEldestEntry方法如下

  1. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  2. return false;
  3. }

总是返回false,所以开发者需要实现LRU算法只需要继承LinkedHashMap并重写removeEldestEntry方法,下面以MyBatis的LRU算法的实现举例
keyMap = new LinkedHashMap(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;

  1. protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
  2. boolean tooBig = size() > size;
  3. if (tooBig) {
  4. eldestKey = eldest.getKey();
  5. }
  6. return tooBig;
  7. }
  8. };

开发者的子类并不需要直接操作eldest(上例中获得eldestKey只是MyBatis需要映射到Cache对象中的元素),只要根据自己的条件(一般是元素个数是否到达阈值)返回true/false即可。注意,要按照LRU排序必须在new LinkedHashMap()的构造函数的最后一个参数传入true(true代表LinkedHashMap内部的双向链表按访问顺序排序,false代表按插入顺序排序)。

在LinkedHashMap的注释里明确提到,该类在保持插入顺序、不想HashMap那样混乱的情况下,又没有像TreeMap那样的性能损耗。同时又能够很巧妙地实现LRU算法。其他方面和HashMap功能一致。有兴趣的同学可以仔细看看LinkedHashMap的实现。

LinkedHashMap实现LRU;具体实现如下:只需要重写removeEldestEntry方法即可

  1. import java.util.ArrayList;
  2. import java.util.Collection;
  3. import java.util.LinkedHashMap;
  4. import java.util.Map;
  5. import java.util.Map.Entry;
  6. public class LRULinkedMap<K, V> {
  7. /**
  8. * 最大缓存大小
  9. */
  10. private int cacheSize;
  11. private LinkedHashMap<K, V> cacheMap;
  12. public LRULinkedMap(int cacheSize){
  13. this.cacheSize = cacheSize;
  14. cacheMap = new LinkedHashMap(cacheSize, 0.75F, true){
  15. @Override
  16. protected boolean removeEldestEntry(Entry eldest) {
  17. return cacheSize + 1 >= cacheMap.size();
  18. }
  19. };
  20. }
  21. public void put(K key, V value){
  22. cacheMap.put(key, value);
  23. }
  24. public V get(K key){
  25. return cacheMap.get(key);
  26. }
  27. public Collection<Map.Entry<K, V>> getAll(){
  28. return new ArrayList<Map.Entry<K, V>>(cacheMap.entrySet());
  29. }
  30. public static void main(String[] args) {
  31. LRULinkedMap<String, Integer> map = new LRULinkedMap<>(3);
  32. map.put("key1", 1);
  33. map.put("key2", 2);
  34. map.put("key3", 3);
  35. for (Map.Entry<String, Integer> e : map.getAll()){
  36. System.out.println(e.getKey()+"====>"+e.getValue());
  37. }
  38. System.out.println("\n");
  39. map.put("key4", 4);
  40. for (Map.Entry<String, Integer> e : map.getAll()){
  41. System.out.println(e.getKey()+"====>"+e.getValue());
  42. }
  43. }
  44. }

原文转自
https://www.cnblogs.com/mengheng/p/3683137.html

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,126人围观)

还没有评论,来说两句吧...

相关阅读