码哒,今天无意中发现Android 5.0(api level 21)之前的LruCache实现居然存在一个bug。

由于在电脑上(Java SE环境,非手机上)测试code比较方便,我便将最近写在Android项目中的框架代码copy到Java项目中进行测试,然后缺少一个LruCache, 我也直接将其源码复制过来,但是报了一个错误,正是下面第一段代码中map.eldest();这句,但是这个方法由于不是Java标准API而被@hide了,我便想也没想,直接改成了遍历map并取出最后一个元素(思维定式,以为LinkedHashMap的最后一个元素就是那个eldest()的,即LRU的),但是测试中很快发现了问题,然后在LinkedHashMap源码中找到其定义及注释,修改为取出链表的第一个元素了。

然而凑巧的是,今天下班我把代码copy到自己的本儿上准备带回家测试,再次copy这个LruCache源码的时候,发现居然不报错了,纳闷中,我便再次翻到那一行,就发现了题述的问题:那段代码正好跟我昨天刚开始犯的错误一样,遍历最后一个元素当做LRU的,并将去驱逐。并且加了注释,大意是说:由于map.eldest();为非标准API, 所以将其修改了。

OK,看代码吧。

首先来看看正确的实现 Android 6.0(API Level 23):

/**

* Remove the eldest entries until the total of remaining entries is at or

* below the requested size.

*

* @param maxSize the maximum size of the cache before returning. May be -1

* to evict even 0-sized elements.

*/

public 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 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);

}

}

其中有个map.eldest();方法,表示取出LinkedHashMap中最年长的元素,并将其驱逐。

下面来看看错误的实现 Android 5.0(API Level 21):

/**

* @param maxSize the maximum size of the cache before returning. May be -1

* to evict even 0-sized elements.

*/

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 toEvict = null;

for (Map.Entry entry : map.entrySet()) {

toEvict = entry;

}

// END LAYOUTLIB CHANGE

if (toEvict == null) {

break;

}

key = toEvict.getKey();

value = toEvict.getValue();

map.remove(key);

size -= safeSizeOf(key, value);

evictionCount++;

}

entryRemoved(true, key, value, null);

}

}

注意其中这段以及其注释说明:

Map.Entry toEvict = null;

for (Map.Entry entry : map.entrySet()) {

toEvict = entry;

}

遍历取出最后一个元素,这个正是将被驱逐的元素。同时在类说明中给了一段注释:

import java.util.LinkedHashMap;

import java.util.Map;

/**

* BEGIN LAYOUTLIB CHANGE

* This is a custom version that doesn't use the non standard LinkedHashMap#eldest.

* END LAYOUTLIB CHANGE

*

* A cache that holds strong references to a limited number of values. Each time

* a value is accessed, it is moved to the head of a queue. When a value is

* ...(略)

是说,这个版本不能使用非标准APILinkedHashMap#eldest。然而紧接着的一句话:

Each time a value is accessed, it is moved to the head of a queue.

每次访问一个值时,它都会被移动到队列的head(意思是说head是 most-recently,不是 least-recently)。

就说错了,有LinkedHashMap的文档为证:

* A special {@link #LinkedHashMap(int,float,boolean) constructor} is

* provided to create a linked hash map whose order of iteration is the order

* in which its entries were last accessed, from least-recently accessed to

* most-recently (access-order). This kind of map is well-suited to

* building LRU caches.

从“较远的近”(最近最少访问)到“极为最近”排序,说明head是“较远的近”,是 least-recently,是eldest。

这段代码经过测试,在Cache Size填满后,确实总是驱逐最后添加进去的元素。显然不符合Lru的意图。

需要注意的是,LruCache的map是这么构造的:this.map = new LinkedHashMap(0, 0.75f, true);,重点在这个true, 表示任何一个操作(get, put等)都将触发重排序,将这个被操作的元素排到链表的末尾,因此末尾的是最近“频繁”使用的,而不是 LRU(Least Recently Used)最近“最少”使用的,那么这个取出末尾的那个元素并将其驱逐的逻辑,显然是错误的!

那么我们还是回过头来看看eldest()到底做了什么吧!

/**

* Returns the eldest entry in the map, or {@code null} if the map is empty.

* @hide

*/

public Entry eldest() {

LinkedEntry eldest = header.nxt;

return eldest != header ? eldest : null;

}

eldest()直接返回了header.nxt.

public class LinkedHashMap extends HashMap {

/**

* A dummy entry in the circular linked list of entries in the map.

* The first real entry is header.nxt, and the last is header.prv.

* If the map is empty, header.nxt == header && header.prv == header.

*/

transient LinkedEntry header;

而header.nxt又是The first real entry. 意思很明确了,就是返回链表的第一个元素。

那么可以肯定Android 5.0(API Level 21)及以前对LruCache的实现就是一个bug了。

android lrucache清空,Android LruCache 的 Bug相关推荐

  1. android radiogroup清空,android RadioGroup的使用方法

    创建一个MainActivity.java的主类 android:layout_width="fill_parent" android:layout_height="fi ...

  2. android byte[] 清空,android byte的使用

    释放双眼,带上耳机,听听看~! 今天,简单讲讲android里byte的使用. 这个其实很简单,但是自己觉得一直没有完全弄明白,所以记录一下. byte即字节的意思,是java中的基本类型,用心申明字 ...

  3. android surface清空,Android:如何在surfaceDestroyed()之后重启视频预览?

    我通过以下方式创建了一个CapturePreview类和CameraManager类: CapturePreview: public class CaptureView extends Surface ...

  4. android sharedpreference 清空,Android 从SharedPreferences中存储,检索,删除和清除数据...

    示例 创建SharedPreferences BuyyaPref SharedPreferences pref = getApplicationContext().getSharedPreferenc ...

  5. 【Android 内存优化】Bitmap 内存缓存 ( Bitmap 缓存策略 | LruCache 内存缓存 | LruCache 常用操作 | 工具类代码 )

    文章目录 一.Bitmap 内存缓存策略 二.LruCache 内存缓存 三.LruCache 常用操作 四.LruCache 工具类 五.源码及资源下载 官方参考 : Google 官方提供的 内存 ...

  6. 【Android 应用开发】LruCache 简介

    文章目录 LruCache 应用场景 LruCache 算法原理 LruCache 实现 LruCache 参考 LruCache 应用场景 1. 缓存需求 : 处理大量数据时 , 为了提升性能 , ...

  7. Android利用DiskLruCache和LruCache实现简单的照片墙

    现在很多App的某些功能会把图片以及内容缓存在本地,即使是没有网络的情况下也还是可以加载出之前浏览过的内容来,这些功能就是使用了DiskLruCache技术: LruCache负责管理内存中图片的存储 ...

  8. Android 8.1 频频被曝 Bug,是要赶超苹果吗?

    点击上方"CSDN",选择"置顶公众号" 关键时刻,第一时间送达! Android 8.1被曝严重 Bug,这下看你还想不想吃奥利奥了. 虽然很多采用 Andr ...

  9. Android 入门eclipse+ADT配置,bug处理及附件下载(一)

    Android 入门eclipse+ADT配置,bug处理及附件下载(一) 由于时代的变化科技的发展手机也发生了飞跃的变换,从以前的只能音频通话的"大哥大"到现在流行的3G手机:具 ...

最新文章

  1. 阿里发布图计算平台GraphScope,即将向全社会开源
  2. 全国各省“光棍”排名,数据揭秘哪里脱单最难?
  3. C++利用二次探查实现存储机制hash table的算法(附完整源码)
  4. FIR设置过采样率 matlab,Xilinx FIR IP的介绍与仿真
  5. ssh项目同时使用mysql跟sqlserver数据库_MSSQL_如何把sqlserver数据迁移到mysql数据库及需要注意事项,在项目开发中,有时由于项目 - phpStudy...
  6. 前端学习(2872):Vue路由权限『前后端全解析』3
  7. 怎么升级计算机硬盘,研究僧 篇一:记一次老电脑的升级之路
  8. java string返回_Java的String字符串内容总结
  9. Qt文档阅读笔记-Qt工作笔记-QTableWidget::selectedItems()官方解析与实例(如何进行多选)
  10. ORA-00257: archiver error. Connect internal only, until freed——解决
  11. @Bean和@Componet区别
  12. Python-移位密码、仿射变换解密
  13. 程序员的你不可不知的数据库northwind
  14. prosody IM cert证书安装
  15. 卡方检验有哪些指标?卡方值怎么计算?
  16. 网页中那些遇到过的导航选中状态actived selected
  17. 手机微信html文件怎么打开方式,手机微信页面如何用web浏览器打开
  18. JavaFX开发教程——快速入门FX
  19. Excel与Word链接操作:使用邮件合并工具
  20. 不能撑开盒子css,CSS padding在什么情况下不撑开盒子

热门文章

  1. Android版本的 Wannacry 文件加密病毒样本分析 附带锁机
  2. 12本Python书籍推荐
  3. kudu 1.7  源码安装
  4. 「 SCI/EI撰写 」自动化控制领域“专业英语词组”随记
  5. 半小时掌握进制转换!
  6. 5年项目管理经验,总结出沟通的三大要点
  7. 【JS】Promise.race()方法例子流程详解
  8. 用java程序实现冒泡排序
  9. JAVA-千位分隔符插入方法
  10. 概率论 — 浅谈大数定律