HashMap是无序的,LinkedHashMap基于HashMap实现,重新进行了包装,对外表现为双向链表结构,因而是有序的,数据的存储本质上是由父类HashMap完成。LinkedHashMap的增删改查方法都是基于HashMap,只是做了增强。
1.接口实现
继承了HashMap,实现了Map接口。
LinkedHashMap是有序的:accessOrder为false时(默认),输出顺序为insert-order。当accessOrder为true时,输出顺序为access-order,即被访问的元素调整到链表最后面。
2.成员变量
//双向链表的头结点
transient LinkedHashMap.Entry<K,V> head;
//双向链表的尾结点
transient LinkedHashMap.Entry<K,V> tail;
//元素迭代时的顺序,true表示按照访问顺序,即元素被访问到了则在最后面输出。
//false表示按照元素的插入顺序输出
final boolean accessOrder;
//继承HashMap的Node
static class Entry<K,V> extends HashMap.Node<K,V> {
//before为节点的前一个节点引用
//after为节点的后一个节点引用
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
继承自HashMap,Node<K,V> table,size,threshold等用的父类的属性,新增Entry<K,V> head头节点,Entry<K,V> tail尾节点。Entry中属性before,after表示链表的前后节点的引用。Entry继承Node节点。accessOrder遍历时的输出顺序,为false表示按插入顺序输出,为true表示按访问顺序输出。
数据结构:双向链表
3.构造方法
//指定初始容量和加载因子,LinkedHashMap默认按照元素的插入顺序迭代
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
//指定初始化容量
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
//无参构造
public LinkedHashMap() {
super();
accessOrder = false;
}
//通HashMap,Map实现类的转换
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;
}
4.添加元素过程
由于继承自HashMap,添加构成和HashMap一致。主要有3点区别。
1. tab[i] = newNode(hash, key, value, null);
创建一个新的节点,将新节点放在链表的最后。
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和尾结点tail都指向同一个节点p
head = p;
else {
p.before = last;
last.after = p;
}
}
2. afterNodeAccess(e);
当Map中的节点e被访问到,且accessOrder为true时,将该节点放在链表的最后。
//当元素被访问到时,若accessOrder设置为true,则将该元素放在链表的最后。
//这个最后是在LinkedHashMap双向链表的最后,而不是HashMap的链表。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
//调整前:b p a last
//调整后:b a last p
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;
}
}
3. afterNodeInsertion(evict);
当evict为true时,可能会移除链表中最年长的节点,即链表中的头节点。该方法对LinkedHashMap无效,因为总是返回false。针对于继承于LinkedHashMap的缓存类有效,因为能节约内存空间。
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);
}
}
5.查找元素过程
public V get(Object key) {
Node<K,V> e;
//走HashMap的元素查找
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
//找到后将元素放置在链表的最后
afterNodeAccess(e);
return e.value;
}
同HashMap元素查找。当查到的key被访问到,且accessOrder为true时,将该节点放在链表的最后。
6.移除元素过程
//解除删除节点p在双向链表中的引用
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;
}
同HashMap元素删除。afterNodeRemoval(node)。解除删除节点node在LinkedHashMap双向链表的相关引用 before,after。
7.元素的遍历
//双向链表的头结点作为迭代的第一个节点
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = 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();
current = e;
//按照双向链表的顺序遍历节点
next = e.after;
return e;
}
双向链表从head到tail顺序输出。