JDK 1.8 中hash算法和寻找算法是如何优化?
// JDK 1.8以后的HashMap里面的一段源码
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
高低16位参与运算:
比如有一个key的hash值
原值: 1111 1111 1111 1111 1111 1010 0111 1100
右移16位:0000 0000 0000 0000 1111 1111 1111 1111
异或运算: 1111 1111 1111 1111 0000 0101 1000 0011 -> 转换成int值表示hash值
寻址算法优化:
(n - 1) & hash -> 算出数组里的一个位置下标
取模运算性能差一些,为了优化数组寻址过程,数组长度2的n次方,hash & (n – 1)效果跟hash对n取模效果一样,与运算性能更高。
核心在于低16的与运算
hash算法的优化:对每个hash值,在他的低16位中,让高低16位进行了异或,让他的低16位同时保持了高低16位的特征,尽量避免一些hash值后续出现冲突,大家可能会进入数组的同一个位置。
HashMap如何解决hash碰撞问题?
hash冲突问题,链表+红黑树,O(n)和O(logn)
map.put和map.get -> hash算法优化(避免hash冲突),寻址性能优化
假设你的链表很长,可能会导致遍历链表,性能会比较差,O(n)
优化,如果链表的长度达到了一定的长度之后,其实会把链表转换为红黑树,遍历一颗红黑树找一个元素,此时O(logn),性能会比链表高一些。
说说HashMap是如何进行扩容的?
2的N次方扩容。16,32,64。。。
rehash:
如果数组的长度扩容之后 = 32,重新对每个hash值进行寻址,也就是用每个hash值跟新数组的length - 1进行与操作。
n-1 0000 0000 0000 0000 0000 0000 0001 1111
hash1 1111 1111 1111 1111 0000 1111 0000 0101
&结果 0000 0000 0000 0000 0000 0000 0000 0101 = 5(index = 5的位置)
n-1 0000 0000 0000 0000 0000 0000 0001 1111
hash2 1111 1111 1111 1111 0000 1111 0001 0101
&结果 0000 0000 0000 0000 0000 0000 0001 0101 = 21(index = 21的位置)
判断二进制结果中是否多出一个bit的1,如果没多,那么就是原来的index,如果多了出来,那么就是index + oldCap,通过这个方式,就避免了rehash的时候,用每个hash对新数组.length取模,取模性能不高,位运算的性能比较高。
参考资料:
互联网Java工程师面试突击(第三季)-- 中华石杉