Map接口额的哈希表和链接列表实现,允许null值和null键,不保证映射顺序,但却保证顺序永久不变
简单来说就是LinkedHashMap相比于HashMap来说就是多了这些红色的双向链表而已。
linkedHashMap的核心就是存在存储顺序和可以实现LRU算法
LinkedHashMap 实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
LinkedHashMap而言,它继承于 HashMap、底层使用哈希表与双向链表来保存所有元素,通过重写父类相关的方法,来实现自己的链接列表特性。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
//LinkedHashMap采用的 hash算法和 HashMap相同,但它重新定义了数组中保存的元素 Entry,该 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);
}
}
//用一个静态内部类Entry表示双向链表,实现了HashMap中的Node内部类。before和after表示前后指针。使用LinkedHashMap有序就是因此产生。
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
final boolean accessOrder; // 对于访问顺序为true,对于插入顺序为false (是否开启LRU规则)
void reinitialize() {
super.reinitialize();
head = tail = null;
}
//有5个构造函数,可以设置容量和加载因子,且在默认情况下是不开启LRU规则的。同时还以用具有继承K,V关系的map进行初始化。
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
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;
}
public void clear() {
super.clear();
head = tail = null;
}
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new LinkedKeySet();
keySet = ks;
}
return ks;
}
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new LinkedValues();
values = vs;
}
return vs;
}
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
// 内部维护了3个类。LinkedKeySet和LinkedValues
final class LinkedKeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<K> iterator() {
return new LinkedKeyIterator();
}
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED | Spliterator.ORDERED | Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super K> 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);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
final class LinkedValues extends AbstractCollection<V> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<V> iterator() {
return new LinkedValueIterator();
}
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |Spliterator.ORDERED);
}
public final void forEach(Consumer<? 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.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new LinkedEntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry<?,?> e =(Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null &&candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |Spliterator.ORDERED |Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super Map.Entry<K,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);
if (modCount != mc) throw new ConcurrentModificationException();
}
}
LRU规则实现,最近访问放在双向链表最后面
void afterNodeAccess(Node<K,V> e) { // 把当前节点e放到双向链表尾部
LinkedHashMap.Entry<K,V> last;
//accessOrder就是我们前面说的LRU控制,当它为true,同时e对象不是尾节点(如果访问尾节点就不需要设置,该方法就是把节点放置到尾节点)
if (accessOrder && (last = tail) != e) {
//用a和b分别记录该节点前面和后面的节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b =p.before, a = p.after;
//释放当前节点与后节点的关系
p.after = null;
//如果当前节点的前节点是空,
if (b == null)
//那么头节点就设置为a
head = a;
else
//如果b不为null,那么b的后节点指向a
b.after = a;
//如果a节点不为空
if (a != null)
//a的后节点指向b
a.before = b;
else
//如果a为空,那么b就是尾节点
last = b;
//如果尾节点为空
if (last == null)
//那么p为头节点
head = p;
else {
//否则就把p放到双向链表最尾处
p.before = last;
last.after = p;
}
//设置尾节点为P
tail = p;
//LinkedHashMap对象操作次数+1
++modCount;
}
}
afterNodeAccess方法就是如何支持LRU规则的,如果在accessOrder为true的时候,节点调用这个函数,就会把该节点放置到最后面。put,get等都会调用这个函数来调整顺序
重写了get方法
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)// 记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除
afterNodeAccess(e);// 把该节点移到双向链表最后面
return e.value;
}
那么有些小伙伴就问了,那么put方法里面调用了嘛?肯定调用了,但是你不一定找得到,因为LinkedHashMap压根没有重写put方法,每次用LinkedHashMap的put方法的时候,其实你调用的都是HashMap的put方法。那为什么它也会执行afterNodeAccess()方法呢,因为这个方法HashMap就是存在的,但是没有实现,LinkedHashMap重写了afterNodeAccess()这个方法。下面给出HashMap的put局部方法:
if(e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue== null)
e.value = value;
afterNodeAccess(e);//把当前节点移动到双向链表最尾处
return oldValue;
}
其实这个方法在很多的调用都有,这里就不一一解释了。同时,LinkedHashMap对于红黑树的节点移动处理也有专门的方法,这里就不再深入讲解了,方式也是差不多。
⑤分析一个LinkedHashMap自带的移除最头(最老)数据的方法
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
LinkedHashMap有一个自带的移除最老数据的方法,但是这个方法永远是返回false,但是如果我们要实现,可以在继承的时候重写这个方法,给定一个条件就可以控制存储在LinkedHashMap中的最老数据何时删除。具体的在我以前博文多种方式实现缓存机制中有写过。触发这个删除机制,一般是在PUT一个数据进入的时候,但是LinkedHashMap并没有重写Put方法如何实现呢?在LinekdHashMap中,这个方法被包含在afterNodeInsertion()方法之中,而这个方法是重写了HashMap的,但是HashMap中并没有去实现它,所以在put的时候就会触发删除这个机制。