美文网首页
Java基础_LruCache工作原理

Java基础_LruCache工作原理

作者: dashingqi | 来源:发表于2020-03-06 23:11 被阅读0次

本文主要从如下节点讲解

  • LRU算法简介
  • LruCache的简介
  • LruCache的代码实操
  • LruCache的原理
  • LruCache的总结

LRU算法简介

  • LRU(Least Recently Used):是最近最少使用的算法
  • 这个算法的核心就是,当缓存要满的时候,会优先淘汰最近最少使用的缓存对象。
  • 采用LRU算法的有LruCache和DisLruCahce,分别用于内存缓存和磁盘缓存。

LruCache的简介

  • LruCache是一个泛型类。
  • 主要原理就是把最近使用的对象的强引用存储在LinkedHashMap中。当缓存满是,会把最近最少使用的对象从内存中移除。
  • 底层就是在维护一个LinkedHashMap。

LruCache的简单代码实操

  • 代码如下

    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //初始化一个容量为5的LruCache
            LruCache<Integer, Integer> lruCache = new LruCache<>(5);
            //调用put函数添加数据
            lruCache.put(1, 1);
            lruCache.put(2, 2);
            lruCache.put(3, 3);
            lruCache.put(4, 4);
            lruCache.put(5, 5);
            // 通过snapshot()方法获取Map,拿到Entry
            for (Map.Entry<Integer, Integer> entry : lruCache.snapshot().entrySet()) {
                Log.d(TAG, "key = " + entry.getKey() + ", value = " + entry.getValue());
            }
            Integer integer = lruCache.get(2);
            Log.e(TAG, "超出容量");
            //在容量满的情况下,进行put操作
            //通过Log打印,新添加的数据放在队列的尾部,而队列的头部的数据会被移除。
            lruCache.put(6, 6);
            for (Map.Entry<Integer, Integer> entry : lruCache.snapshot().entrySet()) {
                Log.d(TAG, "key = " + entry.getKey() + ",value = " + entry.getValue());
            }
    
        }
    }
    
  • 运行结果如下

    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 1, value = 1
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 2, value = 2
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 3, value = 3
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 4, value = 4
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 5, value = 5
    2020-03-06 10:19:20.264 10184-10184/? E/MainActivity: 超出容量
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 3,value = 3
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 4,value = 4
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 5,value = 5
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 2,value = 2
    2020-03-06 10:19:20.264 10184-10184/? D/MainActivity: key = 6,value = 6
    

LruCache的原理

LruCache的构造方法
  • 代码如下

    public LruCache(int maxSize) {
            if (maxSize <= 0) {
                throw new IllegalArgumentException("maxSize <= 0");
            }
            //lrucahche的容量值
            this.maxSize = maxSize;
            //声明一个容量为0,加载因为为0.75 并且开启访问顺序排序的LinkedHashMap
            this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
        }
    
LruCache的put()方法
  • 代码如下

    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++;
                // safeSizeOf() 默认返回1 先给 size+1;
                size += safeSizeOf(key, value);
                //如果当前添加的key在底层的LinkedHashMap有存在相同,那就把原有的包含键值对的Entry给返回回来
                // 不存在,就返回 null
                previous = map.put(key, value);
                //说明存在要替换的数据
                if (previous != null) {
                    // 将size -1
                    size -= safeSizeOf(key, previous);
                }
            }
    
            //有存在要替换的数据
            if (previous != null) {
                // 该方法默认为空实现
                entryRemoved(false, key, previous, value);
            }
            //进行容量的检测,如果添加数据的时候,超出了容量值,就将头部的数据移除
            trimToSize(maxSize);
            return previous;
        }
    
    //trimToSize的源码解析
    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;
                    }
    
                    // BEGIN LAYOUTLIB CHANGE
                    // get the last item in the linked list.
                    // This is not efficient, the goal here is to minimize the changes
                    // compared to the platform version.
                    Map.Entry<K, V> toEvict = null;
                    for (Map.Entry<K, V> entry : map.entrySet()) {
                      //找到最近最少使用的Entry
                        toEvict = entry;
                    }
                    // END LAYOUTLIB CHANGE
    
                    if (toEvict == null) {
                        break;
                    }
    
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    //移除元素
                    map.remove(key);
                    //size -1
                    size -= safeSizeOf(key, value);
                    //缓存移除的次数+1;
                    evictionCount++;
                }
    
                entryRemoved(true, key, value, null);
            }
        }
    
LruCache的get()方法
  • 代码如下

    public final V get(K key) {
            //对key的非空判断
            if (key == null) {
                throw new NullPointerException("key == null");
            }
    
            V mapValue;
            synchronized (this) {
                // 通过key获取到value
                mapValue = map.get(key);
                //如果value不为null
                if (mapValue != null) {
                    //缓存命中次数自增
                    hitCount++;
                    //返回当前的value
                    return mapValue;
                }
                //缓存没有命中次数自增
                missCount++;
            }
    
            /*尝试创建一个值,这将会花费一段时间,create方法创建的值有可能导致哈希表发生变化,
             *当哈希表中添加了一个冲突的值的时候,将会保留原有的值。
             * Attempt to create a value. This may take a long time, and the map
             * may be different when create() returns. If a conflicting value was
             * added to the map while create() was working, we leave that value in
             * the map and release the created value.
             *
             * 注: 其实create方法是未加锁的,当多个线程调用同一个key,生成多个value,就发生了hash碰撞了,
             * 从而保留第一个被添加进去的值。
             * 
             */
    
            //调用create方法创建一个 createValue  create方法默认返回一个null
            V createdValue = create(key);
            if (createdValue == null) {
                return null;
            }
    
            //同样,我们可以重写create方法,返回一个不为null的value
            //这样就执行如下的代码了
            synchronized (this) {
                //创建值自增
                createCount++;
                //将key和对应的创建值存到LinkedHashMap中。
                //如果该key之前存在,那么返回原有的值,否则返回null
                mapValue = map.put(key, createdValue);
    
                if (mapValue != null) {
                    // There was a conflict so undo that last put
                    //如果返回的value不为null,就将之前源于的值重新存储到LinkedHashMap中
                    map.put(key, mapValue);
                } else {
                    // size +1
                    size += safeSizeOf(key, createdValue);
                }
            }
            //如果存在原有的值
            if (mapValue != null) {
                entryRemoved(false, key, createdValue, mapValue);
                //返回原有的值
                return mapValue;
            } else {
                //进行容量的检测,如果超出了容量的值,就将头部的数据移除
                trimToSize(maxSize);
                //返回创造的值
                return createdValue;
            }
        }
    
LruCache的remove方法
  • 代码如下

    public final V remove(K key) {
            // 对key做非空判断
            if (key == null) {
                throw new NullPointerException("key == null");
            }
    
            V previous;
            synchronized (this) {
                //根据key移除链表中的Node的value,并返回Node的value
                previous = map.remove(key);
                if (previous != null) {
                    //数据删除后,需要经size-1
                    size -= safeSizeOf(key, previous);
                }
            }
            //当获取到的值不为ull。
            // 调用了 entryRemoved方法
            if (previous != null) {
                entryRemoved(false, key, previous, null);
            }
            //返回当前要移除的值
            ret
    
LruCache的entryRemoved()

该方法在我们分析的方法都有调用

put: entryRemoved(false, key, previous, value);

trimSize:entryRemoved(true, key, value, null);

get: entryRemoved(false, key, createdValue, mapValue);
remove: entryRemoved(false, key, previous, null);

  • 其实我们可以通过重写 entryRemoved()方法获得如下几个信息
    • evicted == true 代表着这个方法是在trimSize方法中调用的。
    • evicted == false
      • newValue == null 代表是在remove方法中调用的
      • newValue != null 代表是在get或者put方法中执行的

LruCache的总结

  • LruCache底层是在维护一个LinkedHashMap
  • 针对 k/v 都有非空检查,key和value都不能为null

相关文章

  • Java基础_LruCache工作原理

    本文主要从如下节点讲解 LRU算法简介 LruCache的简介 LruCache的代码实操 LruCache的原理...

  • Android 高级面试-3:Java、同步和并发相关

    1、Java 相关 1.1 缓存相关 LruCache 的原理 DiskLruCache 的原理 LruCache...

  • (1)LruCache原理分析

    浅析LruCache原理 Android用LruCache(Least recently use Cache 意...

  • Android缓存原理

    本文主要内容 LruCache使用 LruCache原理 DiskLruCache使用 DiskLruCache原...

  • 面试题

    LruCache原理 LruCache 即最近最少使用内存(Least recently usage cache)...

  • 缓存分析

    LruCache与DiskLruCache 文章目录 一 Lru算法 二 LruCache原理分析2.1 写入缓存...

  • Android常问的面试题(二)

    (一)图片 1、图片库对比 2、LRUCache原理 LruCache是个泛型类,主要原理是:把最近使用的对象用强...

  • java基础巩固笔记(4)-代理

    标签: java Contents java基础巩固笔记(4)-代理概念动态代理创建动态类动态代理的工作原理面向切...

  • LRUCache原理

    讲到LruCache不得不提一下LinkedHashMap,因为LruCache中Lru算法的实现就是通过L...

  • LRUCache原理

    Least Recently Used => LRU:最近最少使用算法 为什么使用LRUcache?Android...

网友评论

      本文标题:Java基础_LruCache工作原理

      本文链接:https://www.haomeiwen.com/subject/ketzrhtx.html