集合之HashMap 1.7总结

文章目录

    • 底层数据结构:
    • HashMap有什么特点?
    • HashMap 1.7 是如何解决Hash冲突的?
    • HashMap 1.7 是如何降低Hash冲突概率的?
    • HashMap 的长度为什么是2的幂次方?
    • HashMap 容量应该如何设置?
    • 高并发下如何使用HashMap?
    • HashMap在高并发下为什么不安全?
    • 总结的put()和get()方法的流程图

底层数据结构:

	数组+链表
	数组特点:根据下标定位,一般情况下查询、修改快于链表。
	链表特点:连接元素通过指针,新增和删除只需变更指针连接即可,所以一般情况下增删快,查询因需要遍历链表,所以查询效率一般慢于数组。

HashMap有什么特点?

​ 无序,散列存放,遍历结果与存储顺序不一定一致。

​ 如果需要有序存放,可以使用LinkedHashMap来存放元素。

HashMap 1.7 是如何解决Hash冲突的?

采用链地址法来存储发生Hash冲突的元素。

HashMap 1.7 是如何降低Hash冲突概率的?

  • 使用2的幂次方作为容量,计算时使用len-1与key的hash做与运算,减少hash冲突的概率。
  • 高16位做异或运算,(h = key.hashCode()) ^ (h >>> 16)。

HashMap 的长度为什么是2的幂次方?

  • 为了减少Hash碰撞,使Hash计算结果更均匀。
  • 做与运算时,我们需要取数组长度-1,比如使用默认长度16,那么参与与运算的值为15,15的二进制为0000 1111,此时,参与与运算后得到的key的位置还是key本身,这样可以使key分布的更均匀。

HashMap 容量应该如何设置?

  • 參考阿里巴巴开发手册,集合初始化时,应该指定初始值大小。
  • initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即loader factor)默认为0.75, 如果暂时无法确定初始值大小,请设置为16(即默认值)。
  • 如果存储1W个key,那么按照公式:i = 10000/0.75 +1 =13334个。如果不设置初始容量,则需要扩容10次左右,影响效率。

高并发下如何使用HashMap?

  • 线程安全的集合类主要包括了三大类:
    • 遗留的线程集合类如Hashtable、Vector;性能较低
    • Collections修饰的线程安全集合类如synchronizedMap;使用synchronized修饰来保证线程安全。
    • JUC包下的安全类。
  • 高并发下推荐使用ConcurrentHashMap来保证线程安全。

HashMap在高并发下为什么不安全?

  • 数据覆盖问题

    • 高并发下两个线程同时做put操作,当key相等时,第二个线程可能会覆盖第一个线程的值。
  • 循环链表问题

    • 此问题主要发生于扩容时。链表使用了头插法,在扩容时可能会导致原有的链表顺序逆转。

      举例:比如原先链表为a-b-c-null,此时需要扩容,有两个线程同时扩容,第一个线程执行一半因为某种原因卡住了,此时线程2开始执行,执行完成后,c又指向了a,此时线程1恢复执行,本来线程1执行到c后,下一个元素为null,就会停止执行,但是受到线程2的影响此时c指向了a,所以继续执行,就构成了循环链表。

    • 导致的问题:在put或get方法时,会遍历链表,但此时链表构成了循环,所以会一直遍历,导致死循环,逐渐CPU占用会达到100%居高不下。

  • 数据get()为null的情况

    • 当并发操作时,此时有两个线程,一个执行get方法,一个执行put方法,此时执行put方法导致了扩容,会创建一个新数组,引用会赋给新数组,此时可能数据还没有移动到新数组,如果此时执行get方法,那么取到的值可能为空。

总结的put()和get()方法的流程图

  • PUT方法:

集合之HashMap 1.7总结_第1张图片

  • GET方法:
    集合之HashMap 1.7总结_第2张图片

你可能感兴趣的:(集合,java,集合)