介绍
LinkedHashMap继承自HashMap,同时内部用双向链表维护了元素的顺序,使得她的迭代是可以预测的。
类图如下:
数据结构
LinkedHashMap的内部结构如下:
// 内部的节点,继承自HashMap的node,增加了before,after前后的指针用于实现链表。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);}}private static final long serialVersionUID = 3801124242820219131L;/*** 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*/// 迭代排序的方法,true代表按访问顺序,false代表按插入顺序final boolean accessOrder;
可以看出元素的顺序是有两种模式可供选择,一种是插入的顺序,即插入时是按什么顺序,迭代就是什么顺序,一种是元素访问的顺序,当访问一个元素时,这个元素会被放到链表的尾部,迭代的顺序就是从最久访问到最近访问的顺序。
为什么HashMap.Node有了next指针,LinkedHashMap.Entry还要增加after指针?
// HashMap的Node 定义static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;}
注意这里HashMap的Node里面是有一个next指针,维护的是一个单向的链表,这个链表是解决Hash冲突时用的,所以是和before,after有区别的,LinkedHashMap的after指针是用来控制访问顺序的。
构造方法
// 默认构造,accessOrder=false,按插入时顺序访问public LinkedHashMap() {super();accessOrder = false;}// 指定初始容量,accessOrder=falsepublic LinkedHashMap(int initialCapacity) {super(initialCapacity);accessOrder = false;}// 指定初始容量和负载因子public LinkedHashMap(int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor);accessOrder = false;}// 指定初始容量,负载因子,访问顺序,只有这个能指定访问顺序public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}
put方法
直接调用hashmap的put函数,不同的是最后一个参数evict为true,在put完之后会触发LinkedHashMap的钩子函数afterNodeInsertion
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}// 在putVal最后触发void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;// evict为true,头节点不为null,并且removeEldestEntry返回true,就会移除头节点if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;// 移除头节点removeNode(hash(key), key, null, false, true);}}
这里涉及到一个removeEldestEntry函数,默认是直接返回false,也就是说这个判断是永远走不进去,默认是不会删除最久的元素。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false;}
removeEldestEntry这个函数其实是扩展用,我们可以自定义一个子类,覆盖它的逻辑来实现一个类似LRU的算法,比如限定最多允许100个元素,超过100个就删除最老的元素。
代码如下:
private static final int MAX_ENTRIES = 100;protected boolean removeEldestEntry(Map.Entry eldest) {return size() > MAX_ENTRIES;}
这样的话,put元素超过MAX_ENTRIES时就会移除头节点的元素,再加上accessOrder = true时,移除的就是最久没有被访问的元素,利用这个可以非常简单的实现LRU(最近最少访问)算法。
newNode方法
那么双向链表,before,after是在哪里维护的?
LinkedHashMap覆盖了HashMap的newNode方法来维护双向链表的关系。
// 新增一个节点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;// 新增节点p变成尾节点tail = p;// 之前的尾节点是空,说明之前链表根本没有元素if (last == null)// p即是头节点也是尾节点head = p;else {// last不为空,将last和p进行连接,p的before是last,last的after是pp.before = last;last.after = p;}}
get方法
LinkedHashMap重写了HashMap的get方法,重写是为了添加访问顺序的逻辑,当accessOrder为true时,最后调用afterNodeAccess方法,当前访问的节点会移动到末尾。
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 lastLinkedHashMap.Entry<K,V> last;// e本身不是尾节点才进行移动if (accessOrder && (last = tail) != e) {// e赋值为p,b是p的before节点,a是p的after节点LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;// p要转移到尾节点,所以p的after是nullp.after = null;// 如果没有before节点,说明p是头节点if (b == null)// 头节点是p的after节点head = a;else// p有before节点,b节点的after指向a,跳过pb.after = a;// 判断after节点不是nullif (a != null)// after节点的before指向b,跳过pa.before = b;else// afte是null,last指向b,b有可能是nulllast = b;// last为null,那么p是头节点if (last == null)head = p;else {// last不为null,把p挂到last的后面p.before = last;last.after = p;}// p变成了尾节点tail = p;++modCount;}}
remove方法
LinkedHashmap没有重写remove方法,直接调用了HashMap的remove,移除完成后最后会调用钩子函数afterNodeRemoval。
// 移除节点后要维护e前后的before和after指针void afterNodeRemoval(Node<K,V> e) { // unlinkLinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.before = p.after = null;// e的before为null,head指向e的afterif (b == null)head = a;else// e的before不为null,before的after指向e的afterb.after = a;// e的after为null,说明e是尾节点,tail指向e的before,这样e就被移除了if (a == null)tail = b;else// e的after不为null,e的after节点的before指针指向e的before节点,跳过ea.before = b;}
