HashMap:数组
public HashMap(int initialCapacity, float loadFactor) { //初始容量不能<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 < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; //设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操做 threshold = (int) (capacity * loadFactor); //初始化table数组 table = new Entry[capacity]; init(); }
在这里提到了两个参数:初始容量,加载因子。性能
这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是建立哈希表时的容量,this
加载因子是哈希表在其容量自动增长以前能够达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。spa
对于使用链表法的散列表来讲,查找一个元素的平均时间是O(1+a),所以若是负载因子越大,对空间的利用更充分,然然后果是查找效率的下降;线程
若是负载因子过小,那么散列表的数据将过于稀疏,对空间形成严重浪费。系统默认负载因子为0.75,通常状况下咱们是无需修改的。ci
加载因子:hash
loadFactorit
扩容:io
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold) // 这里是关键,一旦大于等于threshold的数值 resize(2 * table.length); // 将会引发容量2倍的扩大 }
void resize(int newCapacity) { 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的值 }
void transfer(Entry[] newTable) { // 保留原数组的引用到src中, Entry[] src = table; // 新容量使新数组的长度 int newCapacity = newTable.length; // 遍历原数组 for (int j = 0; j < src.length; j++) { // 获取元素e Entry<K,V> e = src[j]; if (e != null) { // 将原数组中的元素置为null src[j] = null; // 遍历原数组中j位置指向的链表 do { Entry<K,V> 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方法能够看出,table
e.next=newTable[i];
newTable[i]=e;
链表存储倒过来了,最早出来的会将其next指向null,后面的就指向前一个,固然数据只有原来的一部分。
===================================================================
随着HashMap中元素的数量愈来愈多,发生碰撞的几率就愈来愈大,所产生的链表长度就会愈来愈长,这样势必会影响HashMap的速度,
为了保证HashMap的效率,系统必需要在某个临界点进行扩容处理。
该临界点在当HashMap中元素的数量等于table数组长度*加载因子。
可是扩容是一个很是耗时的过程,由于它须要从新计算这些数据在新table数组中的位置并进行复制处理。
因此若是咱们已经预知HashMap中元素的个数,那么预设元素的个数可以有效的提升HashMap的性能。
问题:
当从新调整HashMap大小的时候,确实存在条件竞争,由于若是两个线程都发现HashMap须要从新调整大小了,它们会同时试着调整大小。
在调整大小的过程当中,存储在链表中的元素的次序会反过来,由于移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。
若是条件竞争发生了,那么就死循环了。