浅谈HashMap

这不是最近大四要开始找工作了嘛,我就仔细看了看HashMap的一些原理,今天就写在CSDN上跟大家分享一下,也算给自己加深一下印象

1、HashMap的概述
HashMap基于Map接口实现,元素以键值对的方式存储,并且允许使用null 建和null值,因为key不允许重复,因此只能有一个键为null,另外HashMap不能保证放入元素的顺序,它是无序的,和放入的顺序并不能相同。

2、HashMap的底层数据结构
1、在jdk 1.8之前 HashMap是数组+链表的数据结构,链表就是用来解决哈希冲突的,所以实际上HashMap就是一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
2、在jdk1.8及1.8之后,对底层结构进行了优化,HashMap的数据结构变成了数组+链表/红黑树的形式,当链表的长度大于8的时候链表就会转换为红黑树。

看到这可能就会有人会问什么是哈希冲突?
哈希冲突其实就是HashMap在进行存值的时候,两个不同的Key通过哈希算法得出来的下标可能相同

3、HashMap的底层实现原理
HashMap的底层实现主要是通过put()和get()两个方法实现的

  • 1、map.put(k,v)实现原理
    首先先将K,V分装到Node节点中,再调用底层的hashcode()方法算出Key的哈希值,然后在对map的容量大小进行取模,算出下标,找到对应数组下标的位置,如果该位置上没有元素则将节点放入该位子上,如果该位子上已经有链表了,则拿着key和链表上每个节点的key进行equals,如果返回的都为false则将node节点添加到链表的尾端,如果有一个返回的是true,则覆盖对应节点的value值
  • 2、map.get(k)实现原理
    首先还是先调用hashcode()方法算出Key的哈希值,然后在对map的容量大小进行取模算出数组的下标,通过数组下标快速定位到某个位置上。如果这个位置上什么都没有,则返回null,如果该位置上有链表则拿着key和链表上每个节点的key进行equals,如果返回的都为false则返回null,如果有一个返回的是true,则返回对应的value值

4、HashMap 中的数组是否可以替换成LinkedList,如果可以为什么不用链表?如果不可以为什么?
当然是可以的,但是替换成链表之后会大大降低HashMap的查询效率,因为数组的查询效率要比链表快很多(数组的空间是连续的,链表的空间是不连续的)。

4、HashMap 的负载因子为什么是0.75
当负载因子太大的时候就会导致大量的哈希冲突,就会影响Hashmap的查询效率,当负载因子小的时候可以很大程度的避免哈希冲突,但是太小的负载因子会导致Hashmap大量的空间资源浪费,所以最后通过泊松分布公式得出负载因子0.75是最好的选择

5、HashMap 的长度为什么是 2 的幂次方
这个我其实也不是很搞懂 我就只搞懂了一部分
HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标。 计算数组下标的算法其实就是取模:用hash值对map的数组长度进行取模获取数组下标(1.8以前)。计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1)(1.8以后), 什么时候hash%length和hash&(length-1)相等呢? 答案就是length是2的n次方
并且HashMap 的长度为是 2 的幂次方这样就能使元素在哈希表中均匀地散列。

6、HashMap线程安全吗?为什么?
不安全,因为在多线程中往HashMap存值的时候会产生数据覆盖在问题

7、既然HashMap是线程不安全的,那如果需要线程安全怎么办?
1、HashTable 采用了synchronized方法全表加锁,使用阻塞同步,但是效率低
2、synchronizedMap synchronizedMap是Collections的私有静态内部类,同样也是采用synchronized的方法
3、CopyOnWriteMap (读写分离思想) 当对容器进行增加,删除,修改操作时,并不是直接操作容器,而是先将当前容器copy,复制出一个新的容器,对新容器进行操作,操作完成之后再将指针指向这个新容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读(数组用volatile修饰),而不用对其进行加锁,当然写的时候是需要加锁的(采用lock加锁)
4、ConcurrentHashMap

8、 ConcurrentHashMap 为什么可以线程安全?
ConcurrentHashMap 采用了分段锁的机制,将数据分成多段进行存储,然后给每一段数据都加上一把锁,在多线程中,一个线程占用锁资源访问该段数据的时候,并不会影响其他段的数据也可以被其他线程所访问,并且ConcurrentHashMap 可以做到读操作的时候不进行加锁

9、 ConcurrentHashMap 的实现原理,1.7 和1.8 的区别
jdk1.7的时候ConcurrentHashMap是由segment和HashEntery 组成的,segment继承了ReentrantLock,是一种可重入锁,HashEntry 则用于存储键值对数据,一个ConcurrentHashMap里包含了一个segment的数组,一个segment里又包含了一个HashEntry数组,每个HashEntry都是链表的元素,所以jdk 1.7的ConcurrentHashMap是数组+链表的数据结构。当对ConcurrentHashMap中的数据进行修改的时候,必须要首先获得对应的segment锁,这样不仅保证了每个segment是线程安全的,也保证了ConcurrentHashMap全局的线程安全。
jdk1.8的时候ConcurrentHashMap抛弃了segment,选择了和HashMap相同的数组+链表/红黑树的数据结构,在锁的是线上采用了CAS 操作和 synchronized 锁实现更加低粒度的锁,将锁的级别控制在了更细粒度的 table 元素级别

10、 jdk 1.8的时候为什么ConcurrentHashMap抛弃了ReentrantLock采用了 synchronized?
我个人认为:synchronized是JVM层面上的关键字所以java的开发人员一直在给它进行优化,在jdk1.8中synchronized锁的性能得到了很大的提升,并且拥有了多种的锁定形态,偏向锁,轻量级锁,重量级锁,并非和之前一样一开始就是重量级锁。并且在粗粒度加锁中ReentrantLock可以通过 Condition 来控制各个低粒度的边界,更加灵活,但是在低粒度下,Condition 的优势就没有了,此时JVM层面的synchronized 不失为一种更好的选择。

我从知乎那拿了两张图 可以更清晰的看出1.7和1.8之间HashMap有什么区别
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值