HashMap作为我们最常用的一个类,而且在面试题中频频出现,让我们来一探究竟吧。
HashMap类图:实现了Map、Cloneable和Serializable类,继承了AbstractMap类。
HashTable维护了一个Entry的数组,初始值为长度为0的空数组,如下:
先看一段简单的代码:
构建函数如下:
默认值定义如下:
由上面的构造函数可知,初始化对象主要设置了HashMap的两个属性:
loadFactor: 加载因子,当HashMap中的个数达到这个比例时,HashMap扩容一倍。
threshold: 是HashMap的阈值,用于判断是否需要调整HashMap的容量。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
构造方法中设定了初始容量必须是2的倍数 (while循环)
HashMap的put方法:
在put方法中,先判断了table是否为空,如果为空,则初始化一个threshold大小的数组;
如果key为null,则放入table数组的第0个元素中;
否则,根据key的hash码找到对应数组中的索引,并判断该位置是否有相同key的元素,如果key相同,则替换value值,不同的话,modCount++,并调用addEntry新增了一个元素。
addEntry中判断了table的元素数量是否超过了threshold值,超过了将重新将table扩容两倍,并将之前的table元素映射到新的table中,再插入新的元素。
由上图可知,插入新元素时,如果当前索引上有entity(之前已经判断了key的hash值相同且key相同时会覆盖value,这里出现了两个不同的key有相同的hash值得情况),则新元素的next指向了旧的entity,即如下图
下面我们再来看看HashMap的get方法:
主要是调用了一个getEntry方法,如下:
方法中可以看到,getEntry根据key的hash值得到了它在table中的索引,并且循环调用了e.next获取到匹配到key的元素(即关键还是key.equals(k)判断,hash值只是定位到它在table中的索引)。
Entity是一个单向列表结构,这样在key的hash值冲突(不同的key的hash值算出了相同的index)时,采用链表式的存储,由于这种实现方式,无论key中存储了何种类型的值,只要实现了相应的equals方法,都可以通过hash值快速得到其在table中的位置。这也是HashMap效率较高的原因。
经过上面的分析,也可以初步理解了为什么HashMap不能支持多线程,以下是我个人的一些想法,具体的原因后续再去探讨
原因1:HashMap会动态分配大小,多线程的情况下可能会出现两个线程同时重新分配大小的问题
原因2:由于HashMap的key值采用hash函数得到其相应的索引,并构造成为单向链表,如果多线程的情况下,可能会出现值覆盖的问题。
还有等等其它问题
学习整理阶段,希望对大家有用,有误之处,希望评论里指出哦