-
HashMap, ConcurrentHashMap,HashTable 的结构,在JDK 1.7 和1.8 中有什么不同
其中抛弃了原有的Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性,这个put的过程很清晰,对当前的table进行无条件自循环直到put成功
a. 判断Node[]数组是否初始化,没有则进行初始化操作
b. 通过hash定位数组的索引坐标,是否有Node节点,没有则进行CAS添加,添加失败进入下次循环
c. 检查到内部在扩容,帮助他
d. 如果f!=null,则使用synchronized锁住f元素(链表/红黑树的头元素)。如果是Node(链表结构)则执行链表的添加操作;如果是TreeNode(树型结构)则执行树添加操作
e.判断链表长度已经达到临界值8(默认值),当节点超过这个值就需要把链表转换为树结构
f.如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容 -
解决hash碰撞方法
开放地址法: 一旦发生冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并记录
链地址法: 将哈希表的每个单元作为链表的头节点,所有哈希地址为i的元素构成一个同义词链表,发生冲突时 放在链表尾部
再哈希:当hash地址冲突时,再通过其他函数计算另一个hash地址,直到冲突不再产生。
建立公共溢区:将哈希表分为基本表和溢出表两个部分,发生冲突的元素都放入溢出表中。 -
h & (length-1) ,为什么HashMap的数组长度要取2的整次幂?
因为这样(数组长度-1)正好相当于一个“低位掩码”。“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问,
-
运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快
-
HashTable中也没有indexOf方法,取而代之的是这段代码:int index = (hash & 0x7FFFFFFF) % tab.length,( 0x7FFFFFFF是long最大值),HashTable默认的初始大小为11,之后每次扩充为原来的2n+1,也就是说,HashTable的链表数组的默认大小是一个素数、奇数。之后的每次扩充结果也都是奇数,当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀
-
Java 8的ConcurrentHashMap作者认为引入红黑树后,即使哈希冲突比较严重,寻址效率也足够高,所以作者并未在哈希值的计算上做过多设计,只是将Key的hashCode值与其高16位作异或并保证最高位为0(从而保证最终结果为正整数)。
-
负载因子(load factor):负载因子等于“size/capacity”,负载极限”的默认值(0.75)是时间和空间成本上的一种折中,如果过高会降低空间开销,会影响put()、get()效率,如果过低 最终使用空间和未使用空间的差值会逐渐增加,空间利用率低下。0.75是怎么得来的呢, 一个bucket空和非空的概率为0.5,通过牛顿二项式等数学计算,得到这个loadfactor的值为log(2),约等于0.693
-
put时,是加到链表尾
-
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在LinkedList中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在LinkedList的尾部,而是放在头部,为什么不加到新链表最后?因为复杂度是 O(N)。如果条件竞发生了,那就死循了。
-
什么是一致性hash?
先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 232-1]),接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。 -
Java8 HashMap扩容时为什么不需要重新hash ?
java8在实现HashMap时做了一系列的优化,其中一个重要的优化即在扩容的时候,原有数组里的数据迁移到新数组里不需要重新hash,而是采用一种巧妙的方法,e.hash & oldCap的结果来判断,如果是0,说明位置没有发生变化,如果是1,说明位置发生了变化,而且新的位置=老的位置+老的数组长度。
map原理
最新推荐文章于 2022-08-11 19:44:32 发布