HashMap底层

HashMap底层

主要jdk1.8中

主要数据结构为:数组+链表+红黑树

构造HashMap后,table一开始为空,默认loadfactor=0.75,

size;//键值对个数

hash方法

右移16位再按位异或

DEFAULT_INITIAL_CAPACITY = 1 << 4;
DEFAULT_LOAD_FACTOR = 0.75f;//扩容阈值,初始化默认0.75
TREEIFY_THRESHOLD = 8;
UNTREEIFY_THRESHOLD = 6;
MIN_TREEIFY_CAPACITY = 64;

transient Node<K,V>[] table;//Hashmap中数组槽
transient int size;//键值对元素个数


  • 先扰动
  • 再hashcode

put方法

putval()--resize()

  1. 最开始table数组为空,去resize()-->指定默认数组容量--默认加载因子
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;

2.封装好new Node(hash,key,value,next)再放入到数组中

计算数组下标(n-1)&hash 此下标当前位置没有值,则把这个新节点放进去

若此数组下标处已经有元素

hashmap插入流程

  1. 数组为空,初始化数组,指定容量为16,loadfactor=0.75

  2. 将key --value封装成Node内部类,Node是单向链表

  3. 计算hash,通过hash去计算数组下标

  4. 当前数组下标中没有元素,直接放到该下标处

    4.1当前数组下标中如果有元素,判断该元素与传入元素的hash以及key是否相同,若相同则覆盖

    4.2若该数组下标中为红黑树节点,执行红黑树插入

    4.2若数组下标有元素且4.1,4.2不成立,则for循环遍历链表,判断下个元素,如果是尾节点直接放入,如果下个元素与传入元素的hash以及key是否相同,若相同则覆盖

    4.3判断是否要树化,bigcount++>=7? 树化条件 链表元素个数大于8,且数组长度为64

    ++size是否大于扩容阈值,resize()方法

hash方法

右移16位再异或运算

让key的hashcode高16与低16都参与到数组下标计算过程中去

高位没起到作用,会导致hash冲突变大

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap始终控制数组长度位2的次方数?

tableSizefor()方法控制

就算初始化HashMap传入的参数容量为10,也会初始化后变为10;

为什么要用(n-1)&hash?

其实要计算数组下标最简单是用计算后的扰动hash对数组长度进行取余操作

hash%16=0-15;

  • 计算机底层 进行位运算更快
  • hash%n==(n-1)&hash

get方法

get()方法流程总结:

  • 首先判断桶数组是否为空,若为空直接返回null。
  • 若桶数组不为空,则计算索引,查看当前索引是否有元素,若有元素,查看key值是否相同。若key值相同,则返回该节点。
  • 若key值不相同,则继续查找,先判断是否树化,若已经树化,则调用红黑树的getTreeNode()进行查找。
  • 若没有树化,则从前往后遍历链表,找到了则返回节点,找不到则返回null。
final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
  • 为什么我们不用hashCode()方法计算的出来的hash值作为桶下标呢?
    因为hashCode()计算出来的hash值太大,需要大量的存储空间;而且这样哈希表就跟普通的数组差不多。

  • 为什么在hash()中计算桶下标要进行(h>>>16)?
    这样保留了高16位,使得高低16位都参与异或运算,降低了哈希冲突的概率。

树化的条件:

  • 当前桶中的链表长度>=8,并且哈希表的长度>=64时,会树化,否则只是进行简单的扩容。
  • 加入红黑树是为了提高由于链表过长而造成的查询效率降低,将时间复杂度由O(n)提升为O(logn)。

5.扩容的条件:当桶数组的容量>12时进行扩容。

6.解树化的条件:当红黑树的节点个数<=6时,会将红黑树转为链表。

resize方法

有两种情况会触发扩容

  • ++size>threshold 16*0.75=12
  • 链表长度大于8且数组长度小于64

数组总是两倍扩容,遍历数组的每个下标位置,原数组中会有四种情况

  • 数组中为空 直接跳过

  • 数组中为单个Node 计算新下标,放到新数组中去

  • 数组中为链表

    ​ 分为高低链插入新数组的下标位置

` if ((e.hash & oldCap) == 0)//低位置下标插入 newTab[j] = loHead;

if ((e.hash & oldCap) == 1)//高位置下标插入 newTab[j + oldCap] = hiHead;`

  • 数组中为红黑树节点 split()方法

    考虑何时反树化 重新计算下标,高低节点分开后,一侧小于6个节点,TreeNode转变为Node

posted @   周末的周末  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示