LinkedHashMap
LinkedHashMap相较于HashMap,其可以保存保存数据添加的顺序,底层实现十分简单,其本身也继承了HashMap
重要属性
/**
* The head (eldest) of the doubly linked list.
*/
// 最老的节点
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
// 最年轻的节点
transient LinkedHashMap.Entry<K,V> tail;
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
// 按照什么规则保存顺序,是访问顺序还是插入顺序
final boolean accessOrder;
put
LinkedHashMap并没有重写put方法,而是重写了newNode以及afterNodeInsertion方法
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;
}
newNode方法会在向table中添加新的节点时调用,这里返回的是LinkedHashMap内部的Entry对象
下面看一下内部的Entry
类很简单,继承自HashMap的Node,并且添加了两个属相before和after
这里需要注意区分LinkedHashMap中Entry的before和after不同于HashMap Node中的prev和next
before和after使用来维护LinkedHashMap内部的双向队列的,一个LinkedHashMap内部只有一个双向队列
prev和next是用来维护每个bin上链表的,每个bin上都可能有一个链表
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);
}
}
下面继续看linkNodeLast
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
// 首先获取LinkedHashMap内部维护的双向队列的尾节点
LinkedHashMap.Entry<K,V> last = tail;
// 将当前节点作为尾节点
// 并且修改一些引用
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
也就是当调用LinkedHashMap的put方法时,当创建一个新的节点时,会将创建的新节点添加到LinkedHashMap内部的双向队列的尾节点后面
查看HashMap的put方法的源码可知,putVal方法的末尾会执行afterNodeInsertion
// evict代表如果为false那么HashMap处于创建模式,默认是true
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// 会根据removeEldestEntry的返回值确定是否删除最老的元素
// LinkedHashMap默认是不删除最老元素的
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
get
LinekdHashMap重写了HashMap的get方法
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
// 和HashMap的get方法相比,多了下面这部分代码
// 如果LinkedHashMap内部的双向链表维护的顺序是节点的访问顺序的话会调用afterNodeAccess
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// 首先获取双向队列的尾节点
if (accessOrder && (last = tail) != e) {
// p代表当前节点
// b代表当前节点在双向队列前面的节点
// a代表当前节点在双向队列后面的节点
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;
}
}
remove
void afterNodeRemoval(Node<K,V> e) { // unlink\
// p代表待删除节点,b代表该节点在双向队列前面的节点,a代表该节点在双向队列后面的节点
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;
}
实现lru
如果使用LinkedHashMap首先lru,那么我们就要重写removeEldestNode方法,在该方法中判断我们此时是否需要删除最老的节点