野蛮生长-今天讲讲Map

转自:微信公众号-Java3y 2021-03-02

讲讲Map,JDK1.8的就好

Map在Java里是一个接口,常见的实现类包括HashMap,LinkedHashMap,TreeMap
和ConcurrentHashMap。

在Java里边,哈希表的实现由数组+链表组成,
HashMap底层数据结构是数组+链表/红黑树;
LinkedHashMap底层数据结构是数组+链表+双向链表;
TreeMap底层数据结构是红黑树;
而ConcurrentHashMap底层数据结构是数组+链表/红黑树。

嗯…我们从HashMap开始讲起吧,你能讲讲当new一个HashMap的时候,会发生什么吗?

HashMap有几个构造方法,但最主要的就是指定初始值的和负载因子的的大小。
如果我们不指定,默认的HashMap的大小为16,负载因子大小为0.75。

还有就是:HashMap的大小只能是2次幂的,假设你传一个10进去,实际上HashMap的大小
是16,你传一个7进去,HashMap的大小是8,具体的实现在tableSizeFor可以看到。

我们把元素放进HashMap的时候,需要算出这个元素所在的位置(hash),在HashMap里
用的是位运算来代替取模,能够更加高速地算出该元素所在的位置。

为什么HashMap的大小只能是2次幂,因为只有大小为2次幂时,才能合理的运用位运算
代替取模,而负载因子的大小决定着哈希表的扩容和哈希冲突。

比如现在我默认的HashMap大小为16,负载因子为0.75,这意味着数组最多只能放12个
元素,一旦超出12个元素就说明需要扩容。

怎么算出是12呢,简单说就是16*0.75,每次put进元素的时候,都会检查HashMap的大小
有没有超过这个阈值,如果有,则需要扩容。

鉴于上面的说法,HashMap的大小只能是2次幂,所以扩容默认是原来的2倍。还有就是
扩容这个操作肯定是耗时的,那能不能把负载因子调的高一点,比如调为1,
那我的HashMap就等到16个元素的时候才开始扩容呢。

当然是可以的,但是不推荐,负载因子调高了,这意味着哈希冲突的概率会增高,
哈希冲突的概率增高,同样会耗时,因为查找的速度变慢了。

我继续问下,put元素的时候,传递的key是怎么计算哈希值的?

实现就在hash方法上,可以发现的是,他是先算出正常的哈希值,
然后与高16位做异或运算,产生最终的哈希值。
这样的好处可以增加了随机性,减少了碰撞冲突的可能性。

你再简单说下put方法和get方法的实现吧

put的时候首先对key做hash运算,计算出该key所在的index,
如果没碰撞,直接放到数组中,
如果碰撞了,要判断当前的数据结构是链表还是红黑树,根据不同的情况来进行插入。

假设key是相同的,则替换原来的值,最后判断哈希表是否满了
(当前哈希表大小*负载因子),如果满了,则扩容。

在get的时候,还是对key做hash运算,计算出该key所在的index,
然后判断是否有hash冲突,假设没有hash冲突直接返回,
假设有冲突则判断当前数据结构是链表还是红黑树,分别从不同的数据结构中取出。

那在HashMap中是怎么判断一个元素是否相同的呢

这么简单都问。首先比较hash值,如果hash值相同说明该元素哈希冲突了,
随后用==运算符和equals()判断该元素是否相同,
如果都相同说明是同一个元素。

你说HashMap的数据结构是数组+链表/红黑树,那什么情况才会用到红黑树呢

当数组的大小大于64且链表大小大于8的时候才会将链表改为红黑树,
当红黑树大小为6时,会退化为链表。

这里红黑树转为链表的操作主要是由于查询和插入时对性能的考量。

链表查询时间复杂度为O(n),插入时间复杂度O(1),
红黑树的查询和插入时间复杂度O(logN)。

你在日常工作中使用LinkedHashMap用的多吗

不多,在前边提到了,LinkedHashMap底层结构是数组+链表+双向链表,
实际上它继承了HashMap,在HashMap的基础上维护了一个双向链表。

有了这个双向链表,我们的插入可以说是有序的,
这里的有序不是指大小有序,而是插入有序。

LinkedHashMap在遍历的时候实际上使用的是双向链表来遍历的,
所以LinkedHashMap的大小不会影响到遍历的性能。

那TreeMap呢

TreeMap在实际开发中使用的也不多,TreeMap的底层结构是红黑树,
TreeMap的key不能为null,如果为null,那还怎么排序呢,
TreeMap的有序是通过Comparator来进行比较的,
如果comparator为null,那就使用自然顺序。

再来讲讲线程安全的Map吧,HashMap是线程安全的吗

HashMap不是线程安全的,在多线程环境下,HashMap有可能
有数据丢失和获取不了最新数据的问题,
比如说:线程A put进去了,现场B get不出来,我们想要线程安全,
一般使用ConcurrentHashMap。

ConcurrentHashMap是线程安全的Map实现类,它在juc包下的。

线程安全的Map实现类除了ConcurrentHashMap还有一个HashTable。
当然了,也可以使用Collections来包装出一个线程安全的Map。
但无论是HashTable还是Collections包装的都比较低效,
因为他们是直接在外层套synchronize,
所以我们一般有线程安全问题都考虑ConcurrentHashMap。

ConcurrentHashMap的底层结构是数组+链表/红黑树,
他能支持高并发的访问和更新,是线程安全的。
ConcurrentHashMap通过在部分加锁和利用CAS算法来实现同步,
在get的时候没有加锁,Node都用了volatile。
在扩容时,会给每个线程分配对应的区间,并且为了防止putVal导致数据不一致,
会给线程所负责的区间加锁。

能给我讲讲JDK7和JDK8中的HashMap和ConcurrentHashMap的区别吗

不能,我不会,你不是说好的是JDK8吗,
其实学习的时候看过JDK7的这两个,还有有一些不一样的地方,
比如JDK7的HashMap是头插法,JDK8变成了尾插法,在JDK7还没引入红黑树。。。

你这很危险,这都不会

说什么呢,危险个锤子

拜拜

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值