https://blog.csdn.net/justloveyou_/article/details/71713781
一、LinkedHashMap原理
它是一个将所有Entry节点链入一个双向链表的HashMap
由于 LinkedHashMap 是HashMap的子类,所以LinkedHashMap自然会【拥有HashMap的所有特性】。
它额外定义了【一个以 head 为头节点的双向链表 用于保持迭代顺序】
默认 按插入顺序排序
对于每次 put 进来 Entry,除了将其保存到哈希表中对应的位置上之外,还会将其插入到双向链表的尾部
head 节点不包含数据
(
LruCache 是一个泛型类,内部采用一个 LinkedHashMap 以 强引用的方式存储外界的缓存对象
LruCache 会 移除较早使用的缓存对象,然后再添加新的缓存对象
LruCache 是线程安全的,内部存取数据都用了同步
)
LinkedHashMap 为什么使用双向链表,而不是单链表?
LinkedHashMap 的重要运用之一是,实现 LRU算法,每次被访问的元素,都会被移到双向链表尾部。
使用双向链表,移动元素速度快,只要把前后节点相连、把本节点放到链表末尾即可。
如果使用单链表,需要先遍历找到 当前节点的上一个节点。这个查找过程会比较耗时。
LinkedHashMap,等于是在HashMap 的哈希表中,用一条线,按插入/访问顺序把各个元素串起来。
LRU算法实现
accessOrder 为 true时,按访问时间排序。
每次访问一个节点后,都会把该节点移到双向链表末端
LinkedHashMap 的遍历,遍历的是 保存顺序的双链表,从头节点往尾部遍历(即从最久未访问的元素开始遍历)
LruCache 每次插入元素,如果容量达到上限,就会遍历删除,删除的就是最久未访问的元素
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
/* 一个数组,数组下标由key 的hash值计算而来
*
* 数组中的每个元素,都是单链表的头节点/jdk1.7以后,也可能是二叉树的根节点
* [Node_hash1, Node_hash2, Node_hash3, ...] 【数组】
* | | |
* Node_hash1_2 Node_hash2_2 ... 【链表】
* | |
* Node_hash1_3 ...
*/
transient Node<K,V>[] table;
}
/* 每次put进来Entry,除了将其保存到哈希表中对应的位置上之外,还会将其插入到双向链表的尾部。
* 通过双向链表来保存插入顺序。
*/
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
//头节点,也是双链表中最老的元素
transient LinkedHashMapEntry<K,V> head;
//尾节点,双链表中最新的元素
transient LinkedHashMapEntry<K,V> tail;
/* true表示按照访问顺序迭代,
* false时表示按照插入顺序
*
* true时:会在每次访问(put/get)时,把最近访问的元素放到双链表的尾部,从而实现按访问时间排序。
*/
private final boolean accessOrder;
/* LinkedHashMap的Entry,在HashMap.Node基础上,增加【before、after】
* 用于与双向链表相连,从而维护Entry插入的先后顺序
*/
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
LinkedHashMapEntry<K,V> before, after;
LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
//构造函数最后会调用该方法,用于初始化双向链表
void init() {
//头节点,不包含数据
header = new Entry<K,V>(-1, null, null, null);
header.before = header.after = header;
}
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;
}
//将节点移到链表尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
//p 指向我们要移动的节点
LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<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;
}
tail = p;
++modCount;
}
}
/**
* LinkedHashMap 的 entrySet()遍历,执行的是 LinkedEntrySet.foreach()方法
*/
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
...
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
//遍历的是 维护的双链表,从头节点往尾部遍历
for (LinkedHashMapEntry<K,V> e = head; (e != null && mc == modCount); e = e.after)
action.accept(e);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
二、LruCache 原理
LRU(Least Recently Used) 最近最少使用
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;//内部就是一个LinkedHashMap
/** Size of this cache in units. Not necessarily the number of elements. */
private int size;
private int maxSize;
...
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
/**
* 最后一个元素 true,表示:LinkedHashMap 【按访问时间排序,最近访问的放尾部】
*/
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/**
* 获取缓存数据
*/
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++;
}
...
}
/**
* 存入数据,会把存入的数据,塞到 LinkedHashMap 的头节点
*/
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;
}
//调整缓存大小(关键方法)
private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
...
if (size <= maxSize) {
break;
}
/* 容量达到上限时,遍历 LinkedHashMap,LinkedHashMap每次访问元素都会把元素移动到链表尾部
* 所以 LinkedHashMap 头部的元素是最久没有访问的。
*
* 这里entrySet()可以看 LinkedEntrySet.forEach()是从head节点开始往后遍历的
* 这里从 head 节点开始,边遍历边删除,从最老的元素开始删除,直到容量不超过上限。
*/
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);
}
}
}
LruCache 是一个泛型类,内部采用一个 LinkedHashMap 以强引用的方式存储外界的缓存对象。
LruCache 会移除较早使用的缓存对象,然后再添加新的缓存对象。
LruCache 是线程安全的,内部存取数据都用了同步。
三、DiskLruCache 原理
DiskLruCache 也是通过LRU算法对缓存进行管理,通过LinkedHashMap,按访问时序排序。
每次访问元素,都会把元素移动到链表尾部。
head节点不包含数据。head的下一个节点就是包含数据的最久没有访问的元素。
当 LinkedHashMap 达到容量时,通过 LinkedHashMap.entrySet(),从链表头节点开始遍历,边遍历边删除。
直到容量不超过上限。
从而实现LRU算法。
推荐阅读:
《Android开发艺术探索》第12.2 Android中的缓存策略
Map 综述(二):彻头彻尾理解 LinkedHashMap https://blog.csdn.net/justloveyou_/article/details/71713781