JDK容器学习之LinkedHashMap (一):底层存储结构分析

LinkedHashMap 底层存储结构分析

HashMap 是无序的kv键值对容器,TreeMap 则是根据key进行排序的kv键值对容器,而LinkedHashMap同样也是一个有序的kv键值对容器,区别是其排序方式依据的是进入Map的先后顺序

LinkedHashMap 继承自 HashMap, 直接看其内部方法,并没有覆盖HashMap的增删查询接口,连tables数组也没有重新覆盖,所以数据结构基本没啥变化

1.继承体系

首先看继承体系如下

输入图片说明

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

看到这里,有个地方比较有意思,HashMap 既然已经实现了Map接口,为什么 LinkedHashMap 也要另外实现 Map 接口?

  • 首先不显示添加这个实现是没有问题的,从继承体系来讲集成了 HashMap类,必然是实现了Map接口的
  • 非功能角度出发,这样有什么好处?
    • 继承体系一目了然,从源码上就可以看出这个是Map接口的一个实现
    • 从源码中很清楚就可以得知Map中的方法,LinkedHashMap 可以直接使用,如果没有显示加上,则需要向上查找父类中的提供的方法

从个人角度触发,这应该是一种编程习惯的问题

2. 数据结构

同样从put(k,v)方法出发,通过查看新增一个kv对,数据是如何保存的来确定数据存储结构,因为 LinkedHashMap 并没有覆盖 put() 方法,所以可以确定底层的存储结构一致,那么有序是如何保证的呢?

查看源码,发现新增了两个成员

/**
 * The head (eldest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * The tail (youngest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> tail;


static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

上面两个维护的是一个双向链表的头尾,这个链表根据插入Map的顺序来维护Node节点的,以此保证了顺序

3. 双向链表的维护

既然LinkedHashMapHashMap的基础上维护了一个双向链表,那么这个链表的增删修改的逻辑是怎样的?

LinkedHashMap 扩展了 Entry类,新增了before, after, 分别指向该节点在链表中的前后节点

新创建一个节点

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    // 将新增的节点放在链表的最后面
    linkNodeLast(p);
    return p;
}

依然以put(k,v)作为研究对象,分析链表的关系维护

主要方法: java.util.HashMap#putVal

1. 新增一个Map中已经存在的kv对

当插入已经存在的kv对时,不会创建新的Node节点,而会调用下面的方法

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
            
        if (a != null)
            a.before = b;
        else
            last = b;
            
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

accessOrder true表示链表的顺序根据访问顺序来,false表示根据插入顺序来

默认会设置为false,此时上面的逻辑基本上不走到,即表示插入一个已存在的kv对,不会修改链表的顺序

如果显示设置 accessOrder为true,则会将修改的节点,放在链表的最后

2. 新增一个Map中不存在,且没有hash碰撞

新增一个不存在的kv对,首先是调用上面的方法,创建一个Node节点: LinkedHashMap.Entry<K,V>, 在创建节点的同时,就已经将节点放在了链表的最后, 实现逻辑如下

// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}
3. 新增一个Map中不存在,但出现hash碰撞

同上

小结

  • LinkedHashMap 存储结构和 HashMap 相同,依然是数组+链表+红黑树
  • LinkedHashMap 额外持有一个双向链表,维护插入节点的顺序
  • 最终的数据结构如下图
    • 实际的元素存储与HashMap一致,依然是数组+链表+红黑树的形式
    • 区别在于:
      • 除了维护数组+链表的结构之外,还根据插入Map先后顺序维护了一个双向链表的头尾head,tail
      • Node基本结构,相比较HashMap而言,还增加了 before,after 两个分别指向双向链表中前后节点的属性
      • 即下图中的双向链表中的节点,其实值依然是下面的数组+链表结构中的元素

相关博文

关注更多

扫一扫二维码,关注小灰灰blog

小灰灰blog

转载于:https://my.oschina.net/u/566591/blog/1549904

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LinkedHashMap 是继承自 HashMap 的类,在 HashMap 的基础上通过维护一条双向链表来解决无法保持遍历顺序和插入顺序一致的问题,并提供了对访问顺序的支持。在 JDK 1.8 版本中,LinkedHashMap 使用了和 HashMap 相同的底层数据结构,即拉链式散列结构,并在解决长链表问题上引入了红黑树优化。这样,LinkedHashMap 可以提供高效的增删改查操作,并且在遍历时可以按照插入或访问的顺序进行遍历。 LinkedHashMap底层数据结构和 HashMap 一样,都是使用数组加链表或红黑树的方式来处理冲突。每个数组元素都是一个链表或红黑树的头节点,每个节点包含一个键值对。当插入或查找元素时,根据键的哈希值找到对应的数组下标,然后在链表或红黑树中进行操作。 在 LinkedHashMap 中,除了继承了 HashMap 的方法,还覆写了部分方法来维护双向链表。具体来说,LinkedHashMap 在 put、remove 和 get 等方法中添加了对双向链表的操作,以保证插入和访问的顺序。当插入一个新的元素时,LinkedHashMap 会将该元素插入到链表的末尾;当访问一个已有元素时,LinkedHashMap 会将该元素移动到链表的末尾。通过这种方式,LinkedHashMap 可以保持元素的插入或访问顺序,实现了有序遍历的效果。 总结起来,LinkedHashMap底层原理是在 HashMap 的基础上通过维护一条双向链表来实现插入和访问的顺序,而在 JDK 1.8 中,LinkedHashMap 使用了和 HashMap 相同的底层数据结构,即拉链式散列结构,并引入了红黑树优化。这样,LinkedHashMap 可以提供高效的增删改查操作,并且在遍历时可以按照插入或访问的顺序进行遍历。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [LinkedHashMap(JDK1.8)源码解析](https://blog.csdn.net/qq_41242680/article/details/114637171)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值