容器-HashMap计算Hash值底层源码分析(十七)
-
计算Hash值
-
获得Key对象的hashcode
首先调用key对象的hashcode()的方法,获得key的hashcode的值
-
根据hashcode计算出hash值(要求在[0,数组长度-1]区间)
hashcode是一个整数,我们需要转化成[0,数组长度-1]的范围,我们要求转换后的hash值尽量均匀的分布在[0,数组长度-1]这个区间,减少“hash冲突”
-
-
一种极端简单的算法是:
-
hash值=hashcode/hashcode
也就是说,hash值总是1.意味着,键值对对象都会存储到数组索引1位置,这样就形成了一个非常长的链表。相当于每存储一个对象都会发生“hash冲突”,hashMap也退化成了一个"链表"。
-
-
一种简单和常用的算法是(相对取余法)
-
hash值=hashcode%数组长度
这种算法可以让hash值均匀的分布在[0,数组长度-1]这个区间,但是,这种算法由于使用了“除法”,效率低下。JDK后来改进了算法,首先约定数组长度必须为2的整数幂,这样采用位运算即可实现取余的效果:hash值=hashcode&(数组长度-1)。(&:与运算,二进制的数中,两个为才为1,否则全为0).
-
-
我们先去看put()的方法,只有添加元素,才会涉及到hash值的运算
-
map.put()方法用Ctrl+鼠标左键进入源代码,再用Ctrl+Alt选择put方法中的HashMap接口的实现类,进入源代码
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
-
然后我们看hash(key)方法
/** * Computes key.hashCode() and spreads (XORs) higher bits of hash * to lower. Because the table uses power-of-two masking, sets of * hashes that vary only in bits above the current mask will * always collide. (Among known examples are sets of Float keys * holding consecutive whole numbers in small tables.) So we * apply a transform that spreads the impact of higher bits * downward. There is a tradeoff between speed, utility, and * quality of bit-spreading. Because many common sets of hashes * are already reasonably distributed (so don't benefit from * spreading), and because we use trees to handle large sets of * collisions in bins, we just XOR some shifted bits in the * cheapest possible way to reduce systematic lossage, as well as * to incorporate impact of the highest bits that would otherwise * never be used in index calculations because of table bounds. */ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }//主要看三目运算后面的那一个是什么,(h = key.hashCode()) ^ (h >>> 16) //先去取key的hashCode()的值赋给h,再用h做了一个右位移16的处理
-
(h = key.hashCode()) ^ (h >>> 16)//先去取key的hashCode()的值赋给h,再用h做了一个右位移16的处理,就是把32位的整数取了16位(右位移16位也就是从左到右去16位)
^(异或运算,相同为0,相异为1)
假设hashCode()的值是:456789
对应的二进制:0000 0000 0100 0101 0110 0111 1000 1001
和右位移16位也就是从左到右去16位:0000 0000 0100 0101做^运算
-
-
然后返回到putVal()方法
/** * 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) //(n - 1) & hash这个与运算就是为了计算哈希值的 //n是我们之前分析出来的是16,16-1=15 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; }
-
分析putVal()方法中计算哈希值的部分
```java
if ((p = tab[i = (n - 1) & hash]) == null)
//(n - 1) & hash这个与运算就是为了计算哈希值的
//n是我们之前分析出来的是16,16-1=15
```
15的二进制:0000 0000 0000 0000 0000 0000 0000 1111再去与hash做与运算
- 总的一个分析:
- 拿一个hashcode()的值先去转成二进制,搞成32位的整数,
- 去前16位的数,在补齐32位,
- 然后做异或运算,算出来是一个hash
- hash和n-1的二进制做与运算
- 得出hash值