加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

HashMap

发布时间:2020-12-14 06:36:41 所属栏目:Java 来源:网络整理
导读:这里是修真院后端小课堂,每篇分享文从 【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】 八个方面深度解析后端知识/技能,本篇分享的是: 【HashMap 】 (1)背景介绍: 不讲HashMap的使用方法,看一看

这里是修真院后端小课堂,每篇分享文从

【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】

八个方面深度解析后端知识/技能,本篇分享的是:

【HashMap 】

(1)背景介绍:

不讲HashMap的使用方法,看一看底层的源码是什么?

思考:HashMap使用key,·value进行存储,使用的数据结构是什么?

我们知道数组和链表两种数据结构

数组:

优点:查询速度快

缺点:增加和删除慢

链表:

优点:增加和删除快

缺点:查询速度慢

那我们可不可以将两者的优点结合一下,达到查询、增加、删除效率都非常快呢?

所以我们猜测一下,HashMap能否源码底层数据结构是采用的链表+数组的形式呢?

(2)知识剖析:

1.HashMap数据底层具体存储的是什么?

Java是一门面向对象开发的语言,可以把所有东西可以看做是对象。

通过查看源码可知:map里面的key和value都保存在了这个Node对象里面。

class Node{

? ? private K key;? ?//用来定位数组索引位置

? ? private V value;

? ? private Node next;? //链表的下一个node

}

2.数组怎么表示?

transient Node< K,V>[] table;

每个列表被称为桶,即哈希桶数组,是一个Node的数组。

HashMap使用哈希表进行存储。怎样得到表中对象的索引位置?

key.hashCode( )----->hashCode----->Hash算法的高位运算和取模运算----->得到存储位置

有时两个key会定位到相同的位置,就发生了Hash碰撞。Hash算法计算结果越分散均匀,Hash碰撞的概率就越小,map的存取效率就会越高。

如果哈希桶数组很大,即使较差的Hash算法也会比较分散,如果哈希桶数组数组很小,即使好的Hash算法也会出现较多碰撞。

3.数组要不要有一个大小呢?

学过数组都知道,要给数组一个初始化大小,那么HashMap有没有大小呢?

Defalutsize = ...? 初始大小???

Maxmumsize = ...? ?上限大小???

肯定是要有的。

4.数组初始大小是多少呢?

从源码可以看到

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

初始大小是16

如果初始大小不够用,或者达到某个值,是不是要进行数组大小的扩容?

final Node[] resize()

5.扩容是不是要有一个依据?

int threshold = 16; Node[] table的初始化长度

transient int size; HashMap中实际存在的键值对数量

size > threshold * 小于1的小数?

实际使用的size肯定不可能达到16才进行扩容,那样会损失性能,一定要达到一个点就进行扩容,这个点是0.25 0.5 0.75哪一个呢?

源码中定义了一个扩容因子

static final float DEFAULT_LOAD_FACTOR = 0.75f

size = threshold * Load factor ----->16 * 0.75 =12

所以在12的时候就会进行扩容。

为什么要选择0.75?

默认的负载因子0.75是对空间和时间效率的一个平衡选择,建议大家不要修改,除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。

6.链表的是不是需要一个长度限制呢?

负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响HashMap的性能。链表达到一个限制值后,是否需要进行一个改变?

在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。

当链表长度太长时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能

源码中定义,当链表超过8时,就转化为红黑树

static final int TREEIFY_THRESHOLD = 8;

7.Node节点到底存在哪里?

存在哪里要有一个位置,这个位置就是通过计算得来的。

利用这个key进行一个计算,计算出这个Node应该存在哪里。

想知道key,value存放在HashMap结构中的位置,使用hash函数,源码的实现(方法一+方法二):

方法一:

static final int hash(Object key) {? ?//jdk1.8 & jdk1.7

? ? ?int h;

? ? ?// h = key.hashCode() 为第一步 取hashCode值

? ? ?// h ^ (h >>> 16)? 为第二步 高位参与运算

? ? ?return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

}

方法二:

static int indexFor(int h,int length) {? //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的

? ? ?return h & (leng

(3)常见问题:

HashMap线程安全吗?

(4)解决方案:

HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全在多线程使用场景中,应该尽量避免使用线程不安全的HashMap,而使用线程安全ConcurrentHashMap。

(5)编码实战:

比如我的一个key的哈希值

h=hashCode():

22222222222222222222000011101010

h>>>16:

00000000000000002222222222222221

hash=h^(h>>>16):

22222222222222210000111100010101

(n-1)&hash:

22222222222222210000111100010101

最后取后四位0101转换成十进制就是5

(6)拓展思考:

JDK1.8与JDK1.7的性能对比?

JDK1.8引入红黑树大程度优化了HashMap的性能。

(7)参考文献:

Java 8系列之重新认识HashMap——美团技术团队

(8)更多讨论:

Q1.Map集合还有哪些实现类?

A1:LinkedHashMap、Hashtable和treeMap

Q2.HashMap为什么要保证hashmap的容量为2的幂次方?

A2:HashMap容量为2的幂次方,是为了减小哈希冲突,因为如果存在哈希冲突的时候,存储entry的时候,会出现一个位置上存在多个entry的情况,会造成读写速度的降低;使用2的幂次方的容量,存储的时候,会根据key的哈希值跟hashmap的(容量-1)进行位运算,这样的运算,当(容量-1)的二进制数全为1的时候,存储位置的计算结果是哈希冲突最小的。

Q3.HashMap优缺点?

A3:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

(9)鸣谢:

(10)结束语:

今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~

PPT链接?视频链接

更多内容,可以加入IT交流群565734203与大家一起讨论交流

这里是技能树·IT修真院:,初学者转行到互联网的聚集地

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读