什么是resize?
resize就是重新计算容量;向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素;当然java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组;就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。
1. 何时resize,下面是addEntry()方法的代码片段
1
2
3
4
|
...
if
(size++ >= threshold)
resize(
2
* table.length);
...
|
阈值(threshold)是怎么计算的?如下源码:
1
|
threshold = (
int
)(capacity * loadFactor);
|
以默认值为例,阈值=16*0.75=12,当元素个数大于12时就要扩容;那剩下的4(如果内部形成了Entry链则大于4)个数组位置还没有放置对象就要扩容,岂不是浪费空间了?
这是时间和空间的折中考虑;loadFactor过大时,map内的数组使用率高了,内部极有可能形成Entry链,影响查找速度;loadFactor过小时,map内的数组使用率旧低,不过内部不会生成Entry链,或者生成的Entry链很短,由此提高了查找速度,不过会占用更多的内存;所以可以根据实际硬件环境和程序的运行状态来调节loadFactor;
2. 如何做resize?我们看一看resize()源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void
resize(
int
newCapacity) {
//传入新的容量
Entry[] oldTable = table;
//引用扩容前的Entry数组
int
oldCapacity = oldTable.length;
if
(oldCapacity == MAXIMUM_CAPACITY) {
//扩容前的数组大小如果已经达到最大(2^30)了
threshold = Integer.MAX_VALUE;
//修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
return
;
}
Entry[] newTable =
new
Entry[newCapacity];
//初始化一个新的Entry数组
transfer(newTable);
//!!将数据转移到新的Entry数组里
table = newTable;
//HashMap的table属性引用新的Entry数组
threshold = (
int
)(newCapacity * loadFactor);
//修改阈值
}
|
3. transfer()偷偷干了些什么?
如果把旧的Entry链放到新数组的对应位置上,简单明了,但是这样操作对吗??
看一看transfer()源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
void
transfer(Entry[] newTable) {
Entry[] src = table;
//src引用了旧的Entry数组
int
newCapacity = newTable.length;
for
(
int
j =
0
; j < src.length; j++) {
//遍历旧的Entry数组
Entry<K,V> e = src[j];
//取得旧Entry数组的每个元素
if
(e !=
null
) {
src[j] =
null
;
//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
do
{
Entry<K,V> next = e.next;
int
i = indexFor(e.hash, newCapacity);
//!!重新计算每个元素在数组中的位置
e.next = newTable[i];
//标记[1]
newTable[i] = e;
//将元素放在数组上
e = next;
//访问下一个Entry链上的元素
}
while
(e !=
null
);
}
}
}
|
indexFor()是计算每个元素在数组中的位置,源码:
1
2
3
|
static
int
indexFor(
int
h,
int
length) {
return
h & (length-
1
);
//位AND计算
}
|
例如,旧数组容量为16,对象A的hash值是4,对象B的hash值是20,对象C的hash值是36;
通过indexFor()计算后,A、B、C对应的数组索引位置分别为4,4,4; 说明这3个对象在数组的同一位置上,形成了Entry链;
旧数组扩容后容量为16*2,重新计算对象所在的位置索引,A、B、C对应的数组索引位置分别为4,20,4; B对象已经被放到别处了;
总结:resize时,HashMap使用新数组代替旧数组,对原有的元素根据hash值重新就算索引位置,重新安放所有对象;resize是耗时的操作。
转自:http://my.oschina.net/placeholder/blog/180069