结构
hashTable底层是数组+ 链表
hashMap底层是数组+链表+红黑树
hashMap
数组中的每个元素可视为一个桶,桶里面放的是链表,当桶的个数> 64
,单链的长度>8
,那么链表被替换为红黑树结构,同时使用尾插法。当数据长度<6
时候红黑树又转化为单链。
put存取原理:
(1)计算key的hash值,然后进行二次hash,根据二次hash结果找到对应的索引位置
(2)如果这个位置有值,先进性equals比较,若结果为true则取代该元素,若结果为false,就使用高低位平移法将节点插入链表(JDK8以前使用头插法,JDK8之后就用尾插法。但是头插法在并发扩容时可能会造成环形链表或数据丢失,而高低位平移发会发生数据覆盖的情况)
hash冲突
首先需要了解下散列映射,h=f(x),x为键值,h为hash值,f为散列函数,hash冲突是因为x的是无限的,而h的存储范围是有限的,导致x1≠x2,但是f(x1)=f(x2),这就是hash冲突。(理论上不同的哈希值散列映射后的哈希值不相同)
解决hash冲突
1.开发寻址法(线性探测法)
从发生冲突的位置依次往后探测,将空的hash桶放入重复的值
2.链地址法
将重复的hash值放入同一个hash桶里,该hash桶使用单链表存储冲突的元素,各链表的表头结点存放在hash表中。
hashMap<key,value>()
key可以为null,value也可为null;但是只能有一个键值为null,在hashMap源码中当key=null,value=0。
hashTable<key,value>()
key和value都不能为null
在计算hash值的时候(h = key.hashCode()) ^ (h >>> 16),为什么要使用 ‘>>> 16’ 这样的位运算符?
^
是逻辑运算异或操作(相同得0,不同得1)
在不参与异或和右移位运算符时候
hash = key.hashCode() 01101010 11101111 11100010 11000100
(n - 1) 00000000 00000000 00000000 00001111
------------------------------------------------------------
(n - 1) & hash = 00000000 00000000 00000000 00000100
可以看出,计算后得的值低位一样,只是高位不同而已,这样hash碰撞中很容易相同的键值映射到同一个hash桶里面,而对hash值进行为运算符可以将利用低位部分,而将高位的低位的进行混合就能增加hash值的随机性,使得hash桶的映射更加均匀,提高hash表的性能。
参考文献:
hashMap详解
hashTable详解
HashMap 要用 h^(h >>>16) 计算hash值?槽位数必须是 2^n?