【源码】LinkedHashMap源码剖析

注:以下源码基于jdk1.7.0_11

之前的两篇文章通过源码分析了两种常见的Map集合,HashMap和Hashtable。本文将继续介绍另一种Map集合——LinkedHashMap。

顾名思义,LinkedHashMap除了是一个HashMap之外,还带有LinkedList的特点,也就是说能够保持遍历的顺序和插入的顺序一致,那么它是怎么做到的呢?下面我们开始分析。

首先看构造器。

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

LinkedHashMap直接继承自HashMap,所以拥有HashMap的大部分特性,比如支持null键和值,默认容量为16,装载因子为0.75,非线程安全等等。

但是LinkedHashMap还有很多个性的地方,下面来看成员变量:

 private transient Entry<K,V> header;//内部双向链表的头结点
    /**
     *代表这个链表的排序方式,true代表按照访问顺序,false代表按照插入顺序。
     */
 private final boolean accessOrder;

LinkedHashMap比HashMap多了两个成员变量,其中header代表内部双向链表的头结点,后面我们就会发现,LinkedHashMap除了有个桶数组容纳所有Entry之外,还有一个双向链表保存所有Entry引用。遍历的时候,并不是去遍历桶数组,而是直接遍历双向链表,所以LinkedHashMap的遍历时间不受桶容量的限制,这是它和HashMap的重要区别之一。

而这个accessOrder代表的是是否按照访问顺序,true代表是,默认是插入顺序。所以我们可以将accessOrder置为true来实现LRU算法,这可以用来做缓存。

再看构造器:

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(m);
        accessOrder = false;
    }
   public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

构造器首先都会调用父类也就是HashMap的构造器来初始化桶数组,而accessOrder之后会被初始化,除了最后面的一个构造器允许指定accessOrder外,其他构造器都默认将accessOrder置为了false。

读者可能很奇怪,不是还有个header么,这个双向链表为啥不在构造器中初始化呢?这得回到HashMap中查看hashMap的构造器了:

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
       ... ...
        init();
    }

HashMap构造器最后一步调用了一个init方法,而这个init方法在HashMap中是个空实现,没有任何代码。这其实就是所谓的“钩子”,具体代码由子类实现,如果子类希望每次构造的时候都去做一些特定的初始化操作,可以选择复写init方法。

我们看到LinkedHashMap中确实复写了init:

 @Override
    void init() {
        header = new Entry<>(-1, null, null, null);//初始化双向链表
        header.before = header.after = header;//不光是双向链表,还是循环链表
    }

在init方法中,果然初始化了双向链表,而且我们还发现,这不光是个双向链表,还是个循环链表。

HashMap内部的Entry类并没有before和after指针,也就是说LinkedHashMap自己重写了一个Entry类

private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;//前驱、后继指针
        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }
        /**
         * Removes this entry from the linked list.
         */
        private void remove() {
            before.after = after;
            after.before = before;
        }
        /**
         * Inserts this entry before the specified existing entry in the list.
         */
        private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
        /**
         * This method is invoked by the superclass whenever the value
         * of a pre-existing entry is read by Map.get or modified by Map.set.
         * If the enclosing Map is access-ordered, it moves the entry
         * to the end of the list; otherwise, it does nothing.
         */
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }

这里的Entry选择继承父类的Entry类,也就是说LinkedHashMap中的Entry拥有三个指针,除了前驱后继指针外用于双向链表的连接外,还有一个next指针用于解决hash冲突(引用链)。

除此之外,Entry新增了几个方法,remove和addbefore用来操作双向链表不用多说。而recordAccess方法比较特殊,这个方法在HashMap中也是空实现,并在put方法中会调用此方法:

 public V put(K key, V value) {//HashMap的put方法
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);//发生覆盖操作时,会调用此方法
                return oldValue;
            }
        }
      ... ...
    }

此外,在LinkedHashMap的get方法中,也会调用此方法:

 public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }

也就是说,只要涉及到访问结点,那么就会调用这个方法。观察该方法的逻辑:如果accessOrder为true,那么会调用addBefore方法将当前Entry放到双向链表的尾部,最终在我们遍历链表的时候就会发现最近最少使用的结点的都集中在链表头部(从近期访问最少到近期访问最多的顺序),这就是LRU。

LinkedHashMap并没有复写put方法,但是却复写了addEntry和createEntry方法,之前分析HashMap的时候我们就知道了,put方法会调用addEntry将键值对挂到桶的某个合适位置,而addEntry又会调用createEntry方法创建一个键值对对象。因而,LinkedHashMap其实是间接更改了put方法,想想也很容易理解,LinkedHashMap除了要向桶中添加键值对外,还需向链表中增加键值对,所以必须得修改put方法。

void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);
        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;//标记最少访问的对象
        if (removeEldestEntry(eldest)) {//判断是否需要删除这个对象---->可由子类实现来提供缓存功能
            removeEntryForKey(eldest.key);
        }
    }
    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++;
    }

createEntry方法会将键值对分别挂到桶数组和双向链表中。

比较有意思的是addEntry方法,它提供了一个可选的操作,我们可以通过继承LinkedHashMap并复写removeEldestEntry方法让该子类可以自动地删除最近最少访问的键值对——这可以用来做缓存!!

LinkedHashMap自定义了迭代器以及迭代规则,LinkedHashMap是通过内部的双向链表来完成迭代的,遍历时间与键值对总数成正比,而HashMap遍历时间与容量成正比,所以通常情况下,LinkedHashMap遍历性能是优于HashMap的,但是因为需要额外维护链表,所以折中来看,两者性能相差无几。

 private abstract class LinkedHashIterator<T> implements Iterator<T> {
        Entry<K,V> nextEntry    = header.after;//指向链表首部
        Entry<K,V> lastReturned = null;
        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;
        }
    }

总结:

1.LinkedHashMap继承自HashMap,具有HashMap的大部分特性,比如支持null键和值,默认容量为16,装载因子为0.75,非线程安全等等;

2.LinkedHashMap通过设置accessOrder控制遍历顺序是按照插入顺序还是按照访问顺序。当accessOrder为true时,可以利用其完成LRU缓存的功能;

3.LinkedHashMap内部维护了一个双向循环链表,并且其迭代操作时通过链表完成的,而不是去遍历hash表。

【源码】LinkedHashMap源码剖析

时间: 2024-08-26 18:47:52

【源码】LinkedHashMap源码剖析的相关文章

【Java集合源码剖析】LinkedHashmap源码剖析

LinkedHashMap简介 LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同. LinkedHashMap可以用来实现LRU算法(这会在下面的源码中进行分析). LinkedHashMap同样是非线程安全的,只在单线程环境下使用. LinkedHashMap源码剖析 LinkedHashM

转:【Java集合源码剖析】LinkedHashmap源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985   前言:有网友建议分析下LinkedHashMap的源码,于是花了一晚上时间研究了下,分享出此文(这个系列的最后一篇博文了),希望大家相互学习.LinkedHashMap的源码理解起来也不难(当然,要建立在对HashMap源码有较好理解的基础上). LinkedHashMap简介 LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加

Spring Boot 揭秘与实战 源码分析 - 工作原理剖析

文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoConfiguration4. 扩展阅读 3.1. 核心注解 3.2. 注入 Bean 结合<Spring Boot 揭秘与实战 源码分析 - 开箱即用,内藏玄机>一文,我们再来深入的理解 Spring Boot 的工作原理. 在<Spring Boot 揭秘与实战 源码分析 - 开箱即用,内藏

LinkedHashMap 源码分析

LinkedHashMap 源码分析 1. 基本结构 1. 实现 实现的接口是 Map 2. 继承 ?? 继承的是 HashMap 这个就比较熟悉了,事实上我们会看到 LinkedHashMap 代码量非常的少,主要就是因为他继承的 HashMap ,继承了大多数的操作. 仔细一点的都会发现 HashMap 里面有非常多的空白方法,这些方法其实是模板方法,为了让继承 HashMap 的类重写一些自己的特性.而不破坏代码结构. 3. 数据域 1. 基本字段 ?? 在 HashMap 的基础上他添加

LRU、FIFO缓存实现以及LinkedHashMap源码

本篇将描述如何使用LinkedHashMap实现LRU以及FIFO缓存,并将从LinkedHashMap源码层面描述是如何实现这两种缓存的. 1.缓存描述 首先介绍一下FIFO.LRU两种缓存: FIFO(First In First out):先见先出,淘汰最先近来的页面,新进来的页面最迟被淘汰,完全符合队列. LRU(Least recently used):最近最少使用,淘汰最近不使用的页面. 2.代码实现 以下是通过LinkedHashMap实现两种缓存. public class Ca

LRU算法实现,HashMap与LinkedHashMap源码的部分总结

关于HashMap与LinkedHashMap源码的一些总结 JDK1.8之后的HashMap底层结构中,在数组(Node<K,V> table)长度大于64的时候且链表(依然是Node)长度大于8的时候,链表在转换为红黑树时,链表长度小于等于6时将不会进行转化为红黑树.目的是为了保证效率.其中链表的结点只有next,LinkedHashMap是在Entry<K,V>中添加before, after(双向链表的定义),保证可迭代,遍历时为存入顺序. 下面是LinkedHashMap

【MySQL源码】源码安装和启动mysql

--[MySQL源码]源码安装和启动mysql --------------------------------------2014/08/19 本机环境:ubuntu12.04,fedora-17 MYSQL版本:5.5.28 CMAKE版本:2.8.9 一.下载最新版本的cmake,解压后编译安装. sudo ./configure --prefix=/usr/local/etc/cmake-2.8.9 sudo make sudo make installsudo ln -s /usr/l

MINA2 源码学习--源码结构梳理

一.mina的整体框架结构及案例: 1.整体结构图: 简述:以上是一张来自网上比较经典的图,整体上揭示了mina的结构,其中IoService包含客户端IoConnector和服务端IoAcceptor两部分.即无论是客户端还是服务端都是这个结构.IoService封装了网络传输层(TCP和UDP),而IoFilterChain中mina自带的filter做了一些基本的操作之外,支持扩展.经过FilterChain之后最终调用IoHandler,IoHandler是具体实现业务逻辑的处理接口,具

Android二维码扫描源码

Android二维码扫描源码 支持平台:Android   运行环境:Eclipse   开发语言:Java 下载地址:http://t.cn/R7HfKOY 源码简介 源码运行截图