HashMap下标得来
假设我们要往HashMap中put一个元素,那么如何确定这个元素所在的下标呢。这里看一下JAVA8的源码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
关注这一句话p = tab[i = (n - 1) & hash]
这里的hash值又是如何得来的呢,继续看源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
关注(h = key.hashCode()) ^ (h >>> 16)
问题1:为什么要进行>>>无符号右移运算?
问题2:为什么要进行异或^运算?
-
为什么要进行>>>无符号右移运算?
解决这一个问题,我们首先要明白hash最关键的就是散列。所以在进行运算的时候,我们尽可能让不同的输入,可以换来尽可能多的不同输出。
p = tab[i = (n - 1) & hash]
HashMap的默认数组长度为16,转换为二进制为1111,在与hash值&时,1111只会与hashcode中的低4位相联系,为了达到散列的效果,我们应该尽可能让hashcode的高16位和低16位数都参与进来。
进行>>>16位运算,可以让高16位与低16位产生联系,可以同时保留高16位于低16位的特征。 -
为什么要进行异或^运算?
为什么这里是^运算,而不是&运算。
&运算的特点是除了1&1之外,所有的数相&结果都是0,所以&运算,高十六位所代表的部分特征就可能被丢失,大大降低散列特点,出现更多的相同结果。
而^第一个操作数的的第n位于第二个操作数的第n位相反才为1,否则为0。目的同样在于尽可能保留高16位和第16位数的特征,达到更好的离散效果。
问题3: 为什么数组长度要与hash进行&运算?
- 为什么数组长度要与hash进行&运算?
hash是32位二进制数值,数组中无法存储这么多的元素,进行&运算,降低存储空间。
问题4: 为什么数组长度要减1?
- 为什么数组长度要减1?
只有当length为2的n次方时,(length-1)&key.hashCode才等于key.hashCode%length。
而&运算不像%运算需要转换为10进制进行运算,性能会更高。
作者理解有限,若有错误,欢迎指正,不要开喷作者QAQ
HashMap在Java8中通过特定的散列算法确定元素下标。本文关注于为何使用>>>无符号右移、异或^运算以及为何数组长度与hash进行&运算,并解释了这些操作如何提高散列效果和性能。

4501

被折叠的 条评论
为什么被折叠?



