IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android源码分析:LruCache 缓存机制实现原理,【架构师必备】 -> 正文阅读

[移动开发]Android源码分析:LruCache 缓存机制实现原理,【架构师必备】

 map.get(1);     //访问1
map.get(2);     //访问2

for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

}


输出结果如下:

0:0
3:3
4:4
5:5
6:6
1:1
2:2


即最近访问的对象会被放到队尾,然后最后输出,那么这就正好满足的LRU缓存算法的思想。可见**LruCache**巧妙实现,就是利用了**LinkedHashMap**的这种数据结构。

下面我们在**LruCache**源码中具体看看,怎么应用**LinkedHashMap**来实现缓存的添加,获得和删除的。

#### LruCache源码分析

我们先看看成员变量有哪些:

public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;

/** Size of this cache in units. Not necessarily the number of elements. */
private int size;   //当前cache的大小
private int maxSize;     //cache最大大小

private int putCount;       //put的次数
private int createCount;    //create的次数
private int evictionCount;  //驱逐剔除的次数
private int hitCount;       //命中的次数
private int missCount;      //未命中次数

//...省略...

}


构造函数如下,可以看到**LruCache**正是用了**LinkedHashMap**的**accessOrder=true**构造参数实现LRU访问顺序:

public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException(“maxSize <= 0”);
}
this.maxSize = maxSize;
//将LinkedHashMap的accessOrder设置为true来实现LRU顺序
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}


##### 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++;     //插入次数加1
    size += safeSizeOf(key, value);     //更新缓存的大小
    previous = map.put(key, value);
    //如果已有缓存对象,则缓存大小的值需要剔除这个旧的大小
    if (previous != null) {
        size -= safeSizeOf(key, previous);
    }
}

//entryRemoved()是个空方法,可以自行实现
if (previous != null) {
    entryRemoved(false, key, previous, value);
}

//调整缓存大小(关键方法)
trimToSize(maxSize);
return previous;

}


可以看到\*\*put()**方法并没有什么难点,重要的就是在添加过缓存对象后,调用**trimToSize()\*\*方法,来判断缓存是否已满,如果满了就要删除近期最少使用的算法。

##### trimToSize方法

public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
//如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ “.sizeOf() is reporting inconsistent results!”);
}

        //如果缓存大小size小于最大缓存,或者map为空,则不需要再删除缓存对象,跳出循环
        if (size <= maxSize || map.isEmpty()) {
            break;
        }

        //迭代器获取第一个对象,即队头的元素,近期最少访问的元素
        Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
        key = toEvict.getKey();
        value = toEvict.getValue();
        //删除该对象,并更新缓存大小
        map.remove(key);
        size -= safeSizeOf(key, value);
        evictionCount++;
    }
    entryRemoved(true, key, value, null);
}

}


**trimToSize()**方法不断地删除**LinkedHashMap**中队头的元素,即近期最少访问的,直到缓存大小小于最大值。

当调用**LruCache**的**get()**方法获取集合中的缓存对象时,就代表访问了一次该元素,将会更新队列,保持整个队列是按照访问顺序排序。这个更新过程就是在**LinkedHashMap**中的\*\*get()\*\*方法中完成的。

我们先看LruCache的get()方法。

##### get方法

//LruCache的get()方法
public final V get(K key) {
if (key == null) {
throw new NullPointerException(“key == null”);
}

V mapValue;
synchronized (this) {
    //获取对应的缓存对象
    //LinkedHashMap的get()方法会实现将访问的元素更新到队列尾部的功能
    mapValue = map.get(key);

    //mapValue不为空表示命中,hitCount+1并返回mapValue对象
    if (mapValue != null) {
        hitCount++;
        return mapValue;
    }
    missCount++;    //未命中
}

/*
 * 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方法默认返回null,并没有实现创建对象的方法。
 * 如果需要事项创建对象的方法可以重写create方法。因为图片缓存时内存缓存没有命中会去
 * 文件缓存中去取或者从网络下载,所以并不需要创建,下面的就不用看了。
 */

V createdValue = create(key);
if (createdValue == null) {
    return null;
}

//假如创建了新的对象,则继续往下执行
synchronized (this) {
    createCount++;
    //将createdValue加入到map中,并且将原来键为key的对象保存到mapValue
    mapValue = map.put(key, createdValue);

    if (mapValue != null) {
        // There was a conflict so undo that last put
        //如果mapValue不为空,则撤销上一步的put操作。
        map.put(key, mapValue);
    } else {
        //加入新创建的对象之后需要重新计算size大小
        size += safeSizeOf(key, createdValue);
    }
}

if (mapValue != null) {
    entryRemoved(false, key, createdValue, mapValue);
    return mapValue;
} else {
    //每次新加入对象都需要调用trimToSize方法看是否需要回收
    trimToSize(maxSize);
    return createdValue;
}

}


其中**LinkedHashMap**的\*\*get()\*\*方法如下:

//LinkedHashMap中的get方法
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
//实现排序的关键方法
if (accessOrder)
afterNodeAccess(e);
return e.value;
}


调用的\*\*afterNodeAccess()\*\*方法将该元素移到队尾,保证最后才删除,如下:

void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
//当前节点p移动到尾部之后,尾部指针指向当前节点
tail = p;
++modCount;
}

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
tail = p;
++modCount;
}
[外链图片转存中…(img-CrMzAZR6-1630507997475)]

[外链图片转存中…(img-VP3Jfc1v-1630507997477)]

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-02 11:30:10  更:2021-09-02 11:30:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:48:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码