在学习LruCache源码之前,我们有必要简单了解LinkedHashMap(使用):
LinkedHashMap
LinkedHashMap 内部维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。(多个线程同时访问链接的哈希映射,其中至少一个线程从结构上修改了该映射,必须保持外部同步。)
- LinkedHashMap元素的顺序:
- 插入顺序的链表;(默认插入顺序)
- 访问顺序(调用 get 方法)的链表。(调用get方法后,会将这次元素移到链表尾)
定义一般都是很难理解的,来俩个demo就明白了
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
map.put("A","java");
map.put("B", "golang");
map.put("C", "c#");
map.put("D", "php");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
运行结果
A=java
B=golang
C=c#
D=php
log可以看出输出顺序按照插入顺序的!
访问顺序进行排序么?下面我们继续
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
map.put("A","java");
map.put("B", "golang");
map.put("C", "c#");
map.put("D", "php");
map.get("A");
map.get("D");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
构造函数不相同,看一下结果
B=golang
C=c#
A=java
D=php
LruCache源码分析
先对get方法进行分析
public final V get(K key) {
// key为空会抛异常
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//若key不为空则命中次数hitCount加1并return这个value,
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
//否则missCount加1
missCount++;
}
//根据key尝试创建value,如果创建返回的createdValue是null, 直接返回null
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
//若createdValue不为null,则把createdValue放回map
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);
}
}
//存在旧值则返回旧值,否则返回这个createdValue。
//这一步是不是很迷糊啊
/*
map.put("A","java");
String Astr = map.put("A","c#");
key一致的情况下(Astr上一个旧值是java)
*/
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
再看看我们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++;
//put方法将键值对放入map
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//重新计算大小,删除访问次数最少的元素。
trimToSize(maxSize);
return previous;
}
trimToSize会一直尝试删除队首元素(访问次数最少的元素),直到size不超过最大容量。
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<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);
}
}
//这个方法要特别注意,如果看了我Rxhttp缓存那块代码,
//应该知道我重写这个方法,测量对象内存大小(总和)与maxsize进行比较,
//才能正确的进行内存回收
protected int sizeOf(K key, V value) {
return 1;
}
Rxhttp部分代码
mCache = new LruCache<String, Serializable>(cacheSize) {
@Override
protected int sizeOf(String key, Serializable value) {
return calcSize(value);
}
};
-------------------
/**
* 测量对象内存占用大小(实现Serializable)
* 这么测量对象内存是错误的(有误差)
*/
private static int calcSize(Serializable o) {
int ret = 0;
class DumbOutputStream extends OutputStream {
int count = 0;
public void write(int b) throws IOException {
count++; // 只计数,不产生字节转移
}
}
DumbOutputStream buf = new DumbOutputStream();
ObjectOutputStream os = null;
try {
os = new ObjectOutputStream(buf);
os.writeObject(o);
ret = buf.count;
} catch (IOException e) {
// No need handle this exception
e.printStackTrace();
ret = -1;
} finally {
try {
os.close();
} catch (Exception e) {
}
}
return ret;
}