在jdk1.7中,扩容需要满足以下两个条件:
1.存储新值时已有元素的个数必须大于等于阈值
2.当前存储数据发生在新值存储hash时碰撞
在jdk1.8中,扩容只需要满足一个条件:当前存储新值时已有元素的个数大于等于阈值(注意不替换已有元素)(已有元素等于阈值,下一个存储必然会触发扩容机制)或者将数据保存到链表中。这时候如果数据大于8,总数小于64,就会发生扩容。注意:
(1)扩展必须在放置新值的时候,新值不在替换之前位置的情况下
(2)扩展发生后,会判断存储的对象个数。如果大于阈值,将执行扩展。
(3)第一次put的时候,先触发扩容(实例化的HashMap的内部数组默认为null,即不实例化。第一次调用put方法时,会先启动初始化和扩展,长度为16.),然后保存数据,然后判断是否需要扩展;如果不是第一次,则不会再次初始化。直接保存数据,然后判断是否需要扩容;
HashMap的容量有一个上限,必须小于1<<30,即1073741824。如果容量超过这个数,就不再增加,阈值设置为Integer.MAX_VALUE([公式] ,即永远不会超过阈值)。
源码:jdk7中new Hashmap()时初始化对象,而jdk8中new Hashmap()并没有初始化对象,而是在put()方法中通过判断对象是否为空,如果为Null初始化通过调用 resize() 对象。
public V put ( K key , V value ) {
return putVal ( hash ( key ) , key , value , false , true ) ;
}
/**
* 实现 Map.put 及相关方法
*
* @param 哈希键值计算传递下标
* @参数键
* @参数值
* @param onlyIfAbsent true 只在值为空时存储数据,false 存储数据
* @param 驱逐
* @return 返回覆盖的值,如果没有覆盖则返回 null
*/
final V putVal ( int hash , K key , V value , boolean onlyIfAbsent , boolean evict ) {
//声明入口数组 object tab[]: current Entry[] object
Node < K , V > [ ] tab ;
//声明入口对象p:这里表示存储的单个节点
Node < K , V > p ;
//n:为当前Entry对象的长度//i:为下标
int n , i为当前存放对象节点的位置;
/**
* 过程判断
* 1.如果当前Node数组(tab)为空,直接创建(通过resize()创建),创建后设置当前长度为n
* 2.如果要存储对象的Node节点为空,直接在对象存储位置新建Node,直接存储值
* 3.存储的Node数组不为空,存储的下标节点Node不为空(Node节点为链表的第一个节点)
* 1) 比较存储在链表第一个节点的对象和当前对象是否为同一个对象,如果是则覆盖并返回原值
* 2) 如果不分两种情况
* (1)存储节点为红黑树节点结构,调用putTreeVal()方法直接插入数据
* (2)如果不是红黑树,则表示为链表,遍历
* A.如果存储的链表的下一个位置为空,先直接存储该值,保存后再检查当前存储的位置是否大于链表的第8位
* 一种。如果大于,则调用treeifyBin方法判断是否对链表进行扩展或转换为红黑树(大于8且总数据量大于64,则转红黑,否则为数组将扩大)
* b。当前保存的位置链表长度不大于8,则保存成功,终端循环操作。
* B. 如果链表中存储的下一个位置有值,且该值与存储的对象“相同”,则直接覆盖,返回原值
* 以上两种情况AB执行后,判断返回的原始对象是否为空,如果不是,则返回原始对象的原始值
* 上述123三种情况中,如果原值没有被覆盖,则表示新增了新存储的数据。数据存储后,size+1,然后判断当前数据量是否大于阈值。
* 如果大于阈值,则扩容。
*
/ if ( ( tab = table ) == null || ( n = tab.length ) == 0 )
n = ( tab = resize ( ) ) 。 _ 长度;if ( ( p = tab [ i = ( n - 1 ) & hash ] ) == null
)
tab [ i ] = newNode ( hash , key , value , null ) ;
否则 {
节点< K , V > e ; ķķ ;
if ( p . hash == hash &&
( ( k = p . key ) == key || ( key != null&&键等于( . k ) ) ) )
e = p ;
else if ( p instanceof TreeNode )
//直接将数据存入红黑树
e = ( ( TreeNode < K , V > ) p ) . putTreeVal (这,选项卡,哈希,键,值);
else {
for ( int binCount = 0 ; ; ++ ; binCount ) {
if ( ( e = p . next ) == null ) {
p . next = newNode (哈希,键,值, null ) ;
如果 ( binCount >= TREEIFY_THRESHOLD - 1 ) //-1 for 1st
treeifyBin ( tab , hash ) //该方法判断是扩展还是需要将链表转换为红黑树break ;}
if ( e . Hash == hash &&
( ( k = e . Key ) == key || ( key ! = null && key . Equals ( k ) ) ) )
;
p = e ;
}
}
if ( e != null ) { //key
V oldValue = e的现有映射。价值;if ( ! onlyIfAbsent || oldValue == null )
e . 价值=价值;afterNodeAccess ( e ) ; 返回旧值;} }
++ modCount ;
//如果不是替换数据保存,而是新位置保存后,地图大小加1,然后判断容量是否超过阈值,如果超过则扩容
if ( ++ size > threshold )
调整大小( ) ;
afterNodeInsertion (驱逐) ;
返回空值;
}
treeifyBin() 方法判断是否将当前链表展开或转换为红黑树
/**
* 替换给定哈希索引处 bin 中的所有链接节点,除非
* 表太小,在这种情况下调整大小。
* 从链表中指定哈希位置的节点开始,全部替换为红黑树结构。
* 除非整个数组对象(Map集合)的数据量非常小(小于64),这种情况下Map是通过resize()来扩展的,而不是把链表转化为红黑树。
*/
final void treeifyBin ( HashMap . Node < K , V > [ ] tab , int hash ) {
int n , index ; 哈希映射。节点< K , V > e ;
//如果Map为空或者当前存储数据n(可以理解为map的size())个数小于64,然后进行扩容
if ( tab == null|| ( n = tab.Length ) < MIN_TREEIFY_CAPACITY )调整大小( ) ; _ //如果size()大于64,则将存储值的链表转换为红黑树else if ( ( e = tab [ index = ( n - 1 ) & hash ] ) != null ) {
哈希映射。树节点< K ,
V > hd = null , tl = null ;
做 {
哈希映射。TreeNode < K , V > p = replacementTreeNode ( e , null ) ;
如果 ( tl == null )
hd = p ;
否则 {
p 。上一页= tl ;
tl. 下一个= p ;
}
tl = p ;
} while ( ( e = e . next ) != null ) ;
如果 ((标签[索引] =高清) !=空)
高清。树化(选项卡);
}
}
Java 源代码执行此操作:
先根据key计算hashCode值,然后对数组长度减一做AND运算。为什么当 hashmap 数组的初始大小是 2 的幂时,hashmap 效率最高?你可以看到第七个问题。因此,当数组长度为2的n次幂时,不同key计算的索引相同的概率较小,则数据在数组上分布更均匀,查询效率更高.
HashMap中默认的数组大小为16,因为16是2的整数次幂,在数据量较小的情况下可以更好的减少key之间的冲突。存储大容量时,最好预先指定HashMap的大小为2的整数次幂。如果不指定,HashMap的构造方法也会初始化为大于和最接近指定的2次幂价值。
HashMap展开时:必须重新计算原数组中的数组,放入新数组中。这是调整大小。那么我们什么时候应该扩张呢?当 HashMap 中的元素个数超过数组大小 * loadFactor(加载因子)时,数组会被扩展。loadFactor 的默认值为 0.75。
所以一开始,在指定HashMap的大小时,我们把它设置为最接近我们预期的数/0.75,得到最接近并大于他的数(这个数是2的指数倍数),所以我们同时考虑 & 的问题,也避免了调整大小的问题。
如果大家对此比较感兴趣,想了解更多相关知识,可以关注一下极悦的HashMap扩容机制解读,里面有更丰富的知识等着大家去学习,希望对大家能够有所帮助哦。
你适合学Java吗?4大专业测评方法
代码逻辑 吸收能力 技术学习能力 综合素质
先测评确定适合在学习