大家好,又见面了,我是你们的朋友全栈君。
HashMap:
public HashMap(int initialCapacity, floatloadFactor) {
//初始容量不能<0
if (initialCapacity < 0)throw new IllegalArgumentException(“Illegal initial capacity: ” +initialCapacity);
//初始容量不能 > 最大容量值,HashMap的最大容量值为2^30
if (initialCapacity >MAXIMUM_CAPACITY)
initialCapacity=MAXIMUM_CAPACITY;//负载因子不能 < 0
if (loadFactor <= 0 ||Float.isNaN(loadFactor))
throw new IllegalArgumentException(“Illegal load factor: ” +loadFactor);
//计算出大于 initialCapacity 的最小的 2 的 n 次方值。
int capacity = 1;
while (capacity
capacity<<= 1;this.loadFactor =loadFactor;
//设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作
threshold = (int) (capacity *loadFactor);//初始化table数组
table = newEntry[capacity]; init();
}
在这里提到了两个参数:初始容量,加载因子。
这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,
加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。
对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;
如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,一般情况下我们是无需修改的。
加载因子:
loadFactor
扩容:
void addEntry(int hash, K key, V value, intbucketIndex) {
Entry e =table[bucketIndex];
table[bucketIndex]= new Entry(hash, key, value, e);
if (size++ >= threshold) //这里是关键,一旦大于等于threshold的数值
resize(2 * table.length); //将会引起容量2倍的扩大
}
void resize(intnewCapacity) {
Entry[] oldTable=table;
int oldCapacity =oldTable.length;
if (oldCapacity ==MAXIMUM_CAPACITY) {
threshold=Integer.MAX_VALUE;return;
}
Entry[] newTable= new Entry[newCapacity]; //新的容器空间
transfer(newTable); //复制数据过去
table =newTable;
threshold= (int)(newCapacity * loadFactor); //重新计算threshold的值
}
voidtransfer(Entry[] newTable) {//保留原数组的引用到src中,
Entry[] src =table;//新容量使新数组的长度
int newCapacity =newTable.length;
//遍历原数组
for (int j = 0; j < src.length; j++) {
//获取元素e
Entry e =src[j];if (e != null) {//将原数组中的元素置为null
src[j] = null;
//遍历原数组中j位置指向的链表
do{
Entry next =e.next;//根据新的容量计算e在新数组中的位置
int i =indexFor(e.hash, newCapacity);//将e插入到newTable[i]指向的链表的头部
e.next =newTable[i];
newTable[i]=e;
e=next;
}while (e != null);
}
}
}
通过上面的transfer方法可以看出,
e.next=newTable[i];
newTable[i]=e;
链表存储倒过来了,最先出来的会将其next指向null,后面的就指向前一个,当然数据只有原来的一部分。
===================================================================
随着HashMap中元素的数量越来越多,发生碰撞的概率就越来越大,所产生的链表长度就会越来越长,这样势必会影响HashMap的速度,
为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理。
该临界点在当HashMap中元素的数量等于table数组长度*加载因子。
但是扩容是一个非常耗时的过程,因为它需要重新计算这些数据在新table数组中的位置并进行复制处理。
所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
问题:
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。
在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。
如果条件竞争发生了,那么就死循环了。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/149916.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...