JDK之LinkedHashMap源码解析

本文解析了Java中LinkedHashMap的实现原理,介绍了其与HashMap的区别,并通过示例代码展示了LinkedHashMap保持插入顺序的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

刚入java不久的程序猿,对于简单的使用已毫不满足,最终为了一探究竟,翻开了JDK的源码,以下观点为自己的理解及看了多篇博客的总结,欢迎各位大神指出不对的地方,当然也欢迎和我一样刚学的同学,一起加油努力吧~~

LinkedHashMap是什么

首先看到这个名字,大家肯定会想LinkedHashMap会不会和HashMap有关系。没错,他们是有关系的, LinkedHashMap是继承了HashMap的,也就是说HashMap是LinkedHashMap的父类,那他们之间到底有什么区别呢,我这里先说下区别,下面我会通过代码来让大家看清这些区别,LinkedHashMap是有序的,但是HashMap是无序的,这里的有序指的是放入顺序,对于LinkedHashMap简单的介绍到这里,有了初步了解后,我们来看下具体的源码吧,如果没有看过HashMap的小伙伴建议先去看一下HashMap源码解析

LinkedHashMap源码解析

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>{
...
}    

刚刚上面已经说了LinkedHashMap继承了HashMap,具体代码请看HashMap源码解析,就不详细介绍了,接下来我们看一段测试的代码,了解下刚刚说的有序


package test;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;

public class Test {

    public static void main(String[] args) {
        LinkedHashMap<String, String> linkedHashMap=new LinkedHashMap<>();
        HashMap<String, String> hashMap=new HashMap<>();
        linkedHashMap.put("1", "1");
        linkedHashMap.put("2", "2");
        linkedHashMap.put("3", "3");
        linkedHashMap.put("4", "4");
        linkedHashMap.put("5", "5");
        linkedHashMap.put("6", "6");
        linkedHashMap.put("7", "7");
        hashMap.put("1", "1");
        hashMap.put("2", "2");
        hashMap.put("3", "3");
        hashMap.put("4", "4");
        hashMap.put("5", "5");
        hashMap.put("6", "6");
        hashMap.put("7", "7");
        for (Entry<String, String> string : linkedHashMap.entrySet()) {
            System.out.println(string);
        }
        System.out.println("-----------------------");

        for (Entry<String, String> string : hashMap.entrySet()) {
            System.out.println(string);
        }
    }

}   

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

1=1
2=2
3=3
4=4
5=5
6=6
7=7
*************************************
3=3
2=2
1=1
7=7
6=6
5=5
4=4

上面为测试的代码和测试的结果,我们可以看到HashMap输出后之前的顺序已经打乱了,是无序的,而LinkedHashMap按照放入顺序输出,是有序的,有了基本概念后我们去探究一下源码

    /**
     * The head of the doubly linked list.
     */
    private transient Entry<K,V> header;

    /**
     * LinkedHashMap排序标志,true表示按照访问排序,false表示按照插入排序
     */
    private final boolean accessOrder;
    /**
     * 调用父类HashMap中构造方法,参数为初始化容量和加载因子
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    /**
     * 调用父类HashMap中构造方法,参数为初始化容量,加载因子为HashMap中默认的0.75f
     */
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    /**
     * 调用父类HashMap中构造方法,参数为HashMap中默认的初始化容量16,加载因子0.75f
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    /**
     * 调用父类HashMap中构造方法,参数为map
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super(m);
        accessOrder = false;
    }

    /**
     * 调用父类HashMap中构造方法,参数为初始化容量,加载因子,排序方法
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

上面就是LinkedHashMap的构造方法,都是调用了父类的构造,所以只要HashMap看懂了,这个没太大难度,这里主要注意的就是调用完父类构造后,设置了一个排序方法,如未传入参数,默认为按照插入顺序排序,接下来我们继续看代码

    /**
     * 重写父类inti方法,初始化header
     */
    @Override
    void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

    /**
     * 重写父类transfer方法,父类resize方法将会调用此方法,重写该方法目的是为了提高效率
     */
    @Override
    void transfer(HashMap.Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e = header.after; e != header; e = e.after) {
            if (rehash)
                e.hash = (e.key == null) ? 0 : hash(e.key);
            int index = indexFor(e.hash, newCapacity);
            e.next = newTable[index];
            newTable[index] = e;
        }
    }


    /**
     * 判断是否包含这个value
     */
    public boolean containsValue(Object value) {
        // 参数为null时,进行特殊处理
        if (value==null) {
            for (Entry e = header.after; e != header; e = e.after)
                if (e.value==null)
                    return true;
        } else {
            for (Entry e = header.after; e != header; e = e.after)
                if (value.equals(e.value))
                    return true;
        }
        return false;
    }

    /**
     * 根据key值获取value
     */
    public V get(Object key) {
        //调用HashMap的getEntry方法获取entry
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        //这个方法在后面内部类的时候会说到,false时这个方法毫无作用
        e.recordAccess(this);
        return e.value;
    }

    /**
     * 清空LinkedHashMap,调用父类clear方法,header置空
     */
    public void clear() {
        super.clear();
        header.before = header.after = header;
    }

上面介绍了方法的作用,方法里的循环,我们来看一下下面的内部类来了解下

    /**
     * LinkedHashMap entry.
     */
    private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // 用于循环的双向链表
        Entry<K,V> before, after;

        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }

        /**
         * 删除链表中的entry
         */
        private void remove() {
            before.after = after;
            after.before = before;
        }

        /**
         * 添加entry到集合的尾部
         * 这里我刚开始看的时候有点晕,这里大概说下,after与before也是entry
         */
        private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

        /**
         * 记录访问次数并将访问过的entry移到尾部
         */
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            //只有accessOrder为true时才执行
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }

上面的内部类介绍了entry的数据结构以及添加时所做的事,还剩下一些东西,下面一起列出来了

    //迭代
    private abstract class LinkedHashIterator<T> implements Iterator<T> {
        Entry<K,V> nextEntry    = header.after;
        Entry<K,V> lastReturned = null;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            return nextEntry != header;
        }

        public void remove() {
            if (lastReturned == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            LinkedHashMap.this.remove(lastReturned.key);
            lastReturned = null;
            expectedModCount = modCount;
        }

        Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();

            Entry<K,V> e = lastReturned = nextEntry;
            nextEntry = e.after;
            return e;
        }
    }

    private class KeyIterator extends LinkedHashIterator<K> {
        public K next() { return nextEntry().getKey(); }
    }

    private class ValueIterator extends LinkedHashIterator<V> {
        public V next() { return nextEntry().value; }
    }

    private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() { return nextEntry(); }
    }

    // These Overrides alter the behavior of superclass view iterator() methods
    Iterator<K> newKeyIterator()   { return new KeyIterator();   }
    Iterator<V> newValueIterator() { return new ValueIterator(); }
    Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }

    /**
     * 添加entry
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //调用父类方法
        super.addEntry(hash, key, value, bucketIndex);

        // 移除双向链表中近期最少使用的点
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }

    /**
     * 添加entry,与addEntry区别是不扩容或移除近期最少使用的点
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

    /**
     * 移除双向链表中近期最少使用的点
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

好了LinkedHashMap分析就到这了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值