09.Java 集合 - LinkedHashMap

基本概念

LinkedHashMap 是 key 键有序的HashMap的一种实现。它除了使用哈希表这个数据结构,使用双向链表来保证key的顺序。

LinkedHashMap 提供了两种 key 的顺序:

  • 访问顺序(access order):可以使用这种顺序实现LRU(Least Recently Used)缓存。
  • 插入顺序(insertion orde):同一 key 的多次插入,并不会影响其顺序。

内部构造

1.继承关系

LinkedHashMap 继承自 HashMap,说明它的内部同样采用哈希表的结构来存储元素。如下图所示:

这里写图片描述


2.Entry

即节点,它是 LinkedHashMap 的内部类,继承自 HashMap.Entry。

在创建该节点时通过调用 HashMap.Entry 的构造函数实现,说明它们的结构一样,是组成单向链表的节点。

private static class Entry<K, V> extends HashMap.Entry<K, V>{

    // 构造函数
    Entry(int hash, K key, V value, HashMap.Entry<K, V> next) {
        super(hash, key, value, next);
    }

    // 省略部分代码...
}

3.单向链表

上面提到 LinkedHashMap.Entry 可以表示单向链表的节点。那么它是如何实现节点的添加、删除?

// 添加节点
Entry e1 = ...
Entry e2 = new Entry(hash,key,value,e2);

// 移除节点
e2.next = null;

观察代码可知,它通过设置成员变量 next 的值来实现单向链表的基本操作。


4.双向循环链表

LinkedHashMap.Entry 既可以表示单向链表的节点,也可以表示双向循环链表的节点。

它通过两个成员变量: before、after,表示双向循环链表的前后指针。

通过 remove、addBefore 实现双链表节点的加入/移除。

private static class Entry<K, V> extends HashMap.Entry<K, V>{

    // 表示前、后指针
    Entry<K, V> before, after;

    //移除操作
    private void remove() {
        before.after = after; // ①
        after.before = before; //②
    }

    //添加操作
    private void addBefore(Entry<K, V> existingEntry) {
        // ①~④
        after = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }

    // 省略部分代码...
}

操作过程如下图:

  • 删除操作(对应方法注释①②)

这里写图片描述

  • 添加操作(对应方法注释①~④)

这里写图片描述


5.构造函数

LinkedHashMap 在所有的构建过程中都调用了 HashMap 的构造函数来实现。

HashMap 的构造方法都会调用 init 方法执行执行初始化,该方法在这里被重写。

因此 LinkedHashMap 的创建过程为:

LinkedHashMap.construct -> HashMap.construct -> LinkedHashMap.init

下面来看它的源码:

// 按访问顺序排序,否则按照插入顺序排序
 private final boolean accessOrder;

public LinkedHashMap() {
    super();
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

// 关键
void init() {
    // 创建循环双链表
    header = new Entry<>(-1, null, null, null);
    header.before = header.after = header;
}

观察代码,LinkedHashMap 与 HashMap 一样,内部都采用的了哈希表的结构,不同的是:

  • LinkedHashMap 在构建时还会额外的创建一个双循环链表

  • LinkedHashMap 构造函数的入参多了一个参数:accessOrder。该参数表示在访问 LikedHashMap 集合时是否按照访问顺序获取键值对,具体的作用下面会探究到。


操作方法

1.添加操作

在 LinkedHashMap 类中,put 方法表示添加操作。该方法继承自它的父类 HashMap。

put 方法的实现过程是:

  • 先遍历哈希表是否存在匹配的 key,如果有则进行修改操作
  • 若没有通过进行添加操作

在 LinkedHashMap 中类对 addEntry 、createEntry 进行了重写。其调用过程为:

super.put-> addEntry ->super.addEntry -> createEntry

代码如下:

// 1.HashMap.put
public V put(K key, V value) {
    // 省略部分代码...

    // 不存在相同节点,执行添加操作;该方法在 LinkedHashMap 被重写
    addEntry(hash, key, value, i);

    return null;
}

// 2.LinkedHashMap.addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
    super.addEntry(hash, key, value, bucketIndex);

    Entry<K, V> eldest = header.after;

    // 默认返回 false
    if (removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
    }
}

// 3.HashMap.addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
    // 省略代码:判断扩容操作,是的话,重新计算哈希表的位置...

    // 创建新节点,并插入链表
    createEntry(hash, key, value, bucketIndex);
}

// 4.LinkedHashMap.createEntry
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++;
}

2.修改操作

在 LinkedHashMap 类中,put 方法同样表示修改操作。上面提到该方法继承自它的父类 HashMap。

// 1.HashMap.put
public V put(K key, V value) {
    // 省略部分代码...

    // 遍历该位置上的链表
    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;

            // 关键,在 HashMap 为空方法,这里对它进行了重写。
            e.recordAccess(this);
            return oldValue;
        }
    }

    addEntry(hash, key, value, i);
    return null;
}

不同于 HashMap 的 Entry,在 LinkedHashMap 中节点对 recordAccess 进行方法进行了重写。

// LinkedHashMap.Entry.recordAccess
void recordAccess(HashMap<K, V> m) {
    LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;
    if (lm.accessOrder) {
        lm.modCount++;
        // 移除当前节点
        remove();
        // 添加到双链表的头节点之前,即链表末尾
        addBefore(lm.header);
    }
}

通过 LinkedHashMap 将操作过的节点转移到双链表的末尾位置,以此实现 LRU。


3.删除操作

在 LinkedHashMap 类中,remove 方法同样表示删除操作。该方法继承自它的父类 HashMap,这里不再分析。


4.查询操作

在 LinkedHashMap 类中,get 方法表示操作查询。

与修改操作一样,不同的是其内部类 Entry 定义的 recordAccess 方法 。

public V get(Object key) {

    // HashMap.getEntry
    Entry<K, V> e = (Entry<K, V>) getEntry(key);

    if (e == null){
        return null;
    }

    // 关键
    e.recordAccess(this);
    return e.value;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oxf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值