Java容器(五):LinkedHashMap实现原理

从之前的LinkedList源码分析来看,带有Linked的,其实就是和双链表相关,毫无疑问,LinkedHashMap就是HashMap再多加一个双向链表,其内部的存储规则和HashMap是一样的,但是在迭代中,HashMap是无序的,LinkedHashMap是有序的 
  LinkedHashMap维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。 
  

一、LinkedHashMap的数据结构

<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#000088">class</span> <span style="color:#4f4f4f">LinkedHashMap</span><<span style="color:#4f4f4f">K</span>,<span style="color:#4f4f4f">V</span>>
    <span style="color:#000088">extends</span> <span style="color:#4f4f4f">HashMap</span><<span style="color:#4f4f4f">K</span>,<span style="color:#4f4f4f">V</span>>
    <span style="color:#000088">implements</span> <span style="color:#4f4f4f">Map</span><<span style="color:#4f4f4f">K</span>,<span style="color:#4f4f4f">V</span>></code></span>

  LinkedHashMap继承自HashMap,因此继承了HashMap的成员变量等

<span style="color:#000000"><code><span style="color:#880000">//header是双链表的头结点</span>
<span style="color:#000088">private</span> <span style="color:#000088">transient</span> Entry<K,V> header; 
<span style="color:#880000">//accessOrder为true时,按访问顺序排序,false时,按插入顺序排序</span>
<span style="color:#000088">private</span> <span style="color:#000088">final</span> <span style="color:#000088">boolean</span> accessOrder; </code></span>

  LinkedHashMap中有两个特有的成员变量 — header和accessOrder,接下来会讲到 
  

<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#009900">LinkedHashMap</span>(<span style="color:#000088">int</span> initialCapacity, <span style="color:#000088">float</span> loadFactor) {
        <span style="color:#000088">super</span>(initialCapacity, loadFactor);
        accessOrder = <span style="color:#000088">false</span>;
}</code></span>

  LinkedHashMap共有五个构造方法,每一个方法都是调用父类HashMap的构造方法,并且accessOrder都为false,也就是默认按插入顺序排序

LinkedHashMap的数据结构

这里写图片描述

  图中,有两个比较特殊的变量,after和before,他们是Entry引用,before指向当前节点的前一个节点,after指向当前节点的后一个节点(注意绿线) 
  LinkedHashMap中,覆盖了Entry内部类,在原来的Entry的实现上,增加了before和after,这就是双联链表的前引用和后引用,并增加了addBefore和remove等重要方法 
  

<span style="color:#000000"><code>    <span style="color:#000088">private</span> static <span style="color:#000088">class</span> <span style="color:#4f4f4f">Entry</span><<span style="color:#4f4f4f">K</span>,<span style="color:#4f4f4f">V</span>> <span style="color:#000088">extends</span> <span style="color:#4f4f4f">HashMap</span>.<span style="color:#4f4f4f">Entry</span><<span style="color:#4f4f4f">K</span>,<span style="color:#4f4f4f">V</span>> {
        <span style="color:#880000">// These fields comprise the doubly linked list used for iteration.</span>
        Entry<K,V> before, after;

        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            <span style="color:#000088">super</span>(hash, key, value, next);
        }

        <span style="color:#880000">/**
         * Removes this entry from the linked list.
         */</span>
        <span style="color:#000088">private</span> void remove() {
            before.after = after;
            after.before = before;
        }

        <span style="color:#880000">/**
         * Inserts this entry before the specified existing entry in the list.
         */</span>
        <span style="color:#000088">private</span> void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = <span style="color:#000088">this</span>;
            after.before = <span style="color:#000088">this</span>;
        }</code></span>

二、LinkedHashMap的存储实现

  在HashMap中,存储实现的流程是这样的: 
  put() -> addEntry() -> createEntry() 
  put()中计算当前key的hash值所在的哈希表的索引位置,并在createEntry()中完成key-value节点在该索引位置的插入

  而在LinkedHashMap中,覆盖了addEntry() 和 createEntry()方法的实现,而没有覆盖put方法,因此这就证明了LinkedHashMap的存储规则是和HashMap一样的 
  我们来看看addEntry() 和 createEntry()方法

<span style="color:#000000"><code>    <span style="color:#000088">void</span> addEntry(<span style="color:#000088">int</span> hash, K key, V <span style="color:#000088">value</span>, <span style="color:#000088">int</span> bucketIndex) {
        <span style="color:#880000">//调用HashMap的addEntry()</span>
        super.addEntry(hash, key, <span style="color:#000088">value</span>, bucketIndex);

        <span style="color:#880000">// Remove eldest entry if instructed</span>
        Entry<K,V> eldest = header.after;
        <span style="color:#000088">if</span> (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }


    <span style="color:#000088">void</span> createEntry(<span style="color:#000088">int</span> hash, K key, V <span style="color:#000088">value</span>, <span style="color:#000088">int</span> bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = <span style="color:#000088">new</span> Entry<>(hash, key, <span style="color:#000088">value</span>, old);
        table[bucketIndex] = e;
        <span style="color:#880000">//addBefore,其他都和HashMap的createEntry相关</span>
        e.addBefore(header);
        size++;
    }</code></span>

  从上面的代码看,LinkedHashMap和HashMap中,addEntry() 和 createEntry()的实现差不多,而LinkedHashMap多调用了一个新增的addBefore方法,这是LinkedHashMap存储排序最关键的地方 
  

<span style="color:#000000"><code>       <span style="color:#880000">/**
         * 将当前节点插入到existingEntry的前面
         */</span>
       <span style="color:#000088">private</span> <span style="color:#000088">void</span> <span style="color:#009900">addBefore</span>(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = <span style="color:#000088">this</span>;
            after.before = <span style="color:#000088">this</span>;
        }</code></span>

  通过addBefore,这样便形成了一个双线链表 
  这里写图片描述

三、HashMap和LinkedHashMap遍历

  首先来看HashMap的遍历,HashMap的核心代码如下:

<span style="color:#000000"><code>    <span style="color:#000088">private</span> <span style="color:#000088">abstract</span> <span style="color:#000088">class</span> <span style="color:#4f4f4f">HashIterator</span><<span style="color:#4f4f4f">E</span>> <span style="color:#000088">implements</span> <span style="color:#4f4f4f">Iterator</span><<span style="color:#4f4f4f">E</span>> {
        Entry<K,V> next;        <span style="color:#880000">// next entry to return</span>
        <span style="color:#000088">int</span> expectedModCount;   <span style="color:#880000">// For fast-fail</span>
        <span style="color:#000088">int</span> <span style="color:#000088">index</span>;              <span style="color:#880000">// current slot</span>
        Entry<K,V> current;     <span style="color:#880000">// current entry</span>

        <span style="color:#880000">//当调用keySet().iterator()时,调用此代码</span>
        HashIterator() {
            expectedModCount = modCount;
            <span style="color:#000088">if</span> (size > <span style="color:#006666">0</span>) { <span style="color:#880000">// advance to first entry</span>
                Entry[] t = table;
                <span style="color:#880000">//从哈希表数组从上到下,查找第一个不为null的节点,并把next引用指向该节点</span>
                <span style="color:#000088">while</span> (<span style="color:#000088">index</span> < t.length && (next = t[<span style="color:#000088">index</span>++]) == <span style="color:#000088">null</span>)
                    ;
            }
        }

        <span style="color:#000088">public</span> <span style="color:#000088">final</span> <span style="color:#000088">boolean</span> hasNext() {
            <span style="color:#000088">return</span> next != <span style="color:#000088">null</span>;
        }

        <span style="color:#880000">//当调用next时,会调用此代码</span>
        <span style="color:#000088">final</span> Entry<K,V> nextEntry() {
            <span style="color:#000088">if</span> (modCount != expectedModCount)
                <span style="color:#000088">throw</span> <span style="color:#000088">new</span> ConcurrentModificationException();
            Entry<K,V> e = next;
            <span style="color:#000088">if</span> (e == <span style="color:#000088">null</span>)
                <span style="color:#000088">throw</span> <span style="color:#000088">new</span> NoSuchElementException();

            <span style="color:#880000">//如果当前节点的下一个节点为null,从节点处罚往下查找哈希表,找到第一个不为null的节点</span>
            <span style="color:#000088">if</span> ((next = e.next) == <span style="color:#000088">null</span>) {
                Entry[] t = table;
                <span style="color:#000088">while</span> (<span style="color:#000088">index</span> < t.length && (next = t[<span style="color:#000088">index</span>++]) == <span style="color:#000088">null</span>)
                    ;
            }
            current = e;
            <span style="color:#000088">return</span> e;
        }

        <span style="color:#000088">public</span> <span style="color:#000088">void</span> remove() {
            <span style="color:#000088">if</span> (current == <span style="color:#000088">null</span>)
                <span style="color:#000088">throw</span> <span style="color:#000088">new</span> IllegalStateException();
            <span style="color:#000088">if</span> (modCount != expectedModCount)
                <span style="color:#000088">throw</span> <span style="color:#000088">new</span> ConcurrentModificationException();
            Object k = current.key;
            current = <span style="color:#000088">null</span>;
            HashMap.<span style="color:#000088">this</span>.removeEntryForKey(k);
            expectedModCount = modCount;
        }
    }</code></span>

从这里可以看出,HashMap遍历时,按哈希表的每一个索引的链表从上往下遍历,由于HashMap的存储规则,最晚添加的节点都有可能在第一个索引的链表中,这就造成了HashMap的遍历时无序的。

然后来看LinkedHashMap的遍历:

<span style="color:#000000"><code>    <span style="color:#000088">private</span> <span style="color:#000088">abstract</span> <span style="color:#000088">class</span> <span style="color:#4f4f4f">LinkedHashIterator</span><<span style="color:#4f4f4f">T</span>> <span style="color:#000088">implements</span> <span style="color:#4f4f4f">Iterator</span><<span style="color:#4f4f4f">T</span>> {
        <span style="color:#880000">//nextEntry指向头结点的下一个节点,也就是双向链表中的第一个节点</span>
        Entry<K,V> nextEntry    = header.after;
        Entry<K,V> lastReturned = <span style="color:#000088">null</span>;

        <span style="color:#880000">/**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */</span>
        <span style="color:#000088">int</span> expectedModCount = modCount;

        <span style="color:#000088">public</span> <span style="color:#000088">boolean</span> hasNext() {
            <span style="color:#000088">return</span> nextEntry != header;
        }

        <span style="color:#000088">public</span> <span style="color:#000088">void</span> remove() {
            <span style="color:#000088">if</span> (lastReturned == <span style="color:#000088">null</span>)
                <span style="color:#000088">throw</span> <span style="color:#000088">new</span> IllegalStateException();
            <span style="color:#000088">if</span> (modCount != expectedModCount)
                <span style="color:#000088">throw</span> <span style="color:#000088">new</span> ConcurrentModificationException();

            LinkedHashMap.<span style="color:#000088">this</span>.remove(lastReturned.key);
            lastReturned = <span style="color:#000088">null</span>;
            expectedModCount = modCount;
        }

        <span style="color:#880000">//当调用next时,会调用此代码</span>
        Entry<K,V> nextEntry() {
            <span style="color:#000088">if</span> (modCount != expectedModCount)
                <span style="color:#000088">throw</span> <span style="color:#000088">new</span> ConcurrentModificationException();
            <span style="color:#000088">if</span> (nextEntry == header)
                <span style="color:#000088">throw</span> <span style="color:#000088">new</span> NoSuchElementException();

            从第一个节点开始,依次往下遍历
            Entry<K,V> e = lastReturned = nextEntry;
            nextEntry = e.after;
            <span style="color:#000088">return</span> e;
        }
    }</code></span>

四、总结

可见LinkedHashMap的实现原理还是比较简单的,而且LinkedHashSet的内部完全就是通过LinkedHashMap来实现的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值