ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

HashMap 有什么特点

2022-05-20 16:04:26  阅读:166  来源: 互联网

标签:链表 hash HashMap 什么 元素 特点 key null 节点


JDK8 之前底层实现是数组 + 链表,JDK8 改为数组 + 链表/红黑树,节点类型从Entry 变更为 Node。
主要成员变量包括存储数据的 table 数组、元素数量 size、加载因子 loadFactor。

  • table 数组记录 HashMap 的数据,每个下标对应一条链表,所有哈希冲突的数据都会被存放到同一条链表,
    Node/Entry 节点包含四个成员变量:key、value、next 指针和 hash 值。

  • HashMap 中数据以键值对的形式存在,键对应的 hash 值用来计算数组下标,如果两个元素 key 的 hash 值一样,就会发生哈希冲突,被放到同一个链表上,为使查询效率尽可能高,键的 hash 值要尽可能分散。

  • HashMap 默认初始化容量为 16,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。

相关方法的源码
JDK8 之前
  • hash:计算元素 key 的散列值

    1. 处理 String 类型时,调用 stringHash32 方法获取 hash 值。
    2. 处理其他类型数据时,提供一个相对于 HashMap 实例唯一不变的随机值 hashSeed 作为计算初始量。
    3. 执行异或和无符号右移使 hash 值更加离散,减小哈希冲突概率。
  • indexFor:计算元素下标

    1. 将 hash 值和数组长度-1 进行与操作,保证结果不会超过 table 数组范围。
  • get:获取元素的 value 值

    1. 如果 key 为 null,调用 getForNullKey 方法,如果 size 为 0 表示链表为空,返回 null。
      如果 size 不为 0 说明存在链表,遍历 table[0] 链表,如果找到了 key 为 null 的节点则返回其 value,否则返回 null。
    2. 如果 key 为 不为 null,调用 getEntry 方法,如果 size 为 0 表示链表为空,返回 null 值。如果 size 不为 0,首先计算 key 的 hash 值,然后遍历该链表的所有节点,如果节点的 key 和 hash 值都和要查找的元素相同则返回其 Entry 节点。
    3. 如果找到了对应的 Entry 节点,调用 getValue 方法获取其 value 并返回,否则返回 null。
  • put:添加元素

    1. 如果 key 为 null,直接存入 table[0]。
    2. 如果 key 不为 null,计算 key 的 hash 值。
    3. 调用 indexFor 计算元素存放的下标 i。
    4. 遍历 table[i] 对应的链表,如果 key 已存在,就更新 value 然后返回旧 value。
    5. 如果 key 不存在,将 modCount 值加 1,使用 addEntry 方法增加一个节点并返回 null。
  • resize:扩容数组

    1. 如果当前容量达到了最大容量,将阈值设置为 Integer 最大值,之后扩容不再触发。
    2. 否则计算新的容量,将阈值设为 newCapacity x loadFactor 和 最大容量 + 1 的较小值。
    3. 创建一个容量为 newCapacity 的 Entry 数组,调用 transfer 方法将旧数组的元素转移到新数组。
  • transfer:转移元素

    1. 遍历旧数组的所有元素,调用 rehash 方法判断是否需要哈希重构,如果需要就重新计算元素 key 的 hash 值。
    2. 调用 indexFor 方法计算元素存放的下标 i,利用头插法将旧数组的元素转移到新数组。
JDK8
  • hash:计算元素 key 的散列值

    1. 如果 key 为 null 返回 0,否则就将 key 的 hashCode 方法返回值高低16位异或,让尽可能多的位参与运算,让结果的 0 和 1 分布更加均匀,降低哈希冲突概率。
  • put:添加元素

    1. 调用 putVal 方法添加元素。
    2. 如果 table 为空或长度为 0 就进行扩容,否则计算元素下标位置,不存在就调用 newNode 创建一个节点。
    3. 如果存在且是链表,如果首节点和待插入元素的 hash 和 key 都一样,更新节点的 value。
    4. 如果首节点是 TreeNode 类型,调用 putTreeVal 方法增加一个树节点,每一次都比较插入节点和当前节点的大小,待插入节点小就往左子树查找,否则往右子树查找,找到空位后执行两个方法:balanceInsert 方法,插入节点并调整平衡、moveRootToFront 方法,由于调整平衡后根节点可能变化,需要重置根节点。
    5. 如果都不满足,遍历链表,根据 hash 和 key 判断是否重复,决定更新 value 还是新增节点。如果遍历到了链表末尾则添加节点,如果达到建树阈值 7,还需要调用 treeifyBin 把链表重构为红黑树。
    6. 存放元素后将 modCount 加 1,如果 ++size > threshold ,调用 resize 扩容。
  • get :获取元素的 value 值

    1. 调用 getNode 方法获取 Node 节点,如果不是 null 就返回其 value 值,否则返回 null。
    2. getNode 方法中如果数组不为空且存在元素,先比较第一个节点和要查找元素的 hash 和 key ,如果都相同则直接返回。
    3. 如果第二个节点是 TreeNode 类型则调用 getTreeNode 方法进行查找,否则遍历链表根据 hash 和 key 查找,如果没有找到就返回 null。
  • resize:扩容数组

    1. 重新规划长度和阈值,如果长度发生了变化,部分数据节点也要重新排列。
  • 重新规划长度

    1. 如果当前容量 oldCap > 0 且达到最大容量,将阈值设为 Integer 最大值,return 终止扩容。
    2. 如果未达到最大容量,当 oldCap << 1 不超过最大容量就扩大为 2 倍。
    3. 如果都不满足且当前扩容阈值 oldThr > 0,使用当前扩容阈值作为新容量。
    4. 否则将新容量置为默认初始容量 16,新扩容阈值置为 12。
  • 重新排列数据节点

    1. 如果节点为 null 不进行处理。
    2. 如果节点不为 null 且没有next节点,那么通过节点的 hash 值和 新容量-1 进行与运算计算下标存入新的 table 数组。
    3. 如果节点为 TreeNode 类型,调用 split 方法处理,如果节点数 hc 达到6 会调用 untreeify 方法转回链表。
    4. 如果是链表节点,需要将链表拆分为 hash 值超出旧容量的链表和未超出容量的链表。对于hash & oldCap == 0 的部分不需要做处理,否则需要放到新的下标位置上,新下标 = 旧下标 + 旧容量。

标签:链表,hash,HashMap,什么,元素,特点,key,null,节点
来源: https://www.cnblogs.com/javaupup/p/16292547.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有