基础变量
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
HashMap默认的初始容量为16,同时还强调了容量必须是2的幂次方。
最大容量为1<<30。默认的装载因子 为0.75,这个下面再说。
其主体是一个Entry数组table,当需要时会重新分配大小,但是长度依然必须是2的幂次方。
装载因子
装载因子=实际放入的元素/HashMap的总容量。
为了避免元素冲突,容量都会比放入的元素大,因此越小的装载因子意味着元素发生碰撞的概率越低,但是也意味着浪费空间的概率会越高。
容量:2的幂次方
HashMap的容量从来都是2的幂次方,虽然插入的数量/装载因子 得到的数字可能不是2的幂次方,但是该容量在申请的时候从来都会变成2的幂次方,这是有原因的。
搜寻位置:哈希与再哈希
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
首先判断key是否为空,hashMap是支持放入null作为key的。
其次,对key的hashCode进行再哈希得到hash。
为何需要进行再哈希呢?事关如下这个函数:indexFor。
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
这个函数没有什么特殊,仅仅是使用了位运算来代替求余运算。得到的余数对应着元素的位置。
但是这有个问题:由于length是2的幂次方,length-1就成为了除了最高位为0,其余位全都为1的值,此时如果单纯的通过key的hash进行计算,如果hash的对应低位没变(这很有可能,比如length-1=0111,而hash为1111,10111,11111,110111等等),由于取余结果仅仅跟这些低位有关,而这些低位的值从来没有为了区分整个key.hashCode而做过优化,他们无法代表整个key.hashCode做出区分,因此会导致冲突的高发率。
因此,put方法中在indexFor之前,对原有的key.hashCode进行再次hash,使得再哈希结果中的1尽量均匀而且随机的分布在所有数位上,这样求余之后得到的结果碰撞率就减小了。
再哈希方法如下:
static int hash(int h) {
// 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);
}
这就是hashMap容量为何要做到2的幂次方,同时,hashMap的put方法为何要再次hash的意义。
如果还是没有弄懂这么做的意义的话,请仔细琢磨加黑的语句。
对该语句的理解:虽然key.hashCode都是不相同的,这足以区分各个key,但是,他们的不同仅仅是表现在了hashCode的除去低length-1位的高位中,因为在上述的例子中:比如length-1=0111,而hashCode为1111,10111,11111,110111等等,那么他们的区分度并不在末尾的三位上,而是在高位上,因为末尾三位跟length-1相与结果是一样的,这就产生了冲突。因此,再哈希的意思就是尽量让这末尾的三位中也能体现出各自hashCode的不同,这就是意义所在。