JDK8源代码分析Map之HashMap、HashTable等数据结构底层实现

先总览下Map继承体系:

HashMap

1、继承体系

HashMap继承了AbstractMap并实现了Map,Cloneable,Seriliazable接口

 2、重要参数

初始/最大的数组大小 (initial_capacity)

初始 2^4 = 16   

最大 2^30,10亿多

初始负载因子(load_factor

初始:0.75

树化链表及数组的阈值 treeify

1、链表长度至少为8

2、数组容量至少为64(链表阈值的四倍64

树退化为链表(untreeify_threshold)的阈值

红黑树长度至多为(<=) 6时

哈希表

HashMap 的实际大小,可能不准(因为当你拿到这个值的时候,可能又发生了变化)

扩容的门槛threshold,有两种情况

1、如果初始化时,给定数组大小的话,通过 tableSizeFor 方法计算,数组大小永远接近于 2 的幂次方,比如你给定初始化大小 19,实际上初始化大小为 32,为 2 的 5 次方。

2、如果是通过 resize 方法进行扩容,threshold= 数组容量 * 0.75

负载因子

threshold = capacity * loadFactory

当前哈希表结构修改次数

3、链表Node结点数据结构

4、红黑树的结点

5、HashMap底层存储结构图示

 

HashMap重点梳理~

重点1:put数据原理分析

如 :put("x","y")

1、获取“x”字符串的Hash值,经过Hash值的扰动函数,使Hash值更散列;

2、构造出Node对象;

3、由路由算法,找出node应存放在数组的位置;

 

PS:路由址公式:(table.length-1) & node.hash 求得存放的数组下标;

table.length必然是2的倍数,table.length-1相当于一个低位掩码)的二进制形式必定是类似:00…000 1111 的形式进行与运算,前边都是0高位不参与运算;

Hash()方法即扰动函数,作用是:让hash值的高16位也参与路由运算

扰动函数https://www.javacodegeeks.com/2015/09/an-introduction-to-optimising-a-hashing-strategy.html

2进制32位带符号的int表值范围从-21474836482147483647只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的;

但是前后40多亿的映射空间,对于初始值数组长度为16的HashMap来说,是绝对放不下的,因此,提供一个“低位掩码”(table.length-1) 与上 hash,即路由算法

得到的低位值用来作数组下标;这时,新的问题产生,即便散列值分布的再松散,只去后几位,碰撞也会很严重,扰动函数:

混合原始哈希码的高位和低位,以此来加大低位的随机性尽量做到任何一位的变化都能对最终得到的结果产生影响

352个字符串,在他们散列值完全没有冲突的前提下,对它们做低位掩码,取数组下标

 

重点2:什么是hash碰撞?

hash碰撞不可避免,因为不同的hash值转化成二进制(如1122的二进制 000..0 0100 0110 0010 3170的二进制 000..0 1100 0110 0010),

由于后四位相同,但高位不进行运算,进行寻址操作时得到的下标地址相同,这就造成冲突;

Hash碰撞带来的问题,理想情况下查找的时间复杂度为O(1),但是如果链化的很严重,相当于退化到了O(n)

 

重点3:什么是链化?为什么引入红黑树?

由于Hash碰撞的问题引入链化,为了解决链化严重的问题引入红黑树;

 

重点4:HashMap扩容门槛及原理

扩容的门槛threshold,有两种情况

1、如果初始化时,给定数组大小的话,通过 tableSizeFor 方法计算,数组大小永远接近于 2 的幂次方,比如你给定初始化大小 19,实际上初始化大小为 32,为 2 的 5 次方。

2、如果是putVal()触发,通过 resize 方法进行扩容,threshold= 数组容量 * 0.75

new HashMap() 实例化时

tableSizeFor()

putVal时触发扩容

Resize()

扩容,解决hash冲突导致链化严重的问题!

0、判断是否为空

1、判断是否只有一个元素,直接插入

2、判断是否是树化

3、判断是否形成链表

 

重点5:HashMap  Jdk1.7 于 1.8对比!

头插法 尾插法

JDK1.7

在高并发下可能会出现 扩容死锁及数据丢失的问题!

在扩容时,扩容是创建一个新的数组,然后旧数组数据进行转移!问题发生在resize()时->transfer()方法,多个线程同事对一个数组扩容,导致链表发生环链,导致死循环,cpu占有率可能飙升至100%!

JDK1.8

改进措施是在resize()方法的一个分支里,使用了四个指针,两个高位、两个低位指针,通过hashcdoe & 旧数组的长度 得到的值只有两种情况1等于0,此时放入低位指针 2等于旧数组长度,放入高位指针

 

 

Hashtable (注意t小写哦)

打开源码发现除了构造外几乎所有方法都加了synchronized关键字:

注意到类开头的注释:

Hashtable已经不被推荐,如果不要求线程安全请使用HashMap;如果有线程安全需求,有效率更高的ConcurrentHashMap!

总结:本质上是线程安全的HashMap,跟Vector一样,都是用Synchronized修饰方法!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值