基于前面的Java并发 - J.U.C并发容器类Map - HashMap简单分析,我们接着来对HashMap的相关知识进行一个补充。
HashMap插入数据的思路
- HashMap插入数据的思路
- 对应的数组下标的元素为空,直接放
- key 是不是同一个key,如果是则进行value的替换
- key 不是同一个,且数组有元素,则判断是链表还是树
- 是链表:判断是否达到了树的要求,没达到就挂链表,否则变成红黑树
- 是红黑树:挂到红黑树后边的节点中去
当 链表长度==8
的时候,会自动转换成红黑树
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
但是即便如此,就算加入了红黑树,如果数组长度一定的情况下,大量数据放入map中,发生hash碰撞的几率就越大,那么红黑树必然也会越来越庞大,照样会影响查询的效率。本质来说,这是数组的长度限制导致的,所以数组的扩容很是必要。
HashMap的扩容
-
数组的扩容:当数组满足空间被占用的75%的时候(默认的填充因子是0.75f),会进行扩容,数组容量和填充因子都向左平移1位
<< 1
即呈2倍扩容。
此时会进行创建新的数组,然后再对旧的数组及挂在在元素下的链表(或红黑树)进行迁移。
迁移的情況:
- 遍历老数组,如果有元素且只有一个元素,那么直接进行迁移
- 数组的元素下是一个链表,那么就遍历链表,取模然后插入新的数组
- 数组的元素下是一个红黑树,那么就打散树之后再遍历树的元素,取模然后插入新的数组中
HashMap的总结
这部分在Java并发 - J.U.C并发容器类Map - HashMap简单分析 也有描述:
- HashMap是数组+链表构成的,JDK1.8之后,加入了红黑树.
- HashMap默认数组初始化大小为
16
,如果瞎设置数字,它会自动调整成2 的倍数
. - HashMap链表在长度为
8
之后,会自动转换成红黑树,数组扩容之后,会打散红黑树,重新设置. - HashMap扩容变化因子是
0.75
,也就是数组的3/4被占用之后,开始扩容。 - 在第一次调用PUT方法之前,HashMap是没有数组也没有链表的,在每次put元素之后,开始检查(生成)数组和链表.
与HashTable的比较
大体是一致的,只是暴露的方法里边多了 synchronized
修饰符,确保了线程安全,但是不推荐使用,主要是在多线程调用的时候,一个同步的修饰符会导致性能下降,与Map设计的初衷提升访问速度相违背。所以对于HashTable只要知道它线程安全的原因是加入了synchronized修饰符
- get 方法中加入了synchronized修饰符
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~
网友评论