参考:
Java编程的逻辑
https://blog.csdn.net/blingfeng/article/details/79974169
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
是HashMap的子类,但可以保持元素按插入或访问有序,这与TreeMap按键排序不同
内部多了一个双向链表维护键值对的顺序,每个键值对既位于哈希表中,也位于这个双向链表中。
比如一个配置文件,其中有一些键值对形式的配置项,但其中有一些键是重复的,希望保留最后一个值,但还是按原来的键顺序输出,LinkedHashMap就是一个合适的数据结构。
比如希望的数据模型可能就是一个Map,但希望保持添加的顺序;
比如一个购物车,键为购买项目,值为购买数量,按用户添加的顺序保存。
希望Map能够按键有序,但在添加到Map前,键已经通过其他方式排好序了,这时,就没有必要使用TreeMap了,毕竟TreeMap的开销要大一些。
比如,在从数据库查询数据放到内存时,可以使用SQL的order by语句让数据库对数据排序。
LRU缓存
transient LinkedHashMap.Entry<K,V> head;//双向链表的头
transient LinkedHashMap.Entry<K,V> tail;//双向链表的尾
final boolean accessOrder;//表示是按访问顺序还是插入顺序
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);
}
}
//accessOrder默认为false,即按照插入顺序来连接,true则为按照访问顺序来连接
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
通过重写HashMap的put方法里的钩子函数来实现:当插入元素时节点会插入双向链表的链尾,如果key重复,则也会将节点移动至链尾
重写了newNode:体现了Entry要扩展Node的原因
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;
}
}
重写了afterNodeAccess:
//已经存在此节点,则移动至链尾
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;
//如果p前面没有元素,则p在修改位置之前为头结点,则直接让a成为头结点;
//否则b的尾指针指向a
if (b == null)
head = a;
else
b.after = a;
//如果a不为空,则a的头指针指向b;
//否则,说明p在修改位置之前为尾指针,令b成为尾指针
if (a != null)
a.before = b;
else
last = b;
//如果双向链表中只有p一个节点,则令p即为头结点,也为尾节点;否则,将p插入链尾
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
//设置p为链尾
tail = p;
++modCount;
}
}
/**
* p将引用移除
* b | p | a
* ------------- | ------------- | -------------
* |before| after| <==|==> |before| after| <==|==> |before| after|
* ------------- | ------------- | -------------
*
* 1.b为NULL时,则a变为头结点;否则b的after指向a
* head
* a p
* (b) ------------- -------------
* NULL <------ |before| after| ...... |before| after| (p最后将插入链尾)
* ------------- -------------
* 2.a为NULL时,则b变为链尾节点,after经过1会指向null;否则a的before指向b
*
* tail
* b p
* ------------- (a) -------------
* |before| after| -------> NULL ...... |before| after| (p最后将插入链尾)
* ------------- -------------
* 3.a,b都为NULL时,p即为头结点,又为尾节点
* 因为p前后都没有元素,则双向链表中只有p一个节点
*
* 4.最后,将p设置为tail
* (head) tail
* b p
* ------------- (a) -------------
* |before| after| -----> null或非null .......|before| after| (p最后将插入链尾)
* ------------- -------------
*
*/
此外HashMap的putVal()方法,还调用了afterNodeInsertion()方法:
//当插入时,将双向链表的头结点移除
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);
}
}
通过重写HashMap的get方法来实现:当用get()方法获取value时会将节点移动至链尾
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
// accessOder为true时,被访问的节点被置于双向链表尾部
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
通过重写HashMap的HashMap方法里的钩子函数来实现
重写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;
}
之前介绍的Map接口的实现类都有一个对应的Set接口的实现类,比如HashMap有HashSet,TreeMap有TreeSet,LinkedHashMap也不例外,它也有一个对应的Set接口的实现类LinkedHashSet。
LinkedHashSet是HashSet的子类,但它内部的Map的实现类是LinkedHashMap,所以它也可以保持插入顺序