1.构造方法
/**
* @param maxSize for caches that do not override {@link #sizeOf}, this is
* the maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this cache.
*/
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,所以它是用LinkedHashMap来保存缓存数据,再看看LinkedHashMap的构造方法做了什么。
public LinkedHashMap(
int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
init();
this.accessOrder = accessOrder;
}
第一个参数是初始大小,第二个参数是负载因子,默认是0.75f,就是当HashMap内容超过0.75后会主动扩容,第三个参数是控制排序,如果为true,那么LinkedHashMap就是按照最常访问排序,为false则是按照插入时间排序。
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
private void makeTail(LinkedEntry<K, V> e) {
// Unlink e
e.prv.nxt = e.nxt;
e.nxt.prv = e.prv;
// Relink e as tail
LinkedEntry<K, V> header = this.header;
LinkedEntry<K, V> oldTail = header.prv;
e.nxt = header;
e.prv = oldTail;
oldTail.nxt = header.prv = e;
modCount++;
}
LinkedHashMap是一个双向循环链表,这段代码第一步就是将传进去的e节点先剥离出来,然后将其放到剥离之后的链表的尾巴上:
2.get()方法
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
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.
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
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;
}
}
首先判断key的有效性,然后进入一个同步块中,因为LinkedHashMap不是线程安全的,所以要在同步块里处理数据,如果拿到目标,就直接返回目标,否则进行下一步,通过create()方法生成一个目标,但是create()默认返回null,需要自己重写:
protected V create(K key) {
return null;
}
如果没重写这个方法,会在判断createValue时返回null,也就没下面的事情了,如果重写了这个方法,就要更新数据,再次进入同步块,这里做了两次put操作,是因为create()方法没有在同步块中,有可能当create()了一个key的值之后这个key对应的值已经并不是null了(有可能此时key已经被put了值进去)。
第二个同步块里面在成功放入create的mapValue之后进行了一个trimToSize()的操作,很显然,这是在控制LinkedHashMap的大小,剔除最不常使用的变量,它的具体写法是这样的:
/**
* 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<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);
}
}
大概意思就是如果超出了限定大小,就要把最旧的数据移除,直到符合要求。
2.put()方法
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
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) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
put()也是类似的检查合法性,放入数据,调整大小的过程。
参考博文:
LruCache源码解析