Java集合框架之六----------LinkedHashMap和LinkedHashSet源码分析

1.LinkedHashMap源码分析

1.1 概述

LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题。除此之外,LinkedHashMap 对访问顺序也提供了相关支持。在一些场景下,该特性很有用,比如缓存。在实现上,LinkedHashMap 很多方法直接继承自 HashMap,仅为维护双向链表覆写了部分方法。

1.2 原理

HashMap的底层结构

Java集合框架之六----------LinkedHashMap和LinkedHashSet源码分析_第1张图片

LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。其结构可能如下图:

Java集合框架之六----------LinkedHashMap和LinkedHashSet源码分析_第2张图片

淡蓝色表示前驱,红色箭头表示后继

1.3 源码分析

 

 

1.3.1 Entry的继承体系及特性分析

分析一下键值对节点的继承体系。先来看看继承体系结构图

Java集合框架之六----------LinkedHashMap和LinkedHashSet源码分析_第3张图片

TreeNode不继承它的一个内部类Node,却继承LinkedHashMap的内部类Entry,个人认为是TreeNode在维持红黑树的时候,还会继续维持链表的结构,而且是双向链表,通过prev和next。

①LinkedHashMap和TreeMap都实现了entry的排序,有什么区别:

--TreeMap按照key排序,而LinkedHashMap按照entry插入或者访问顺序排序

--LinkedHashMap保持entry有序方式是调整链表的before,after指针,而treeMap保持entry有序的方式是对tree结构的调整,因此显然LinkedHashMap代价小

②特殊的构造函数LinkedHashMap(int, float,Boolean)

--boolean = true;迭代器顺序遵循LRU原则,最近最少访问的entry会被最先遍历到,这种map结构非常适合构建LRU缓存

③removeEldestEntry(map.entry)

--通过覆写,可以实现:当添加新的映射到map中时,强制自动移除过期的映射。

--过期数据:

----双链表按插入entry排序,则为最早插入双链表的entry

----双链表按访问entry排序,则为最近最少访问的entry

④和hashmap的比较

--增删改查性能比hashmap要差一些,因为要维护双向链表

--迭代器执行时间长短

----LinkedHashMap和size成比例,HashMap和capacity成比例,因此hashmap相对比较费时,以为size<=capacity

⑤三个特殊回调方法

--afterNodeRemoval,删除节点后,双向链表中unlink

--afterNodeInsertion,插入节点后,是否删除eldest节点

--afterNodeAccess,访问节点后,是否调整当前访问节点的顺序

--这三个方法保证了双向链表的有序性,在hashmap中方法体为空,此处进行覆写

⑥为了清晰理解LHM插入节点后的结构,给出一个例子

--hash函数为:h(key)=key%8

--依次插入元素:(k,v)对依次为:(1,11),(2,12),(3,13),(9,19),(17,27)

--给出结构图:(图中node节点未写出value,只写了key) 

Java集合框架之六----------LinkedHashMap和LinkedHashSet源码分析_第4张图片

 

1.3.2 属性

保存头指针和尾指针

transient LinkedHashMap.Entry head;

transient LinkedHashMap.Entry tail;

通过accessOrder来决定双向链表的排序

final boolean accessOrder;

false:构造函数的默认值,表示按照entry的插入顺序进行排序 ,故每插入一个新的entry则添加到双向链表的尾部。(注意:如果插入entry的key之前就存在双向链表中,则此次插入操作只会更改value,不会更改原双向链表各个entry的顺序)

true:表示按entry的访问顺序进行排序,根据LRU原则,最新访问的entry排列在双链表的尾部

1.3.3 构造函数

 

指定初始化容量和扩容负载因子,默认按插入顺序

public LinkedHashMap(int initialCapacity, float loadFactor) {

        super(initialCapacity, loadFactor);

        accessOrder = false;

}

指定初始化容量,默认按插入顺序

public LinkedHashMap(int initialCapacity) {

        super(initialCapacity);

        accessOrder = false;

}

调用父类的无参构造器

public LinkedHashMap() {

        super();

        accessOrder = false;

}

调用父类插入集合的方法putmapentries

public LinkedHashMap(Mapextends K, ? extends V> m) {

        super();

        accessOrder = false;

        putMapEntries(m, false);

}

初始化容量负载因子,和迭代顺序,false按插入,true按访问

public LinkedHashMap(int initialCapacity,

                         float loadFactor,

                         boolean accessOrder) {

        super(initialCapacity, loadFactor);

        this.accessOrder = accessOrder;

    }

 

1.3.4增加元素

LinkedHashMap并没有重写任何put方法,但是重写了构建新节点的newNode方法。newNode方法会在hashMap中的putVal中被调用,putVal方法会在批量插入数据putMapEntries(Map, Boolean evict)或者插入单个数据public V put (K key, V value)时候被调用

LinkedHashMap重写了newNode方法,每次构建新节点时,通过linkNodeLast(p);将新节点链接在内部双向链表的尾部,创建了一个以null 为节点的entry

Java集合框架之六----------LinkedHashMap和LinkedHashSet源码分析_第5张图片

在构建新节点时,构建的是`LinkedHashMap.Entry` 不再是`Node`

Node newNode(int hash, K key, V value, Node e) {

        LinkedHashMap.Entry p =

            new LinkedHashMap.Entry(hash, key, value, e);

        linkNodeLast(p);

        return p;

}

将新增的节点,连接在链表的尾部

private void linkNodeLast(LinkedHashMap.Entry p) {

        LinkedHashMap.Entry last = tail;

        tail = p;

        if (last == null)

            head = p;

        else {

            p.before = last;

            last.after = p;

        }

}

以及HashMap专门预留给LinkedHashMap的afterNodeAccess() afterNodeInsertion() afterNodeRemoval() 方法。

当accessOrder为true时,将节点移动到最后

void afterNodeAccess(Node e) { // move node to last

        LinkedHashMap.Entry last;

        if (accessOrder && (last = tail) != e) {

            LinkedHashMap.Entry p =             (LinkedHashMap.Entry)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;

        }

}

如果accessOrdertrue且当前节点不是tail节点

当前节点设为p,并得到pbeforeafter节点,分别赋值为ba

为了将p移到最后,将pafter设为null,如果pbeforenull,说明p就是head节点,将head指向pafter;如果before不为null,将bafter指向a;如果anull,说明p就是last节点,将last指向b,因为if以外会有重新设置tail,此处个人认为写不写无所谓;如果a不为null,将abefore指向b;如果lastnull,说明p是新插入的节点,并且链表为空,因此将head指向p,如果last不为null;将p放在last后面,设置新的tail指向p

-----------------------------------------------------------

Evictfalse表示哈希表处于创建模式,只有在使用map集合作为构造器创建linkedHashMap或者HashMap时才会为false

void afterNodeInsertion(boolean evict) { // possibly remove eldest

        LinkedHashMap.Entry first;

        if (evict && (first = head) != null && removeEldestEntry(first)) {

            K key = first.key;

            removeNode(hash(key), key, null, false, true);

        }

}

需要下列三个条件才能进入if语句

1.evict为true,只要不是构造方法中插入map集合,evict为true,否则为false

2.first!=null 表明表不为空,基本满足

3.removeEldestEntry()返回true,该方法定义删除最老节点的规则

--------------------------------------------------------------

protected boolean removeEldestEntry(Map.Entry eldest) {

        return false;

}

LinkedHashMapremoveEldestEntry方法默认返回false,因此不会进入if语句;

removeEldestEntry该方法用于定义删除最老元素的规则,一旦需要删除最老节点,那么将会调用removeNode删除节点。例如重写removeEldestEntry方法,如果一个个链表只能维持100元素,那么插入第101个元素时,

public boolean removeEldestEntry(Map.Entry eldest){

       return size()>100;

}

如果有101个元素,removeEldestEntry返回true,进入if语句,删除第一个元素first;第一个元素表示最近最少使用的元素,因为LinkedHashMap afterNodeAccess方法将最近访问的放到了表尾

-----------------------------------------------------------------

主要是将节点从双向链表中移除

void afterNodeRemoval(Node e) { // unlink

        LinkedHashMap.Entry p =

            (LinkedHashMap.Entry)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;

}

 

1.3.5 查找

LinkedHashMap重写了get和getOrDefault

-----------------------------------------

对比hashmap中的实现,linkedHashMap只是增加了accessOrdertrue的情况,要去回调afterNodeAccess,将e节点移到表尾。

public V get(Object key) {

        Node e;

        if ((e = getNode(hash(key), key)) == null)

            return null;

        if (accessOrder)

            afterNodeAccess(e);

        return e.value;

}

 

public V getOrDefault(Object key, V defaultValue) {

       Node e;

       if ((e = getNode(hash(key), key)) == null)

           return defaultValue;

       if (accessOrder)

           afterNodeAccess(e);

       return e.value;

   }

1.3.6 containsValue

public boolean containsValue(Object value) {

        Node[] tab; V v;

        if ((tab = table) != null && size > 0) {

            for (int i = 0; i < tab.length; ++i) {

                for (Node e = tab[i]; e != null; e = e.next) {

                    if ((v = e.value) == value ||

                        (value != null && value.equals(v)))

                        return true;

                }

            }

        }

        return false;

    }

---------------------------------------------------------------------

上述是HashMapcontainsValue,通过先找数组,再找链表;而LinkedHashMap直接通过headafter来寻找元素,会比hashMap更高效

public boolean containsValue(Object value) {

        for (LinkedHashMap.Entry e = head; e != null; e = e.after) {

            V v = e.value;

            if (v == value || (value != null && value.equals(v)))

                return true;

        }

        return false;

    }

1.3.7 遍历

返回一个LinkedEntrySet()

public Set> entrySet() {

        Set> es;

        return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;

    }

 

final class LinkedEntrySet extends AbstractSet> {

        public final Iterator> iterator() {

            return new LinkedEntryIterator();

        }

}

通过LinkedEntryIterator实现entry的遍历,这里就是迭代器里的nextNode方法代表next()方法

final class LinkedEntryIterator extends LinkedHashIterator

        implements Iterator> {

        public final Map.Entry next() { return nextNode(); }

}

 

abstract class LinkedHashIterator {

        LinkedHashMap.Entry next;

        LinkedHashMap.Entry current;

        int expectedModCount;

 

        LinkedHashIterator() {

初始化时,nextLinkedHashMap内部维护的双向链表的表头

            next = head;

记录当前的modcount,以满足fail-fast

            expectedModCount = modCount;

            current = null;

        }

 

        public final boolean hasNext() {

            return next != null;

        }

通过双向链表的after进行迭代

        final LinkedHashMap.Entry nextNode() {

            LinkedHashMap.Entry e = next;

            if (modCount != expectedModCount)

                throw new ConcurrentModificationException();

            if (e == null)

                throw new NoSuchElementException();

            current = e;

            next = e.after;

            return e;

        }

底层还是调用hashmap的删除方法

        public final void remove() {

            Node 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;

        }

    }

 

    final class LinkedKeyIterator extends LinkedHashIterator

        implements Iterator {

        public final K next() { return nextNode().getKey(); }

    }

 

    final class LinkedValueIterator extends LinkedHashIterator

        implements Iterator {

        public final V next() { return nextNode().value; }

    }

 

    final class LinkedEntryIterator extends LinkedHashIterator

        implements Iterator> {

        public final Map.Entry next() { return nextNode(); }

    }

 

 

}

1.3.8 LRU通过LinkedHashMap实现

 

class LRULinkedHashMap extends LinkedHashMap {

       // 缓存大小

       private int capacity;

       public LRULinkedHashMap(int capacity) {

              // 构造时指定accessOrder为true,按get()时间排序

              super(16, 0.75, true);

              this.capacity = capacity;

       }

       @Override

       public boolean removeEldestEntry(Map.Entry eldest) {

              // 如果添加缓存,即put()后size > capacity,就会移除链表队头,即最近最少使用的缓存项

              return size() > capacity;

       }

}

2.LinkedHashSet源码分析

 前面已经说过LinkedHashSet是对LinkedHashMap的简单包装,对LinkedHashSet的函数调用都会转换成合适的LinkedHashMap方法,因此LinkedHashSet的实现非常简单,这里不再赘述。

public class LinkedHashSet

    extends HashSet

    implements Set, 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;

    }

    ......

}

你可能感兴趣的:(java集合框架)