HashMap
(1)HashMap是一种散列表,采用(数组 + 链表 + 红黑树)的存储结构;
(2)HashMap的默认初始容量为16(1<<4),默认装载因子为0.75f,容量总是2的n次方;
(3)HashMap扩容时每次容量变为原来的两倍;
(4)当桶的数量小于64时不会进行树化,只会扩容;
(5)当桶的数量大于64且单个桶中元素的数量大于8时,进行树化;
(6)当单个桶中元素数量小于6时,进行反树化;
(7)HashMap是非线程安全的容器;
(8)HashMap查找添加元素的时间复杂度都为O(1);
为什么会用三种数据结构:
数组的查询效率为O(1)->定位是哪个桶(时间复杂度O(1)如果没有冲突那么查询过程完毕),链表的查询效率是O(k)->定位桶内的元素(如果有冲突),红黑树的查询效率是O(log k)->定位桶内的元素(如果有冲突),k为桶中的元素个数(所以当元素数量非常多的时候,转化为红黑树能极大地提高效率)
存储结构
包位置
HashMap位于java.util包
蓝线继承,绿线实现
1 |
|
依赖关系
HashMap继承了AbstractMap,实现了Map,Cloneable,Serializable接口
1 | public class HashMap<K,V> extends AbstractMap<K,V> |
HashMap的默认参数
变量 | 默认值 | 功能 |
---|---|---|
DEFAULT_INITIAL_CAPACITY | 16 | 初始桶的数目 |
MAXIMUM_CAPACITY | $2^{30}$ | 最大容量 |
DEFAULT_LOAD_FACTOR | 0.75 | 负载因子 |
TREEIFY_THRESHOLD | 8 | 将链表转化成红黑树的阈值 |
MIN_TREEIFY_CAPACITY | 64 | 桶树化的阈值(当桶的个数达到64的时候才进行树化) |
Hash表每次会扩容长度为以前的2倍
1 | /** |
桶元素的数据结构(数组的每个元素的数据结构)
每个节点包含四个属性,hash,键,值,和下一个元素的指针
1 | /** |
1 | /* ---------------- Static utilities -------------- */ |
成员变量
变量 | 术语 | 说明 |
---|---|---|
size | 大小 | HashMap的存储大小 |
threshold | 临界值 | HashMap大小达到临界值,需要重新分配大小。 |
loadFactor | 负载因子 | HashMap大小负载因子,默认为75%。 |
modCount | 统一修改 | HashMap被修改或者删除的次数总数。 |
Entry | 实体 | HashMap存储对象的实际实体,由Key,value,hash,next组成。 |
1 | /* ---------------- Fields -------------- */ |
构造函数
1 | /* ---------------- Public operations -------------- */ |
常用方法
1 |
|
get()方法的实现
1 |
|
put方法的实现
(1)计算key的hash值;
(2)如果桶(数组)数量为0,则初始化桶;
(3)如果key所在的桶没有元素,则直接插入;
(4)如果key所在的桶中的第一个元素的key与待插入的key相同,说明找到了元素,转后续流程(9)处理;
(5)如果第一个元素是树节点,则调用树节点的putTreeVal()寻找元素或插入树节点;
(6)如果不是以上三种情况,则遍历桶对应的链表查找key是否存在于链表中;
(7)如果找到了对应key的元素,则转后续流程(9)处理;
(8)如果没找到对应key的元素,则在链表最后插入一个新节点并判断是否需要树化;
(9)如果找到了对应key的元素,则判断是否需要替换旧值,并直接返回旧值;
(10)如果插入了元素,则数量加1并判断是否需要扩容;
1 |
|
resize()方法的实现
(1)如果使用是默认构造方法,则第一次插入元素时初始化为默认值,容量为16,扩容门槛为12;
(2)如果使用的是非默认构造方法,则第一次插入元素时初始化容量等于扩容门槛,扩容门槛在构造方法里等于传入容量向上最近的2的n次方;
(3)如果旧容量大于0,则新容量等于旧容量的2倍,但不超过最大容量2的30次方,新扩容门槛为旧扩容门槛的2倍;
(4)创建一个新容量的桶;
(5)搬移元素,原链表分化成两个链表(原红黑树分成两棵树),低位链表存储在原来桶的位置,高位链表搬移到原来桶的位置加旧容量的位置;
1 | /** |
treeifyBin()与方法实现
(1)从链表的第一个元素开始遍历;
(2)将第一个元素作为根节点;
(3)其它元素依次插入到红黑树中,再做平衡;
(4)将根节点移到链表第一元素的位置(因为平衡的时候根节点会改变);
1 | /** |
putAll的实现
1 | /** |
remove()的实现
(1)先查找元素所在的节点;
(2)如果找到的节点是树节点,则按树的移除节点处理;
(3)如果找到的节点是桶中的第一个节点,则把第二个节点移到第一的位置;
(4)否则按链表删除节点处理;
(5)修改size,调用移除节点后置处理等;
1 | public V remove(Object key) { |
TreeNode的treeify()方法
(1)从链表的第一个元素开始遍历;
(2)将第一个元素作为根节点;
(3)其它元素依次插入到红黑树中,再做平衡;
(4)将根节点移到链表第一元素的位置(因为平衡的时候根节点会改变);
1 | /** |
向红黑树中插入元素putTreeVal()
(1)寻找根节点;
(2)从根节点开始查找;
(3)比较hash值及key值,如果都相同,直接返回,在putVal()方法中决定是否要替换value值;
(4)根据hash值及key值确定在树的左子树还是右子树查找,找到了直接返回;
(5)如果最后没有找到则在树的相应位置插入元素,并做平衡;
1 | /** |
红黑树删除元素removeTreeNode()
(1)TreeNode本身既是链表节点也是红黑树节点;
(2)先删除链表节点;
(3)再删除红黑树节点并做平衡;
1 | /** |
参考文献
1 | 作者:技术见闻 |
1 | 作者:彤哥读源码 |