09.Java 集合 - LinkedHashMap

来源:互联网 发布:魔法王座坐骑数据 编辑:程序博客网 时间:2024/06/06 01:34

基本概念

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.putpublic V put(K key, V value) {    // 省略部分代码...    // 不存在相同节点,执行添加操作;该方法在 LinkedHashMap 被重写    addEntry(hash, key, value, i);    return null;}// 2.LinkedHashMap.addEntryvoid 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.addEntryvoid addEntry(int hash, K key, V value, int bucketIndex) {    // 省略代码:判断扩容操作,是的话,重新计算哈希表的位置...    // 创建新节点,并插入链表    createEntry(hash, key, value, bucketIndex);}// 4.LinkedHashMap.createEntryvoid 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.putpublic 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.recordAccessvoid 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 0
原创粉丝点击