本期,边肖将为大家带来关于如何分析HashMap扩展机制源代码的信息。文章内容丰富,从专业角度进行分析和描述。看完这篇文章,希望你能有所收获。
在详细看源代码之前,我们先简单说一下HashMap的底层数据结构。
1.HashMap底部的数据结构是一个数组链表红黑树。
2.我们首先需要了解HashMap底部的两个变量。
2-1:负载系数:默认值为0.75,这是反复测试后最合适的值。
2-2-2:阈值:当地图中的数据大于此阈值时,容量将会扩大。
注:了解有关负载系数的更多信息,请单击此处。
2.现在我们来看看HashMap的构造方法。
HashMap可以通过四种方式构建。
1.空参数构造方法。此时,加载因子默认为0.75,不会创建任何空间。
阈值为0
数组为空。
public HashMap(){ 0
this . LoAdfactor=DEFAULT _ LoAd _ FACTOR;//默认所有其他字段
}
2.给定初始容量,这个构造函数将直接调用第三个构造函数。
阈值已经有值
数组为空。
公共HashMap(int initial capacity){ 0
这个(initialCapacity,DEFAULT _ LOAD _ FACTOR);
}
3.给定初始化大小和加载因子。
1.实际上,不建议修改默认加载因子。当然,除非你很了解逻辑,并且找到了适合你的项目的负载系数。
2.首先判断你给的初始化容量是否合法,如果合法,就用这个初始化容量来计算阈值。
阈值已经有值
数组为空。
公共HashMap(int initialCapacity,float load factor){ 0
if(初始电容0)
引发新的IllegalArgumentException('非法的初始容量: '
initial capacity);
if(initial CAPACITY max _ CAPACITY)
initial CAPACITY=max _ CAPACITY;
if(load factor=0 | | float . isnan(load factor))
引发新的IllegalArgumentException('非法加载因子: '
load FactOr);
this.loadFactor=loadFactor
this . threshold=tablesize for(initial capacity);
}
4.将贴图作为参数传递,加载因子将适应默认值0.75。将其他地图转换为HashMap
阈值已经有值
数组也不是空的
公共HashMap(地图m){ 0
this . LoAdfactor=DEFAULT _ LoAd _ FACTOR;
putMapEntries(m,false);
}
最终无效putMapEntries(Map m,布尔驱逐){ 0
int s=m . size();
if(s)0
if (table==null) { //pre-size
浮子ft=((浮子)s/LoAdFactor)1.0F;
int t=((ft(float)max _ CAPACITY)?
(int)ft : max _ CAPACITY);
if (t阈值)
threshold=tablesize for(t);
}
否则如果阈值
resize();
(地图。条目e :m . EntrySet()){ 0
k key=e . GetKey();
v value=e . GetValue();
putVal(hash(key),key,value,false,驱逐);
>
}
}
}
3、让我们看看是HashMap是怎么进入扩容的
3-1:我们先从 put() 这个方法说起
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
这个put方法底层是调用了一个叫 putVal 的方法,但是在这之前我们有必要看一下hash()这个方法。
直接使用 对象.hashCode(), 可能会出现重复,所以这个hash是对生成的hashcode进行一下扰乱,让其重复性更低。
从这里也可以看到,HashMap只允许一个null键
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
3-2:下面我们看一下这个putVal方法
putVal源码:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
这个源码看起来还是有点复杂的,考虑到很多同学可能和我一样数据结构并不是太好。我把它简化一下,提取里面的思想便于理解
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean
evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0){
// 当数据为null或者长度为0的时候进行扩,并发扩后的长度返回给n(前面说了hashMap底层最开始是个数组)
n = (tab = resize()).length;
}
// 之前可能有同学有疑问,hashcode那么长,为啥默认HashMap数组默认长度是16。其实最后的下标是经过处理的 (n - 1) &
hash
if ((p = tab[i = (n - 1) & hash]) == null){
// 如果当前数组的下标,并没有数据,也就是说当前添加的数据是第一个,那就直接加入进去就好了。不需要排序啥的
tab[i] = newNode(hash, key, value, null);
}
else {
// 找到了数据下标,并且里面的已经有数据了,
// 这里就要找到当前数据的位置属于那里并加入进去,
// 还要判断当前长度是否大于我们设置的长度,大于就要把链转化成红黑树便于查找
}
++modCount;
// 判断当前长度是否大于需要扩的长度,其实也好理解,数组是可以装满的,但是链不可能满呀,但是长度超过一定的长度的时候链的性能就会很差了
if (++size > threshold)
resize();
// 节点插入后的操作,目前这个没有任何实现,里面是个空方法
afterNodeInsertion(evict);
return null;
}
3-3:总结进入扩容的两种情况
添加一个数据的时候,底层数组为空的时候
添加一个数据结束后,判断当前数据个数是否大于threshold (需要扩容)的大小,大于就进行扩容
注:因为数据是具体添加到数组里面的链表,所以不存在数组越界情况。
4、具体看一下扩容代码
扩容源码:
final Node[] resize() {
Node[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}郑州专业妇科医院 http://fk.zyfuke.com/
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft <
(float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node[] newTab = (Node[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode)e).split(this, newTab, j, oldCap);
else { // preserve order
Node loHead = null, loTail = null;
Node hiHead = null, hiTail = null;
Node next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
同样的把源码进行一下简单的分享,去除复杂的内容
// 这个扩容方法就是
// 1、找到新的容量大小和新的threshold大小
// 2、把旧的数据全部复制到新的数组中去
final Node[] resize() {
Node[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 非第一次扩容
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 使用初始化容量或初始化容量+初始化加载因子参数的构造方法,第一次进入扩容
else if (oldThr > 0){
newCap = oldThr;
}
// 使用空参构造方法第一次扩容进入,使用参数为map的构造方法,第一次也会进入这个扩容方法
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 使用初始化容量或初始化容量+初始化加载因子参数的构造方法,第一次进入扩容
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft <
(float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node[] newTab = (Node[])new Node[newCap];
table = newTab;
// 把旧的数据全部复制到新的数组中去
if (oldTab != null) {
//
}
return newTab;
}
5、总结(面试的时候:请你说一下HashMap的扩容):
HashMap底层数据结构是 数组 + 链表 + 红黑树
真正的数据是存储在链表中的,链表的长度是无限的。所以这时候就引入一个变量 threshold
当第一次向map里面添加数据,或添加完数据后的大小,大于 threshold的大小,这时候就会进行扩容
先说一下非第一次扩容,这个相对简单点
1、如果当前的容量大小,大于等于HashMap规定的最大容量的话,直接让threshold等于Integer的最大值,就可以了。
2、一般情况当前数组长度是不会大于最大值的,所以这时候新的数组长度等于旧数组的2倍。如果新的数组长度小于HashMap规定的最大值,并且旧的数组长度也大于等于HashMap规定的默认大小容量大小(16),那么threshold扩大2倍,否则不变
非第一次扩容
1、HashMap,有四个构造方法。空参构造方法的threshold变量是0,其它构造方法threshold都有初始值。
2、当旧的threshold大于0的时候,新的数组容量大小就等于旧的threshold大小。新的threshold大小等于加载因子新的数组大小。
3、当旧的threshold不大于0的时候,新的数组大小就等于默认的大小(16),新的threshold大小,就等于默认的容量大小默认的加载因子大小
上述就是小编为大家分享的如何进行HashMap扩容机制源码分析了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/138376.html