LinkedHashMap继承自HashMap,建议先学习HashMap源码,然后再学习LinkedHashMap源码,HashMap源码,这样会快很多。
LinkedHashMap继承了HashMap,所以大体上是仍然是HashMap的结构,但是在内部实现增加两个指针来构成一个双向链表来维持顺序,同时提供了两种可选择的顺序。LinkedHashMap与HashMap的面向对象设计思想很值得学习,其次LinkedHashMap在需要有序Map时候是一种选择,也为缓存的实现提供一种基础组件。
LinkedHashMap相比于HashMap主要重写了newNode(), afterNodeRemoval(), afterNodeInsertion(), afterNodeAccess()几个重要方法,增加了head,tail,accessOrder三个内部字段。
- 基本字段
transient LinkedHashMap.Entry<K,V> head; //双链表的头指针
transient LinkedHashMap.Entry<K,V> tail; //双链表的尾指针
/**
* accesssOrder为true表示linkedHashMap的顺序是按照访问顺序
* 就是会将最新被访问过的节点移到链表末尾
* 比如1,2,3三个结点,如果访问了一次2,就会变成1,3,2
* 为false表示顺序是按照插入顺序来保持,后续不会更改
* 默认值为false
*/
final boolean accessOrder;
- 构造函数
public LinkedHashMap(int initialCapacity, float loadFactor)
public LinkedHashMap(int initialCapacity)
public LinkedHashMap()
public LinkedHashMap(Map<? extends K, ? extends V> m)
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
基本实现是调用父类的构造函数,然后给accessOrder赋值
- class Entry<K,V> (LinkedHashMap如何维持顺序的)
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
LinkedHashMap的Entry类继承自HashMap的Node类,但是增加了before,after两个字段来构成一个双向链表,通过这两个字段来维持结点的顺序,在put,remove,get之后都会对链表进行相应的调整。
- newNode()函数
LinkedHashMap没有重写HashMap的put和putVal方法,如何将节点连到链表上的,就是通过newNode()函数,因为putVal的时候,一定会newNode,在newNode的时候就将节点连接到到链表上,这样就避免了putVal方法的重写,大量代码重复。
/**
* 只重写了这个方法,没有重写put方法,因为put的时候会调用这个方法
* 在这里把最新的结点插入到链表的末尾来维持顺序
*/
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
/**
* 双向链表的基本操作
*/
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
- afterNodeRemoval函数
/**
* 删除结点的时候,同时在链表中删除
* 链表的基本操作应该都能看懂
*/
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
- afterNodeAccess函数
/**
* 将一个结点移动到链表的末尾,配合accessOrder使用
*/
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<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;
}
}
- afterNodeInsertion函数
这个函数比较有趣,在HashMap的putVal中就调用了他,但是是一个空方法;在LinkedHashMap中又调用了他,但是基本还是个空方法;这个方法在ExpiringCache这个类中才发挥了作用。
/**
* 这个方法在正常的LinkedHashMap方法中是不会被调用的
* 因为removeEldestEntry()返回始终为false
*/
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
看removeEldestEntry函数
/**
* 在LinkedHashMap中始终返回的是false
* 作用是为LinkedHashMap做缓存提供一个实现
* 比如ExpiringCache,只需要重写这个方法
* 在进行缓存淘汰的时候可以发挥作用
* 比如用LinkedHash实现一个LRU算法
*/
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
这个方法在ExpiringCache被重写了,是这样的:
ExpiringCache(long millisUntilExpiration) {
this.millisUntilExpiration = millisUntilExpiration;
map = new LinkedHashMap<String,Entry>() {
protected boolean removeEldestEntry(Map.Entry<String,Entry> eldest) {
return size() > MAX_ENTRIES; //当缓存满的时候,淘汰一个最老的缓存
}
};
}
所以jdk是将LinkedHashMap作为了一种缓存的实现组件来设计,然后空出缓存具体部分由用户实现,我想通过读LinkedHashMap源码,应该很容易实现一个LRU算法了吧。