一、初识LinkedHashMap
先看一下LinkedHashMap类图设计:
LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。
LinkedHashMap的基本实现思想是多态,由上图可以看到LinkedHashMap继承自HashMap,所以也就继承了HashMap中所有非private的方法,而在下图中我们可以看到LinkedHashMap中并没有什么操作数据结构的方法,也就是说LinkedHashMap操作数据结构(比如put一个数据),和HashMap操作数据的方法完全一样,无非就是细节上有一些的不同罢了。
二、源码解析
1、成员变量
// 用于指向双向链表的头部
transient LinkedHashMap.Entry<K,V> head;
//用于指向双向链表的尾部
transient LinkedHashMap.Entry<K,V> tail;
/**
* 用来指定LinkedHashMap的迭代顺序,
* true则表示按照基于访问的顺序来排列,意思就是最近使用的entry,放在链表的最末尾;false则表示按照插入顺序来
*/
final boolean accessOrder;
2、构造函数
// accessOrder默认赋值为false——按照插入顺序来排列
public LinkedHashMap() {
super();
accessOrder = false;
}
// 指定初始化容量,按照插入顺序来排列
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
// 指定初始化容量、加载因子,按照插入顺序来排列
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
// 多了一个 accessOrder的参数,用来指定按照LRU排列方式还是插入顺序的排序方式,accessOrder为true则表示按照基于访问的顺序来排列,意思就是最近使用的entry,放在链表的最末尾
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
3、get方法
public V get(Object key) {
Node<K,V> e;
//调用HashMap的getNode的方法
if ((e = getNode(hash(key), key)) == null)
return null;
//在取值后对参数accessOrder进行判断,如果为true,执行afterNodeAccess
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
// 此函数执行的效果就是将最近使用的Node,放在链表的最末尾
void afterNodeAccess(Node<K,V> e) {
//上一个节点
LinkedHashMap.Entry<K,V> last;
// 仅当按照LRU原则且e不在最末尾,才执行修改链表,将e移到链表最末尾的操作
if (accessOrder && (last = tail) != e) {
// 将e赋值临时节点p, b是e的前一个节点, a是e的后一个节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 设置p的后一个节点为null,因为执行后p在链表末尾,after肯定为null
p.after = null;
// p前一个节点不存在,说明p为头部
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
// p的后一个节点不存在
else
// a为null,说明p为尾部,设置last为b
last = b;
// last为null,说明p是第一个节点,head指向p
if (last == null)
head = p;
// 正常情况,将p设置为尾节点的准备工作,p的前一个节点为原先的last,last的after为p
else {
p.before = last;
last.after = p;
}
// 将p设置为将p设置为尾节点
tail = p;
// 修改计数器+1
++modCount;
}
}
afterNodeAccess方法的逻辑:
正常情况下:查询的p在链表中间,那么将p设置到末尾后,它原先的前节点b和后节点a就变成了前后节点。
情况一:p为头部,前一个节点b不存在,那么考虑到p要放到最后面,则设置p的后一个节点a为head
情况二:p为尾部,后一个节点a不存在,那么考虑到统一操作,设置last为b
情况三:p为链表里的第一个节点,head=p
4、put方法
LinkedHashMap的put方法调用的还是HashMap里的put,不同的是重写了里面的部分方法。关于Has和Map的put方法,可以看这里JDK1.8源码阅读(一):HashMap
LinkedHashMap将其中newNode方法以及之前设置下的钩子方法afterNodeAccess和afterNodeInsertion进行了重写,从而实现了加入链表的目的。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
// new的是自己的Entry类,然后调用了linkedNodeLast
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) {
// 将tail给临时变量last
LinkedHashMap.Entry<K,V> last = tail;
// 把new的Entry给tail
tail = p;
// 若没有last,说明p是第一个节点,head = p
if (last == null)
head = p;
// 否则就做准备工作
else {
p.before = last;
last.after = p;
}
}
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
linkNodeLast(p);
return p;
}
// 插入后把最老的Entry删除,不过removeEldestEntry总是返回false,所以不会删除,这是一个钩子方法给子类用的
void afterNodeInsertion(boolean evict) {
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
5、remove方法
LinkedHashMap的remove方法也是调用的是HashMap的remove方法,只是重写了afterNodeRemoval
void afterNodeRemoval(Node<K,V> e) {
// 与afterNodeAccess一样,记录e的前后节点b,a
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// p已删除,前后指针都设置为null,便于GC回收
p.before = p.after = null;
// 与afterNodeAccess一样类似,b,a互为前后节点的处理
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
6、LinkedHashIterator
abstract class LinkedHashIterator {
// 下一个Entry
LinkedHashMap.Entry<K,V> next;
// 当前的Entry
LinkedHashMap.Entry<K,V> current;
// 是否发生了迭代过程中的修改
int expectedModCount;
LinkedHashIterator() {
// 初始化的时候把head给next
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
// 采用的是链表方式的遍历方式
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
// 记录当前的Entry
current = e;
next = e.after;
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
LinkedHashMap遍历的方式是链表,顺序访问的话速度应该会更快一些。