• java中HashMap的实现原理


    HashMap底层实现原理

    JDK1.7及以前的版本中 ,HashMap 底层由 数组+ 链表 实现。

    新建一个 HashMap的时候就会初始化一个数组。数组中的元素我们称为 Entry,每个 Entry其实就是一个 键值对,并且包含一个指向下一个元素的引用,这就构成了链表。当需要存储一个 键值对(Entry对象) 时,会根据hash算法来决定其在数组中的位置。当需要取出一个 Entry 对象时,也会根据 hash算法找到其在数组中的存储位置,再根据 equals 方法从该位置的链表中取出 Entry。

    HashMap 结合了 ArrayList 的查询效率高的特点以及 LinkedList 插入效率高的特点,但是如果我们要存储的数据过于庞大,肯定会造成多次 哈希冲突,这样一来,链表上的节点就会堆积很多,在做查询的时候效率又会变得很低,失去了 HashMap本来的特点

     在JDK1.8 及之后的版本中,HashMap 底层由 数组 + 链表 + 红黑树 实现。当节点数不大于 8 时,还是一个链表结构,只不过插入节点时变成了尾插法,当节点数大于 8 之后,将链表结构转换为红黑树结构,复杂度也从 O(n) 变成了 O(logn).

    HashMap.put

    put方法在hashmap中起插入的作用,即在当前的hashmap上添加新元素。

    主要流程:

    1. 判断table数组是否为空,或者为 null,否则执行 resize() 扩容操作
    2. 使用 key.hashcode() 将 key 转换为 数组下标 i , 如果 table[i] =null,直接新建节点添加即可,转入 6 ;如果 table[i] != null,则转向 3;
    3. 判断 table[i] 的首个元素是否和 key 一样,如果相同(equals判断)则直接覆盖 value,否则转向 4
    4. 判断 table[i] 是否为 红黑二叉树,如果是,则直接插入键值对,否则转向 5
    5. 遍历 table[i],判断链表长度是否大于 8 ,若是 则把链表转换为 红黑树,进行插入操作;否则进行链表插入操作;如果链表中已存在 key 则直接覆盖 value
    6. 插入成功后,判断实际存在的键值对数量是否超过了 threshold ,如果操作则需要扩容。
    1. public v put(K key, V value) {
    2. if (key == null)
    3. return putForNullKey(value);
    4. // 通过 key 计算hash值
    5. int hash = hash(key);
    6. // 通过 key 找到数组中待存放的下标
    7. int i = indexFor(hash, table.length);
    8. // 链表
    9. for (Entry e = table[i]; e != null; e = e.next) {
    10. object k;
    11. // 如果这个数组下标中有元素,开始遍历链表
    12. if (e.hash == hash && ((k = e.key) == key || key .equals(k))) {
    13. // 如果两个 hash 值相同,或者键相同,则修改 value
    14. V oldValue = e.value;
    15. e.value = Value;
    16. e.recordAccess(this);
    17. return oldValue;
    18. }
    19. }
    20. modCount++;
    21. // 可能两个 hash 值不同,但计算出的 index 一样,这就发生了 hash碰撞
    22. // 使用头插法,将其放在链表头部
    23. addEntry (hash, key, value, i)
    24. return null;
    25. }

    HashMap.get

    get方法在hashmap中起取值的作用,即根据 key 获取对应的 value值

    主要流程:

    1. 首先判断数组是否为空,如果为空直接返回null;
    2. 使用 hashcode() 计算 key的索引 i ,如果 table[i] 为空,返回null;否则判断key值是否相同,若相同则返回该key的value
    3. 如果 key 值不相同,则往后遍历;
    4. 如果 table[i] 是红黑树,则调用红黑树的 getTreeNode() 进行查找
    5. 如果 table[i] 是链表,则遍历链表查找
    1. public V get(Object key) {
    2. Node e;
    3. return (e = getNode(hash(key), key)) == null ? null : e.value;
    4. }
    5. final Node getNode(int hash, Object key) {
    6. Node[] tab; Node first, e; int n; K k;
    7. //若哈希表不为空,并且当前索引位置有元素
    8. if ((tab = table) != null && (n = tab.length) > 0 &&
    9. (first = tab[(n - 1) & hash]) != null) {
    10. //若在一个桶中,并且当前索引位置的key值与要查找的key值相等
    11. //说明找到了,返回该值
    12. if (first.hash == hash &&
    13. ((k = first.key) == key || (key != null && key.equals(k))))
    14. return first;
    15. //若key值不相同,且还有元素
    16. if ((e = first.next) != null) {
    17. //如果此时已经树化,则调用红黑树的getTreeNode()查找
    18. if (first instanceof TreeNode)
    19. return ((TreeNode)first).getTreeNode(hash, key);
    20. do {
    21. //若没有树化,则在从前往后遍历链表,直到找到该元素
    22. if (e.hash == hash &&
    23. ((k = e.key) == key || (key != null && key.equals(k))))
    24. return e;
    25. } while ((e = e.next) != null);
    26. }
    27. }
    28. //若找不到则返回null
    29. return null;
    30. }

  • 相关阅读:
    Day28|Leetcode 93. 复原 IP 地址 Leetcode 78. 子集 Leetcode 90. 子集 II
    “金山-讯飞”杯2024年武汉理工大学程序设计竞赛 A. Mobiusp败走***(思维题-点双连通分量、连通性)
    C++:拷贝构造函数的初始化列表
    Golang-GJSON 快速而简单的方法来从 json 文档获取值
    golang学习之路2-基础认识(上)
    Memcached&Redis构建缓存服务器
    ARM开发(一)预备知识——半导体器件,模拟数字部分,计算机组成及原理
    NewStarCTF 2023 web
    【榜单公布】10·24征文活动结果出炉!
    大学生申请软著的好处
  • 原文地址:https://blog.csdn.net/qq_48626761/article/details/133784153