先看hashMap的get和put方法
-
get
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; // 先获取table,table就是用来存储map所有的桶的。如果table为空,退出 // 获取n,桶数目,如果为0,退出 // 最后通过table[(n-1) & hash]获取key所在桶的第一个node。 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 下面先不看 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; } -
put
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i;n // 这里是给tab赋为table,以及获取table的长度n。t // 如果table未初始化,就先进行初始化 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // i表示的是该key所在桶位置 // i = (n - 1) & hash 就是用来确定该key所在桶的下标的 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; }
分析
看上面注释的位置,我们会发现HashMap是通过hash&(n-1)来确定key在哪个桶的,n为桶的数目。
如果按照一般思路,我们要让hash映射到一个桶上,需要计算出一个桶的索引index,这个index取值范围是[0, n),n为桶的数目。那么我们可以用hash % n 计算得出,因为对n取余数的结果一定在[0,n)范围内
但是java有自己的想法。因为如果当n 为2的整数次幂的话,hash & (n-1) = hash % n 的。java用&运算来取代了%运算。因为&运算比%要来得快。至于为什么会有这个关系的原因如下:
众所周知,2的整数次幂的二进制只有一个1,其余都是0。
- 比如 2^0 = (1)2, 2^1 = (10)2, 2^2=(100)2, 2^3 =(1000)2,2^10 = (10000000000)2
那么2次幂 - 1的话,最高一位变成了0,后面的0全都变为1
- 比如 2^0 - 1 = (0)2, 2^1 - 1 = 01, 2^2 - 1 = (011)2, 2&3 - 1 = 0111, 2^10 - 1 = (01111111111)2
用高位以上全为0,后面全为1的数和hash做&运算,得到的数一定在[0, n)之间,
- 假设n=2^10,那么(11111111111011010)2 & (2&10 - 1)如下
11111111111011010
& 00000001111111111
---------------------
000000001111011010
00000000111011010 是小于2^10次方的(0000000010000000000)。这个数也是hash % n的值
所以,结论就是,hashMap为了提高效率,在确定key属于哪个桶时,用hash & (n -1) 来取代 hash % n 。要使这个式子成立,n必须为2的整数次幂