上一篇分析了LinkedHashMap源代码,这个Map集合除了拥有HashMap的大部分特性之外。还拥有链表的特点,即能够保持遍历顺序与插入顺序一致。

另外。当我们将accessOrder设置为true时。能够使遍历顺序和訪问顺序一致,其内部双向链表将会依照最近最少訪问到最近最多訪问的顺序排列Entry对象,这能够用来做缓存。

这篇文章分析的LruCache并非jdk中的类,而是来自安卓,熟悉安卓内存缓存的必定对这个类不陌生。

LruCache内部维护的就是一个LinkedHashMap。
以下開始分析LruCache。
注:以下LruCache源代码来自support.v4包。

首先是这个类的成员变量:
 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的初始化放在构造器中:

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方法:

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方法完毕的:
 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方法是错误的:
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方法:
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:
  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:这种方法用于计算每一个条目的大小,子类必须得复写这个类。

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

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

 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方法。用于计算每一个条目的大小。

【源代码】LruCache源代码剖析的更多相关文章

  1. android之LruCache源代码解析

    移动设备开发中,因为移动设备(手机等)的内存有限,所以使用有效的缓存技术是必要的.android提供来一个缓存工具类LruCache,开发中我们会经经常使用到,以下来他是怎样实现的. 在package ...

  2. 图像库---Image Datasets---OpenSift源代码---openSurf源代码

    1.Computer Vision Datasets on the web http://www.cvpapers.com/datasets.html 2.Dataset Reference http ...

  3. MINA2 源代码学习--源代码结构梳理

    一.mina总体框架与案例: 1.总体结构图: 简述:以上是一张来自网上比較经典的图,总体上揭示了mina的结构,当中IoService包括clientIoConnector和服务端IoAccepto ...

  4. 【Java集合源代码剖析】TreeMap源代码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/36421085 前言 本文不打算延续前几篇的风格(对全部的源代码加入凝视),由于要理解透Tr ...

  5. Java To CSharp源代码转换

    前言 开发环境 客户端:Unity3D开发(C#) 服务器:Java (基于Java7) 日   期:2016年09月 需求说明 部分服务器的部分逻辑功能在客户端实现一遍,可以简单的理解为服务器的部分 ...

  6. Apple II DOS 源代码发布

    加州山景城的计算机历史博物馆不仅仅展示硬件,还展示软件.博物馆此前已发布了著名软件MacPaint .Photoshop和APL的源代码,现在它公开了1978年的Apple II DOS源代码.源代码 ...

  7. 在Android中调用C#写的WebService(附源代码)

    由于项目中要使用Android调用C#写的WebService,于是便有了这篇文章.在学习的过程中,发现在C#中直接调用WebService方便得多,直接添加一个引用,便可以直接使用将WebServi ...

  8. 1、android源代码下载与跟踪

     学习Android源代码的目的 理解Android API查找API(Activity.Content Provider等) 高级应用开发(ROM定制)  在不同平台下载Android源代码 W ...

  9. Ubuntu10.04下载并编译Android4.3源代码

    注:转载或引用请标明出处    http://blog.csdn.net/luzhenrong45/article/details/9719433 去年用Ubuntu10.10成功下载并编译Andro ...

随机推荐

  1. python基础学习笔记——闭包

    闭包这个概念好难理解,身边朋友们好多都稀里糊涂的,稀里糊涂的林老冷希望写下这篇文章能够对稀里糊涂的伙伴们有一些帮助~ 请大家跟我理解一下,如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内 ...

  2. Mysql 学习目录

    Mysql 目录 Mysql之路[第一篇]:Mysql基础 Mysql之路[第二篇]:Mysql 常用命令 Mysql之路[第三篇]:Python对Mysql的操作 Mysql之路[第四篇]:ORM ...

  3. Leetcode 400.第n个数

    第n个数 在无限的整数序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...中找到第 n 个数字. 为整形范围内 ( n < 231). 示例 1: 输入: 3 输出 ...

  4. PTA 08-图9 关键活动 (30分)

    题目地址 https://pta.patest.cn/pta/test/15/exam/4/question/719 假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它 ...

  5. Get 了滤镜、动画、AR 特效,速来炫出你的短视频开发特技!

    在滤镜美颜.搞怪特效.炫酷场景等各种新奇玩法驱动下,短视频开始让人上瘾. 12 月 3 日,七牛云联合八大短视频特效平台共同推出了中国短视频开发者创意大赛(China Short Video Cont ...

  6. 2016 ACM-ICPC China Finals #F Mr. Panda and Fantastic Beasts

    题目链接$\newcommand{\LCP}{\mathrm{LCP}}\newcommand{\suf}{\mathrm{suf}}$ 题意 给定 $n$ 个字符串 $s_1, s_2, \dots ...

  7. 手把手教你搭建DHCP服务器

    目录 DHCP实现原理 DHCP定义 DHCP分配方式 DHCP工作过程 初次登录 重新登录 更新租约 搭建DHCP服务器 实验目的 实验环境 实验步骤 实验结果 DHCP实现原理 DHCP定义 DH ...

  8. HDU [P3949] XOR

    线性基求第 k 小异或值 http://www.cnblogs.com/Mr-WolframsMgcBox/p/8567844.html 这道题消元下来是一个上三角矩阵,代码简单,但是不使用与本题的情 ...

  9. 洛谷 [P2953] 牛的数字游戏

    SG搜索 n的范围在可以接受的范围内,SG搜索即可 #include <iostream> #include <cstdio> #include <cstring> ...

  10. R语言入门视频笔记--9--随机与数据描述分析

    古典概型的样本总量是一定的,且每种可能的可能性是相同的, 1.中位数:median(x) 2.百分位数:quantile(x)或者quantile(x,probe=seq(0,1,0.2)) #后面这 ...