您的位置:首页 > 其它

LRUCache

2016-07-08 13:58 549 查看
//-----------------------------------------------------------------------------------

转出处:http://blog.csdn.net/chdjj

作者:RowandJJ

//-----------------------------------------------------------------------------------

LruCache内部维护的就是一个LinkedHashMap。

下面开始分析LruCache。

注:下面LruCache源码来自support.v4包。

首先是这个类的成员变量:

[java] view
plain copy

 





private final LinkedHashMap<K, V> map;  

   /** Size of this cache in units. Not necessarily the number of elements. */  

   private int size;//当前大小  

   private int maxSize;//最大容量  

   private int putCount;//put次数  

   private int createCount;//create次数  

   private int evictionCount;//回收次数  

   private int hitCount;//命中次数  

   private int missCount;//丢失次数  

LinkedHashMap的初始化放在构造器中:

[java] view
plain copy

 





public LruCache(int maxSize) {  

        if (maxSize <= 0) {  

            throw new IllegalArgumentException("maxSize <= 0");  

        }  

        this.maxSize = maxSize;  

        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);  

    }  

这里将LinkedHashMap的accessOrder设置为true。
接下来看两个最重要的方法,put和get。首先是put方法:

[java] view
plain copy

 





public final V put(K key, V value) {  

        if (key == null || value == null) {//键值不允许为空  

            throw new NullPointerException("key == null || value == null");  

        }  

        V previous;  

        synchronized (this) {//线程安全  

            putCount++;  

            size += safeSizeOf(key, value);  

            previous = map.put(key, value);  

            if (previous != null) {//之前已经插入过相同的key  

                size -= safeSizeOf(key, previous);//那么减去该entry的容量,因为发生覆盖  

            }  

        }  

        if (previous != null) {  

            entryRemoved(false, key, previous, value);//这个方法默认空实现  

        }  

        trimToSize(maxSize);//若容量超过maxsize,将会删除最近很少访问的entry  

        return previous;  

    }  

put方法无非就是调用LinkedHashMap的put方法,但是这里在调用LinkedHashMap的put方法之前,判断了key和value是否为空,也就是说LruCache不允许空键值。除此之外,put操作被加锁了,所以是线程安全的
既然是缓存,那么必然能够动态删除一些不常用的键值对,这个工作是由trimToSize方法完成的:

[java] view
plain copy

 





public void trimToSize(int maxSize) {  

       while (true) {//不断删除linkedHashMap头部entry,也就是最近最少访问的条目,直到size小于最大容量  

           K key;  

           V value;  

           synchronized (this) {//线程安全  

               if (size < 0 || (map.isEmpty() && size != 0)) {  

                   throw new IllegalStateException(getClass().getName()  

                           + ".sizeOf() is reporting inconsistent results!");  

               }  

               if (size <= maxSize || map.isEmpty()) {//直到容量小于最大容量为止  

                   break;  

               }  

               Map.Entry<K, V> toEvict = map.entrySet().iterator().next();//指向链表头  

               key = toEvict.getKey();  

               value = toEvict.getValue();  

               map.remove(key);//删除最少访问的entry  

               size -= safeSizeOf(key, value);  

               evictionCount++;  

           }  

           entryRemoved(true, key, value, null);  

       }  

   }  

这个方法不断循环删除链表首部元素,也就是最近最少访问的元素,直到容量不超过预先定义的最大值为止。
注:LruCache在android.util包中也有一个LruCache类,但是我发现这个类的trimToSize方法是错误的:

[java] view
plain copy

 





private void trimToSize(int maxSize) {  

        while (true) {  

            K key;  

            V value;  

            synchronized (this) {  

                if (size < 0 || (map.isEmpty() && size != 0)) {  

                    throw new IllegalStateException(getClass().getName()  

                            + ".sizeOf() is reporting inconsistent results!");  

                }  

                if (size <= maxSize) {  

                    break;  

                }  

              

                Map.Entry<K, V> toEvict = null;  

                for (Map.Entry<K, V> entry : map.entrySet()) {  

                    toEvict = entry;  

                }  

      

                if (toEvict == null) {  

                    break;  

                }  

                key = toEvict.getKey();  

                value = toEvict.getValue();  

                map.remove(key);  

                size -= safeSizeOf(key, value);  

                evictionCount++;  

            }  

            entryRemoved(true, key, value, null);  

        }  

    }  

这里的代码将会循环删除链表尾部,也就是最近访问最多的元素,这是不正确的!所以大家在做内存缓存的时候一定要注意,看trimToSize方法是否有问题。

接下来是get方法:

[java] view
plain copy

 





public final V get(K key) {  

        if (key == null) {//不允许空键  

            throw new NullPointerException("key == null");  

        }  

        V mapValue;  

        synchronized (this) {//线程安全  

            mapValue = map.get(key);//调用LinkedHashMap的get方法  

            if (mapValue != null) {  

                hitCount++;//命中次数加1  

                return mapValue;//返回value  

            }  

            missCount++;//未命中  

        }  

  

        V createdValue = create(key);//默认返回为false  

        if (createdValue == null) {  

            return null;  

        }  

        synchronized (this) {  

            createCount++;//如果创建成功,那么create次数加1  

            mapValue = map.put(key, createdValue);//放到哈希表中  

            if (mapValue != null) {  

                // There was a conflict so undo that last put  

                map.put(key, mapValue);  

            } else {  

                size += safeSizeOf(key, createdValue);  

            }  

        }  

        if (mapValue != null) {  

            entryRemoved(false, key, createdValue, mapValue);  

            return mapValue;  

        } else {  

            trimToSize(maxSize);  

            return createdValue;  

        }  

    }  

get方法即根据key在LinkedHashMap中寻找对应的value,此方法也是线程安全的。

以上就是LruCache最重要的部分,下面再看下其他方法:
remove:

[java] view
plain copy

 





public final V remove(K key) {  

      if (key == null) {  

          throw new NullPointerException("key == null");  

      }  

      V previous;  

      synchronized (this) {  

          previous = map.remove(key);//调用LinkedHashMap的remove方法  

          if (previous != null) {  

              size -= safeSizeOf(key, previous);  

          }  

      }  

      if (previous != null) {  

          entryRemoved(false, key, previous, null);  

      }  

      return previous;//返回value  

  }  

sizeof:这个方法用于计算每个条目的大小,子类必须得复写这个类。

[java] view
plain copy

 





protected int sizeOf(K key, V value) {//用于计算每个条目的大小  

       return 1;  

   }  

snapshot方法,返回当前缓存中所有的条目集合

[java] view
plain copy

 





public synchronized final Map<K, V> snapshot() {  

       return new LinkedHashMap<K, V>(map);  

   }  

总结:
1.LruCache封装了LinkedHashMap,提供了LRU缓存的功能;
2.LruCache通过trimToSize方法自动删除最近最少访问的键值对;
3.LruCache不允许空键值;
4.LruCache线程安全;
5.继承LruCache时,必须要复写sizeof方法,用于计算每个条目的大小。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: