首先看看HashMap中一些属性:
//默认的初始容量(容量为HashMap中槽的数目)是16,且实际容量必须是2的整数次幂。
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认加载因子为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 存储数据的Entry数组,长度是2的幂。HashMap采用链表法解决冲突,每一个Entry本质上是一个单向链表。
transient Entry[] table;
// HashMap的底层数组中已用槽的数量
transient int size;
// HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
int threshold;
// 加载因子实际大小
final float loadFactor;
// HashMap被改变的次数
transient volatile int modCount;
//根据key的hashcode重新计算hash值
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
//返回h在数组中的索引值。这里用&代替取模,旨在提升效率。 h & (length-1)保证返回值的小于length。
static int indexFor(int h, int length) {
return h & (length-1);
}
重点说一下由hash值找到对应索引的方法indexFor():
一般我们对哈希表的散列是采用“除法散列法”,Hashtable中是这样实现的。
但取模用到除法运算,效率低下,所以HashMap通过h & (length-1)代替取模同样实现均匀的散列。
HashMap采用h & (length-1)这种方式进行哈希散列的前提是因为规定哈希表的容量必须是2的整数次幂。
原因如下:
①length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;
②其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间
综上所述:length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。
扩容。newCapacity是调整后的单位。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) { //如果容量已经达到HashMap最大值。
threshold = Integer.MAX_VALUE; //则将Integer.MAX_VALUE:2**31-1赋给阀值。
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable); //调用transfer方法,将就HashMap的全部元素添加到新的HashMap中。
table = newTable; //将新HashMap赋值给旧HashMap。
threshold = (int)(newCapacity * loadFactor); //最后再调整HashMap的阀值。
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
//迭代老数组中的元素,根据扩容后的新长度,调用indexFor()重新计算新的索引位置。
for (int j = 0; j < src.length; j++) {
Entry
// 是否包含null值 (同样是对数组的每个链表遍历寻找)
private boolean containsNullValue() {
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value == null)
return true;
return false;
}