我们知道HashMap中经常用到hash()方法。
比如:put()方法中
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 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;
}
我们知道hashCode是一个与元素地址有关的值,具体怎么计算的不需要关心,简单讲就是通过hashCode()方法
将一个对象转换成了一个数,中间经过了一系列运算。
但是HashMap中为什么有多了一个hash方法呢,对key的hashcode()方法的返回值又做了什么操作?
static final int hash(Object key){
int h;
return (key==null)?0:(h=key.hashCode())^(h>>>16); //进行了右移16位的操作(高16位与低16位互换)
}
图片来源知乎
通过计算hash Code()与调用hash方法,就是要达到尽量少的key碰撞,最好是将(key,value)对均匀分配到
table数组中,减少put()和get()等操作的消耗的时间。
HashMap的初始化大小为16,然后每次扩容都是乘以2,这样设计也是有道理的。
HashMap中table[0]处存放的是key为null的键值对,除去这个特例,就剩下15个table位置。
15的二进制写法为1111。我们可以看index for()方法
static int indexFor(int h,int length){
return h&(length-1);
}
通过计算hash()方法得到的值与table的大小减1相与(减的一就是为key=null留的)
我们知道0,1值与1相与的结果只与自身有关,与一个全1的数相与,可以完全体现这个数的特性。
如果我们不选择16或者2的n次方作为table的大小。举个例子:
假设一table大小为15,15-1=14。14的二进制表示为1110,我们可以看出不管一个数的最后一位为什么,相与之后都为0,
这样就相当于忽略了这一位的作用,而1111就不同了,它能够完全显示数本身的特性。
扩容时乘2操作能够继续保持这种特性。
rehash与HashMap的扩容有关,先看一下源码:
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) { //判断容量是否已经超过了最大值
threshold = Integer.MAX_VALUE; //容量最大只能为Integer。MAX_VALUE.
return;
}
Entry[] newTable = new Entry[newCapacity]; //创建一个新的数组
transfer(newTable); //将旧数组放到新的里面
table = newTable;
threshold = (int) (newCapacity * loadFactor); //更新扩容的阈值
}
详细看一下transfer()方法:
void transfer(Entry[] newTable) {
Entry[] src = table; //引用原table
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) { //遍历原table
Entry e = src[j];
if (e != null) { //如果原table[j]处不为null,即存在要复制的元素
src[j] = null; 将原table[j]标记为null,等待GC回收
do { //这个while循环作用时为该处的链表中的所有Entry 在新的table中找到相应的位置
Entry next = e.next;
int i = indexFor(e.hash, newCapacity); //计算在新的table中的下标
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
HashMap是线程不安全的,为什么不安全,哪些操作会导致线程不安全呢?
下一篇文章我会详细分析一下HashMap中的一些可能导致线程不安全的操作。