LinkedHashMap解析
1. 简介
- HashMap没有保证散列表的存取顺序,而LinkedHashMap继承了HashMap,并在此基础上保证了元素的存取顺序,可以按照访问顺序和插入顺序两种顺序进行访问
- LinkedHashMap继承了HashMap,因此具有HashMap的所有特性。
- LinkedHashMap内部维护了一个双向链表,在每次往哈希表中put数据时,也会往双向链表中插入一份数据,虽然增加了一些时间和空间的开销,但是可以保证顺序。
2. 继承体系
//继承了HashMap,实现了Map接口
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
HashMap的内部是数组+链表+红黑树,LinkedHashMap继承于HashMap,只是又在HashMap的基础上又添加了一个双向链表,虽然耗费了一些时间和空间,但是能够保证存取顺序(按照插入顺序或是访问顺序)。HashMap在存储时只需要维护在哈希表中的存储,而LinkedHashMap不仅需要维护在哈希表中的存储,还需要维护在双向链表中的存储,因此会慢一些。
3. 成员变量
//序列化版本号
private static final long serialVersionUID = 3801124242820219131L;
//被transient修饰的变量不能被序列化
//双向链表的头结点
transient LinkedHashMap.Entry<K,V> head;
//双向链表的尾结点
transient LinkedHashMap.Entry<K,V> tail;
/*
accessOrder为true,表明在访问时按照访问顺序输出
accessOrder为false,表明在访问时按照插入顺序输出
如果不传这个accessOrder参数,默认为false
*/
final boolean accessOrder;
4. 构造方法
//指定初始容量和负载因子
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor); //调用父类HashMap的构造方法
accessOrder = false; //输出时按照插入顺序访问
}
//制定初始容量
public LinkedHashMap(int initialCapacity) {
super(initialCapacity); //调用父类HashMap的构造方法
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;
}
5. 内部类
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);
}
}
//HashMap中的内部类
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
6. 成员方法
put(key, value)
/*
LinkedHashMap中并没有重写父类HashMap的put方法,使用时直接调用父类的put方法,在构建时构建的是LinkedHashMap.Entry而不是Node
*/
//但是LinkedHashMap重写了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;
tail = p; //重置尾结点
if (last == null)
head = p; //插入前链表为空,则说明插入的结点在插入之后也是头结点
else {
p.before = last;
last.after = p;
}
}
// HashMap中的newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
以及HashMap专门留给LinkedHashMap使用的afterNodeAccess,afterNodeInsertion,afterNodeRemoval回调方法
afterNodeInsertion,removeEldestEntry是构建LruCache用到的方法,在这里可以忽略它们
LinkedHashMap中
//将结点添加到双链表的结尾
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;
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;
}
}
//根据evict决定是否要删除元素,如果实现LruCache会用到这个方法
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//LinkedHashMap默认返回false,不删除结点
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; //默认返回false
}
//在移除结点时会触发回调,从双向链表中删除
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中
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
remove()
LinkedHashMap并没有重写父类HashMap的remove方法,而是使用的父类的remove方法
get()
//LinkedHashMap重写了HashMap的get方法和afterNodeAccess方法
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder) //如果accessOrder为true,说明是按照访问顺序访问,将该结点添加到双向链表的结尾
afterNodeAccess(e);
return e.value;
}
//将结点添加到双链表的结尾
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;
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;
}
}
遍历
重写了entrySet方法
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
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;
}
afterNodeInsertion()
//根据evict决定是否要删除元素,如果实现LruCache会用到这个方法,删除最古老的元素
//
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//LinkedHashMap默认返回false,不删除结点
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
//removeEldestEntry(first)是否删除最古老元素,LinkedHashMap默认返回false,也就是不删除最古老元素
afterNodeAccess()
在添加元素或访问元素时后调用这个方法,在访问时如果accessOrder为true,会将访问的元素添加到双链表的结尾
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;
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;
}
}
总结
-
LinkedHashMap继承自HashMap,具有HashMap的所有属性
-
LinkedHashMap是在HashMap的基础上添加了一个双链表,用来保证取出时的顺序
-
如果accessOrder为false(LinkedHashMap默认为false),则取出时按照存入顺序取出
如果accessOrder为true,则取出时按照访问顺序取出(每访问一个结点,就将该结点移至双链表的结尾)
-
LinkedHashMap的很多方法都是使用的HashMap的方法。
-
LinkedHashMap可以使用LRU的缓存淘汰策略