Volley网络请求封装之LruCache源码分析

Volley网络请求封装之LruCache源码分析

相关知识点

  1. 常用的缓存算法FIFO、LRU、LFU
  2. Volley与LruCache
  3. LinkedHashMap的特点

知识点讲解

  • 之前也用过很多的网络缓存框架,比如ACache、AsimpleCache等,但是始终觉得还是自己封装的知根知底用起来比较舒服,最近在封装Volley网络请求,当前封装到图片缓存模块,现在GitHub已经开源了很多种网络方面比较出色的框架,比如picasso、谷歌自己的glide、ImageLoader谢列等等,之前也封装过ImageLoader的缓存框架,但是总是觉得欠缺点什么,随着技术的升级以前的软引用和弱引用通过系统自己处理的方式,感觉没以前辣么6了,所以学习一定要知其所以然才不会被坑死,好吧扯远了言归正传。

  • 虽然这里主要分析的是LruCache的源码,但是还是有义务为大家说明一下集中常见的缓存涉及到的算法:FIFO先进先出常见的是栈(栈的概念有很多,希望不要死扣概念,理解我的意思就行)、LRU最近时间一次使用,本文设计到的LinkedHashMap在源码分析的时候会讲,LFU最近使用频率最少使用的算法,注意后两者一个是基于时间的一个是基于频率的不一样哦。

  • 接下来看看LinkedHashMap,LinkedHashMap是Map的子类,他的特点是一个双向链表、每一个数据包含了前面和后面的节点的地址信息,这意味着数据的查询、删除、插入操作很easy而遍历就稍微耗时一点。上满说的只是LinkedHashMap的基本属性,其实他真正的在缓存中实用部分是他默认有两种排序方式,默认的是数据按照默认插入顺序排列,通过flag值可以将这种默认的行为修改为将最新时间段内访问的数据添加到队列结尾,也就是说使用此类对象可以知道轻松的获取新旧数据。这一特点就是LinkedHashMap特别适合实现LRU算法的缓存。好的基础知识介绍完了,来看源码吧。

构造函数

首先,LruCache是在3.1版本后引入的,他的核心存储机制就是灵活的操作LinkedHashMap对象,看源码喜欢从上而下开始,首先来看构造函数

`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);
}`
  • maxSize指的是默认存入到LinkedHashMap对象中实体的数量,这里说默认的原因是你可以通过改写该类的Sizeof方法自己定制如何计算实体对象大小的方法,该函数默认返回值是1.
  • 实例化map的时候使用了超过分配尺寸四分之三自增的属性,这里了解即可,有空补充一片LinkedHashMap的源码分析。

核心方法

  • LruCache最核心的三个操作方法,就是get、put、remove下面我们来意义介绍这三个方法 -

public final V get(K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V mapValue;
    //线程安全
    synchronized (this) {
     //根据key值查找数据,找到返回
        mapValue = map.get(key);
        if (mapValue != null) {
            hitCount++;
            return mapValue;
        }
        missCount++;
    }
    //尝试根据key值创建,create默认返回null,即默认不会创建
    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }
    //如果我们默认修改了create创建方法,并成功创建数据,会尝试调用类似put方法的功能(默认这部分不执行,我们看下面的put方法吧)
    synchronized (this) {
        createCount++;
        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;
    }
}

   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);
        //获取存放的map中的对象,如果之前map中存在,清除之前的修改尺寸
        previous = map.put(key, value);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }
    //调用移除方法,默认没有实现
    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }
    //修正尺寸,稍后会解释
    trimToSize(maxSize);
    return previous;
}`

` /**
 * Removes the entry for {@code key} if it exists.
 *
 * @return the previous value mapped by {@code key}.
 */
public final V remove(K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V previous;
    synchronized (this) {
        //删除操作,并修改尺寸
        previous = map.remove(key);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, null);
    }

    return previous;
}

  • 默认没有实现,但是你可以在做删除操作时做一些操作,

    @param evicted true 表示map正在删除,false单独调用remove或put方法出发的,因为该方法是非线程安全的所以需要考虑这个问题,对删除操作进行处理,没有特殊要求不用重写 @param newValue 如果是put执行的删除操作会将新的值传入

    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}


  • 再来看看最后一个重要的方法吧,关于尺寸的修改:

 public void trimToSize(int maxSize) {
   //不断轮询遍历map
    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 = map.eldest();
            if (toEvict == null) {
                break;
            }

            key = toEvict.getKey();
            value = toEvict.getValue();
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
        }
        //如果你没重写该方法,那就忽略吧
        entryRemoved(true, key, value, null);
    }
}'

总结

  1. 使用该类初始化构造函数设置存放储量就可以,当然该数量可以动态修改
  2. 此缓存只是缓存设置中的一级缓存,对缓存的操作使用get、put、remove方法就好
  3. 其中该类为开发者提供了sizeof、create、entryRemoved的扩展方法,重写这三个方法可以实现自己定制的缓存处理方式
  4. sizeof对应定制对象大小计算方法,create的返回值决定着get方法是否能够自助处理不存在就创建的并存放到缓存中的流程,entryRemoved执行并监听entry被移除和替换掉的后续操作。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值