目录
前言
做过java开发的同学应该都知道HashMap是使用数组+链表的数据结构进行数据的存储,后来演变到jdk8之后,为了提高插入和查询效率,在插入的数据超过某个阈值之后,会将数组+链表的结构转化成数组+红黑树的数据结构,也即如下图:
最近闲来无事,准备将HashMap插入数据的过程,以及其数据结构的转化过程,再回顾一下,故写此篇文章,以是记录。
1、初始化
对于HashMap,在我们初始化以及插入第一条数据的时候,内部发生了什么事情呢?
1.1、初始化
当我们执行了如下一个初始化操作:
HashMap<String,String> map = new HashMap<String,String>();
从源代码层面看,只是对内部一个叫做loadFactor的属性进行了初始化0.75,那么这个loadFactor参数是做什么用的呢?
我们前言中说过,HashMap是用的一个数组+链表的形式进行数据的存储,那么这个数组的长度是如何决定的呢?什么时候要对数组进行扩充,什么时候又要对数组进行缩减呢?那么这个判断依据就是根据loadFactor来决定的。具体来说就是:当插入数据的个数 / 数组的长度 >= loadFactor时,我们就要对数组的长度进行扩展啦。因为当插入的数据越来越大的时候,在数组长度不变的情况下,hash冲突会越来越严重,数组节点下的数据就会越来越多,进而导致查询效率越来越差。
另外需要说明的是:在扩展数组的时候,数组的长度都是2^n个,在初始化的时候,数组长度是2^4 = 16,每一次扩展,都增加一倍。
1.2、插入第一条数据
当我们执行如下操作,插入第一条数据的时候,hashmap的内部又发生什么事情了呢?
map.put("chenza","30")
第一步:先计算字符串"chenza"的hash值,作为判断插入数据哪个节点的依据。
第二步: 初始化数组表table,如上所述,第一次初始化的时候,数组表table的长度是2^4 = 16
第三步:用"chenza"的hash值 和 数组表table的长度(n - 1) 做"且"运算,判断当前数据应该插入到数组的哪个位置。(注:做且运算的原因保证插入的位置是[0,n-1]之间,不会数组越界)
第四步:size字段+1,并判断是否超阈值threshold,如果超过阈值threshold,则进行数组table的扩展,且以后每插入一条数据,都会进行阈值判断。(注:这里的threshold = 当前的数组table长度 * loadFactor,与上节描述一致)
代码执行逻辑如下:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true); //计算key的hash值
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //初始化数组table,长度16
if ((p = tab[i = (n - 1) & hash]) == null) //计算数据应该插入的位置
tab[i] = newNode(hash, key, value, null); //在数组第i个位置插入数据
else {
....... //暂时省略
}
++modCount;
if (++size > threshold) //size记录插入的数据的个数,并判断是否超阈值。
resize();
......
至此,我们的初始化及第一条数据插入已经完成。从上所述,我们得知,我们一共做了三方面的工作:
(1)、初始化参数loadFactor
(2)、完成数组table的初始化,将其长度置为2^4 = 16
(3)、将数据插入到数组table对应的节点上。
到目前位置,我们的HashMap的结构如下:
2、数组 + 链表
现在,在我们的HashMap中,已经有了一条数据,那么我们接下来插入第二条、第三条...第n条数据,看看HashMap中到底经历了怎样的过程。
2.1、插入数据:没有hash冲突
何为hash冲突,在HashMap中所说的hash冲突,指的是:i = hash(key) & (n - 1)之后,数组table在i处已经有数据,代表两个不同的key需要放到数组的同一个节点下。
&n

1485

被折叠的 条评论
为什么被折叠?



