LinkedHashMap构造函数参数详解
LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构。该结构由数组和链表+红黑树,在此基础上LinkedHashMap 增加了一条双向链表,保持遍历顺序和插入顺序一致的问题。
访问顺序存储的LinkedHashMap会把get方法对应的Entry节点放置在Entry链表表尾。
LinkedHashMap构造函数有3个参数:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
initialCapacity:是初始数组长度
loadFactor:负载因子,表示数组的元素数量/数组长度超过这个比例,数组就要扩容
accessOrder:false: 基于插入顺序(默认) true: 基于访问顺序
当accessOrder为true,每次get元素的时候,都会去执行 afterNodeAccess 方法,这个方法会将元素重新插入到双向链表的结尾。
//标准的如何在双向链表中将指定元素放入队尾 // LinkedHashMap 中覆写 //访问元素之后的回调方法 /** * 1. 使用 get 方法会访问到节点, 从而触发调用这个方法 * 2. 使用 put 方法插入节点, 如果 key 存在, 也算要访问节点, 从而触发该方法 * 3. 只有 accessOrder 是 true 才会调用该方法 * 4. 这个方法会把访问到的最后节点重新插入到双向链表结尾 */ void afterNodeAccess(Node<K,V> e) { // move node to last // 用 last 表示插入 e 前的尾节点 // 插入 e 后 e 是尾节点, 所以也是表示 e 的前一个节点 LinkedHashMap.Entry<K,V> last; //如果是访问序,且当前节点并不是尾节点 //将该节点置为双向链表的尾部 if (accessOrder && (last = tail) != e) { // p: 当前节点 // b: 前一个节点 // a: 后一个节点 // 结构为: b <=> p <=> a LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; // 结构变成: b <=> p <- a p.after = null; // 如果当前节点 p 本身是头节点, 那么头结点要改成 a if (b == null) head = a; // 如果 p 不是头尾节点, 把前后节点连接, 变成: b -> a else b.after = a; // a 非空, 和 b 连接, 变成: b <- a if (a != null) a.before = b; // 如果 a 为空, 说明 p 是尾节点, b 就是它的前一个节点, 符合 last 的定义 // 这个 else 没有意义,因为最开头if已经确保了p不是尾结点了,自然after不会是null else last = b; // 如果这是空链表, p 改成头结点 if (last == null) head = p; // 否则把 p 插入到链表尾部 else { p.before = last; last.after = p; } tail = p; ++modCount; } }
每当put元素后,都会执行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); } }
如果是自己继承LinkedhashMap来实现一个LRUCache,则需要重写removeEldestEntry方法,可以将最早访问的元素(即双向链表的头结点)删除。
@Override
//移除最近最少被访问条件之一,通过覆盖此方法可实现不同策略的缓存 protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { return this.size() > cache_size; }
总结
LinkedHashMap在HashMap的基础上使用一个双端链表维持有序的节点。这个有序并不是通常意义上的大小关系,默认情况下使用的插入顺序,意味着新插入的节点被添加到双端链表的尾部,而一旦使用了访问顺序,即accessOrder为true,那么在访问某一节点时,会将该节点移到双端链表的尾部。正因为此特性,可以在LinkedHashMap中使用三个参数的构造方法并制定accessOrder为true将LinkedHashMap实现为LRU缓存,这样经常访问的就会被移到链表的尾部,而越少访问的就在链表的头部。
由于双端链表维持了所有的节点,所以keySet()、values()以及entrySet()得到的键、值、键值对都是按照双端链表中的节点顺序的。
另外尤其需要注意的是,在put、get、remove方法中涉及到的双端链表的操作,由于都是引用的更改,所以并没有影响到HashMap的底层结构:数组+链表+红黑树。