《java集合》–LinkedHashMap
说明:此文章基于jdk1.8
参考:LRU[【Java集合源码剖析】LinkedHashmap源码剖析](http://blog.csdn.net/ns_code/article/details/37867985)
简介
LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,同时又维护了一个双线链表结构,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同。
LinkedHashMap可以用来实现LRU算法
数据结构
LinkedHashMap是HashMap的子类,与HashMap有相同的数据结构,同时为了实现按照插入顺序或者访问顺序进行迭代,LinkedHashMap内部维护了一个双向链表结构。
基本属性
transient LinkedHashMap.Entry head; 指向LinkedHashMap中双向链表的首部指针
transient LinkedHashMap.Entry tail;指向LinkedHashMap中双向链表的尾部指针
final boolean accessOrder;
1、true:按照访问顺序倒序排序,基于LRU算法(最少访问算法),每次操作链表中的一个元素后,都将元素移除然后放到链表的最后,这样保证最近访问的放在最后,保证了最少访问的元素最优先被遍历访问到。
2、false:按照插入顺序排序
构造器
构造器调用了父类 HashMap 的相关构造方法来构造一个底层存放的 table 数组,需要初始长度和负载因子,同时比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;
}
添加元素
LinkedHashMap没有覆写HashMap中的put方法,而是覆写了newNode方法
调用HashMap中的put方法(基于1.8)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
//新增一个节点,LinkedHashMap将同时向双向链表尾部添加节点
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//新增一个节点,LinkedHashMap将同时向双向链表尾部添加节点
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//新增一个节点,LinkedHashMap将同时向双向链表尾部添加节点
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//如果对已存在的key进行覆盖,则相当于访问该key,LinkedHashMap操作双向链表中的元素
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
LinkedList中覆写了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;
}
//覆写红黑二叉树node
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;
}
将新添加的元素添加到双向链表的尾部
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;
}
}
当执行了get或者put已存在的key时,如果accessOrder是true,表示按照LRU算法,将本次访问的元素移除并添加到双向链表的最后
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//判断accessOrder
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中的Entry多了双向链表中 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);
}
}
删除元素
LinkedHashMap 没有覆写HashMap中的remove方法,只是覆写了remove方法的回调,来删除双向链表中的节点
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;
}
获取元素
LinkedHashMap 根据key值获取value时,调用的是父类HashMap中的get方法,在获取完后,判断accessOrder是否为true,如果为true,将本次访问的节点从双向链表中移除,然后添加到双向链表的最后,保证LRU算法迭代访问双向链表
public V get(Object key) {
Node<K,V> e;
//调用父类HashMap中getNode方法
if ((e = getNode(hash(key), key)) == null)
return null;
//accessOrder若是true,按照LRU算法访问双向链表
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return defaultValue;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
遍历LinkedHashMap
遍历LinkedHashMap是访问的双向链表,保证了顺序性
public void forEach(BiConsumer<? super K, ? super V> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.key, e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
迭代器操作是LinkedHashMap中的双向链表
final class LinkedKeyIterator extends LinkedHashIterator
implements Iterator<K> {
public final K next() { return nextNode().getKey(); }
}
final class LinkedValueIterator extends LinkedHashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
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();
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是HashMap和LinkedList两个集合类的存储结构的结合。在LinkedHashMapMap中,所有put进来的Entry都跟HashMap一样保存,但它又额外定义了一个双向链表,每次put进来Entry,除了将其保存到对哈希表中对应的位置上外,还要将其插入到双向循环链表的尾部。
LinkedHashMap由于继承自HashMap,因此它具有HashMap的所有特性,同样允许key和value为null
LinkedHashMap不是线程安全的
accessOrder为false时按照节点的插入顺序排序;为true按照访问顺序倒序排序LRU算法。
LinkedHashSet
LinkedHashSet 是对LinkedHashMap的简单封装,对LinkedHashSet的函数调用都会转换成合适的LinkedHashMap方法。
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
......
// LinkedHashSet里面有一个LinkedHashMap
public LinkedHashSet(int initialCapacity, float loadFactor) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
......
public boolean add(E e) {//简单的方法转换
return map.put(e, PRESENT)==null;
}
......
}