Hashmap知识整理

      在最近秋招的过程中,很多时候都提到了java集合中一个常见Hashmap的提问。因为本人也是从最近才开始重拾java的学习,本篇日记仅用于自己的一个梳习整理,如有错误欢迎指正:

 

首先Hashmap是一种经典散列表,存储的是键值对(key-value)。key值的计算是通过key值计算出对应的hashcode。在通过hashcode计算出对应的hash值,最后通过hash值&(length-1)得到最后的值。

Hash算法的本质上是三步:取key的hashCode的值、根据hashcode计算出hash值、通过取模计算下标。

在JDK1.7中Hashmap的底层结构是链表+数组。(采用头插法)

在JDK1.8中底层结构是链表+数组+红黑树。(采用尾插法)

 

就put方法举例的简要流程:

1.根据key值计算出对应的hash值,找到该元素在数组中下标存在的位置。

2.判断table是否为空,如果为空则进行resize初始化。通过key计算的hash值插入的数组索引然后判断hash是否冲突 ,如果否就插入键值对,如果存在key值直接覆盖其value。

3.如果链表长度大于8且数据总量大于64,则转化为红黑树。(小于64则初始化数组进行扩容)【因为链表时间冗余度为O(n),红黑树冗余度为O(nlogn),在小于8时优先考虑链表,大于8转化成红黑树】

 

Hashmap线程不安全原因:

1.多线程下扩容会死循环。JDK1.7采用的头插法,在多线程环境中,扩容可能导致环形链表的出现。但是JDk1.8采用的尾插法,在扩容时会保持链表元素的本来顺序,不会形成环形链表的出现。

2.多线程的put可能导致元素的丢失。多线程同时执行put操作,如果计算出索引位置相同,可能导致前一个key被后一个key覆盖,从而导致元素的丢失。

3.put和get并发时,可能导致get为null。

解决方法:采用ConcurrentHashmap。采用了相同的底层结构,都是数组+链表+红黑树。在锁的实现上不同意Hashtable采用一个大的synchronize锁住这个表,而是采用CAS+synchronize实现更加低粒度的锁。将锁的级别控制在了更细粒度的哈希桶元素级别,也就是说只需要锁住这个根节点即可。

多线程下安全操作map还可以使用Collections.synchronizeMap的方法,对方法进行同步锁。传入Hashmap对象,使用对象锁保证多线程下的线程安全,本质和Hashtable相似也是进行了全表锁。

 

Hashmap的负载因子(loadFactor)为0.75

是相较于空间时间的一个平衡选择。追溯作者在源码下的注释翻译得:作为一般规则,默认负载因子(0.75)在时间和空间成本上提供了很好的折中。更高的值会降低空间开销 但是提高时间成本,反之就是空间成本增加。本质上就是时间空间的一个平衡选择。

 

Hashmap的最大值为2^30次方

这里引用他人回答:

“JAVA规定了该static final 类型的静态变量为int类型,至于为什么不是byte、long等类型,原因是由于考虑到HashMap的性能问题而作的折中处理!

由于int类型限制了该变量的长度为4个字节共32个二进制位,按理说可以向左移动31位即2的31次幂。但是事实上由于二进制数字中最高的一位也就是最左边的一位是符号位,用来表示正负之分(0为正,1为负),所以只能向左移动30位,而不能移动到处在最高位的符号位!”


 

posted @ 2021-11-02 12:55  麦克斯韦妖dai  阅读(42)  评论(0)    收藏  举报