LinkedHashMap 源码分析

本文深入剖析了LinkedHashMap的内部实现机制,包括其如何通过双向链表维护元素的插入或访问顺序,以及构造方法、数据存取、删除操作的具体流程。同时,文章还探讨了LinkedHashMap与HashMap的区别,以及在JDK8中引入红黑树后的性能优化。

前言

前面对 HashMap 的源码做了分析,我们知道 HashMap 内部的数据结构是数组+单链表/红黑树实现的,这种数据结构是不能保证数据插入的有序性的,因为会对传入的 key 做 hash 运算,然后再做取模运算,通过链表指向的方法去存储数据,这样就导致了遍历数据的时候无法根据我们存入的顺序来读取。

而 LinkedHashMap 重新实现了内部链表节点,为每个节点增加了前置节点和后置节点,从而实现一个双向链表解决了 HashMap 无序的问题,并且可以在创建 LinkedHashMap 的时候设置 accessOrder 这个 final 类型的常量来控制访问内部元素的时候是根据插入顺序(值为false)还是根据访问顺序(值为 true)的,如果是 true 就会在每次调用 get() 方法的时候移动数据,最近访问的元素下次优先访问。

LinkedHashMap 的介绍

先看下介绍:

可以看到 LinkedHashMap 继承于 HashMap 的,他继承了 HashMap 的方法和特性,比如内部的默认初始容量、默认负载因子、扩容机制等。但是 LinkedHashMap 在此基础上又进行了扩展,主要提供了元素的访问顺序上进行了加强。

官方的描述:

Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map. (A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)

使用哈希表和链表实现了 Map 接口,具备有序的迭代特性,这种实现和 HashMap 不同的是内部使用了一个贯穿所有条目的双向链表,这个链表定义了迭代排序,通常是按照插入 Map 时的顺序,如果在调用 put 方法插入值的时候,如果 Map 中存在值,则覆盖原来的值,如果不存在则执行插入。

这里列下 LinkedHashMap 的特性:

  1. 允许key 为 null ,value 为 null
  2. key 不可以重复,value 可以重复
  3. 内部是以数组+双向链表实现的,JDK8 以后加入了红黑树
  4. 内部键值对的存储是有序的(需要注意初始化的时候 accessOrder 属性的设置)。
  5. 初始容量为 16,负载因子为 0.75,也就是当容量达到 容量*负载因子 的时候会扩容,一次扩容增加一倍。
  6. 内部的键值对要重写 key 对象重写 hashCode 方法、equals 方法。
  7. 线程不安全的,如果想要线程安全,使用:
    1. SynchronizedMap 使用 Map<String, String> map1 = Collections.synchronizedMap(new LinkedHashMap<String, String>(16,0.75f,true)); 来获得一个线程安全的 LinkedHashMap,SynchronizedMap内部也是使用的 Synchronized 实现线程安全的

LinkedHashMap 分析

前面讲了 LinkedHashMap 是几继承于 HashMap 的,实现了双向链表,并且通过accessOrder 可以控制访问顺序,来看下是怎么实现的:

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
{
   
   
    // 双向链表的头结点
    transient LinkedHashMapEntry<K,V> head;
    //双向链表尾结点
    transient LinkedHashMapEntry<K,V> tail;
    // 控制访问顺序,如果为 true 顺序为访问顺序,如果为 false,为插入顺序
    final boolean accessOrder;
}

这里在 HashMap 基础上多扩展了三个字段,分别双向链表的头结点 head,尾节点 tail,和控制访问顺序的 accessOrder ,先看下头节点和尾节点的类 LinkedHashMapEntry:

/**
 * 继承于 HashMap Node 节点的 LinkedHashMap 的条目
 * HashMap.Node subclass for normal LinkedHashMap entries.
 */
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
   
   
    // 定义了当前节点的前置节点 before 和后置节点 after
    LinkedHashMapEntry<K,V> before, after;
    LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
   
   
        super(hash, key, value, next);
    }
}

可以看到 LinkedHashMapEntry 在 HashMap 的 Node 节点上多增加了 两个属性:

  • 节点前置节点 指向上一个节点
  • 节点后置节点 指向下个节点

这里可能会有疑问,HashMap.Node 节点已经有了 next 属性,这里为什么还要定义 after 属性?

这是因为 next 属性里面保存的是一个哈希桶位置的当前链表的当前节点的下个节点应用,是在一个链表内的,我们可以通过哈希桶和单链表的 next 快速的找出要查找的元素。

而 after 也是执行下个节点的,不同的是,这里的 after 可以通过不同的哈希桶位置将不同的节点关联起来。

这个图理解下:

黑色 before 指向
蓝色 after 指向
红色 next 指向

before 和 after 将每个节点全部串联了起来,是保证 LinkedHashMap 顺序的关键。

构造方法

    // 定义初始化容量和负载因子,默认按照插入顺序排序
    public LinkedHashMap(int initialCapacity, float loadFactor) {
   
   
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    //定义初始化容量、使用默认负载因子,默认按照插入顺序排序
    public LinkedHashMap(int initialCapacity) {
   
   
        super(initialCapacity);
        accessOrder = false;
    }

    //使用默认初始化容量、使用默认负载因子,默认按照插入顺序排序
    public LinkedHashMap() {
   
   
        super();
        accessOrder = false;
    }

    //传入一个 Map 初始化,默认按照插入顺序排序
    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;
    }

可以看到,每个构造方法都初始化了 accessOrder ,这个 accessOrder 是final 类型的,一经赋值不能修改,并且是个关键属性,所以只能在构造函数才能保证一定能初始化。

存入数据

LinkedHashMap 里面没有重写 put 方法,继续使用的 HashMap 的 put 方法。

那 LinkedHashMap 是怎么在插入新数据的时候保证插入的顺序的呢?

我们前面讲了,LinkedHashMap 内部的元素多了两个属性来保证顺序访问的,那么肯定是在新建节点的时候对不同节点间进行了关联。在 HashMap 的 putval 方法里面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值