HashMap剖析

HashMap剖析

这两天对HashMap做了个回顾再吸收,烦请各路大佬指教。

HashMap的实现原理

1.HashMap基于哈希原理,通过put(key, value)和get(key)方法存储和获取对象;

2.当存储对象时,将键值对传递给put(key value)方法时,它调用键对象key的hashCode()方法来计算hashcode,然后找到bucket位置,来存储值对象value;

3.当获取对象时,也是先计算key的hashCode,找到数组中对应位置的bucket位置,然后通过key的equals()方法找到正确的键值对key-value,然后返回值对象value;

4.HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在链表的下一个节点中。每个链表节点中存储键值对key-value对象,也就是当两个不通的键对象key的hashcode相同时,他们会存储在同一个bucket位置的链表(JDK8链表长度大于8变红黑树)中,取数据可通过键对象key的equals()方法用来找到正确的键值对key-value。

JDK8中HashMap源码之添加数据
详细内容解说可以看这里(有上下两部详解)
查看图例

  1. 计算key的hash值,算出元素在底层数组中的下标位置。
  2. 通过下标位置定位到底层数组里的元素(也有可能是链表也有可能是树)。
  3. 取到元素,判断放入元素的key是否==或equals当前位置的key,成立则替换value值,返回旧值。
  4. 如果是树,循环树中的节点,判断放入元素的key是否或equals节点的key,成立则替换树里的value,并返回旧值,不成立就添加到树里。
  5. 否则就顺着元素的链表结构循环节点,判断放入元素的key是否或equals节点的key,成立则替换链表里value,并返回旧值,找不到就添加到链表的最后。

精简一下,判断放入HashMap中的元素要不要替换当前节点的元素,key满足以下两个条件即可替换:

  1. hash值相等。
  2. ==或equals的结果为true。

JDK8中HashMap源码之获取数据

1.调用key的hashcode方法,根据返回值定位到map里数组对应的下标

2.判断这个数组下标对应的头节点是不是为null,如果是,返回null

3.如果头节点不是null,判断key值的equals方法和查询的key值对比是否为TRUE,如果是则返回这个对象的value值,否则遍历下一个节点

4.如果遍历完map中的所有节点都无法满足上面的判断,则返回null

JDK8之前HashMap底层数据结构

JDK8之前HashMap的实现是数组+链表。它之所以有相当快的查询速度是因为先通过Key计算hashCode来决定一维数组中存储的位置,而增删速度靠的是链表来保证的。

JDK8中HashMap底层数据结构

JDK8中用数组+链表+红黑树的结构来优化,链表长度大于8同时满足HashMap中元素个数大于64则变红黑树,长度小于6变回链表。

什么是Hash表

散列表(也叫哈希表),是根据关键码值(Key Value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫散列表。
hash表里可以存储元素的位置称为桶(bucket)。

什么是Hash冲突

不同的key值产生了相同的Hash地址。

Hash冲突的解决方案

1.开放地址法
探测序列,查找一个空的单元格插入。(线性探测、再平方探测、伪随机探测)

2.链地址法
对于相同的值,使用链表进行连接,使用数组存储每一个链表。(HashMap中使用的方案)

3.公共溢出区法
建立一个特殊存储空间,专门存放冲突的数据。(适用于数据和冲突较少的情况)

4.再散列法
准备若干个函数,如果使用第一个hash函数发生了冲突,就使用第二个hash函数,依次类推。

JDK7中HashMap源码之核心成员变量

1.Entry[] table-这个Entry类型的数组存储了HashMap的真正数据。

2.size-大小,代表HashMap内存储了多少个键值对。

3.capacity-容量,实际上HashMap没有一个成员叫capacity,它是作为table这个数组的大小而隐式存在的。

4.threshold-阈值和loadFactor-装载因子,threshold是通过capacity*loadFactor得到的,当size超过threshold时(刚好相等时不会扩容),HashMap会扩容,再次计算每个元素的哈希位置。

5.entrySet、keySet和values这三个都是一种视图,真正的数据都来自table。

JDK8中HashMap源码之核心成员变量

1.Node[] table-这个Node类型的数组存储了HashMap的真正数据 static class Node<K,V> implements Map.Entry<K,V>。

2.size-大小,代表HashMap内存储了多少个键值对。

3.capacity-容量,实际上HashMap没有一个成员叫capacity,它是作为table这个数组的大小而隐式存在的。

4.threshold-阈值和loadFactor-装载因子,threshold是通过capacity*loadFactor得到的,当size超过threshold时(刚好相等时不会扩容),HashMap会扩容,再次计算每个元素的哈希位置。

5.entrySet、keySet和values这三个都是一种视图,真正的数据都来自table。

6.TREEIFY_THRESHOLD-转换成红黑树的临界值,默认8。

7.UNTREEIFY_THRESHOLD-红黑树转换成链表的临界值,默认6。

8.MIN_TREEIFY_CAPACITY-最小树形化阈值,默认64

JDK8中HashMap为什么到8转为红黑树 到6转为链表

TreeNode(红黑树中)占用空间是普通Node(链表中)的两倍,为了时间和空间的权衡。

节点的分布频率会遵循泊松分布,链表长度达到8个元素的概率为0.00000006。

如果是7,则当极端情况下(频繁的插入和删除的都是同一个哈希桶)会导致频繁的树化和非树化的变换。

JDK7与JDK8中HashMap的区别

1.底层结构

JDK7是数组+链表,存储节点是Entry
JDK8则是数组+链表+红黑树,存储节点是Node
JDK8当链表长度大于8且同事HashMap中的元素个数大于64则转换为红黑树。

2.当哈希表为空时

JDK7会先调用inflateTable()初始化一个数组
JDK8是直接调用resize()扩容

3.Hash冲突时 插入数据

JDK7采用头插
JDK8会将节点插入到链表尾部

4.hash函数

JDK7中的hash函数对哈希值的计算直接使用key的hashCode值,扰动复杂
JDK8采用key的hashCode异或上key的hashCode进行无符号右移16位的结果,结果更散列,而且扰动简单

5.扩容策略

JDK7会重新计算hash
JDK8只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话变成“原索引+oldCap”

文章总结实属不易,如有转载,烦请标明出处~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值