集合源码分析(五)LinkedHashMap实现

版权声明:本文为博主原创文章,转载添加原文链接 https://blog.csdn.net/qq_34190023/article/details/80879392

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内部类。beforeafter表示前后指针。使用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个类。LinkedKeySetLinkedValues
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的时候就会触发删除这个机制。


猜你喜欢

转载自blog.csdn.net/qq_34190023/article/details/80879392