JDK7 中的 HashMap的数据结构是 数组+链表,所以,我们需要通过一些计算尽可能平均的算出数据所在的索引(0-数组长度-1)
HashMap中的源码
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
通过看源码,HashMap底层用的是位运算(& 位与运算符)来计算hashcode所在的索引,但具体是怎么算的呢,这就要根据 位与运算符的特点: 遇false则false,遇0则0(在二进制中,0表示false)
比如:
// 比如hash的capacity 为默认的 16(索引的取值范围为 0 - 15) hashcode的值为 1010 1101
1010 1101
& 0000 1111
0000 1101 index = 13
从上面的例子可以看出,根据与 位与运算符 的特点,取的索引值只能为 0000 - 1111 之间 也就是 0 - 15 之间,而且,还是一点就是由位与运算符计算可以说是平均的计算出了索引值,因为它低位上的值都为1(Hashmap的 capacity都是2的次方幂),最后得出的二进制数,不由最大的索引值控制,而是又hashcode控制,而hashcode的计算又不在我们的预料中,所以这样的算法也算是平均的分配每个元素。
Hashmap 中的capacity 只能是 2的次方幂,于此也是有一定的关系
补充:通过取模也可以算出hashcode所对应的索引值,但是效率没有位运算快
但是,也是因为位与运算符的原因,使得高位上的值无论是什么值都不会影响所算出结果(以上面为例,前28位无论是什么值都不会影响结果,结果只由后四位所得出),hashmap就是尽量的让数据散列开来,这明显了违背了这一原则,所以HashMap源码通过位运算来让所获取到hashcode发生改变,本身hashcode也是任意的,使得hashcode 变得更加散列,让高位所取得不同的值也会影响到获取索引的结果。可以理解为,麻将或者是扑克的洗牌,尽可能的让牌更加散列
hashmap 计算hashcode的源码
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
还有一种情况:那就是key为 null的时候,HashMap是允许key为null(key 为 null 的元素只能有一个)的,那么为空是无法调用它的hashcode方法,HashMap给出了对应的措施,让他对应的索引默认为 0
/**
* Offloaded version of put for null keys
*/
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}