详解 HashMap 源码解析(下)
上文介绍了
HashMap
整体介绍了一下数据结构,主要属性字段,获取数组的索引下标,以及几个构造方法。本文重点讲解元素的添加
、查找
、扩容
等主要方法。
添加元素
put(K key, V value)
首先算出 key 的哈希码,调用hash
方法,获取到hash
值。
调用 putVal()
首先判断哈希数组
table
是否为null
,如果为null
,就扩容。(n - 1) & hash
对应的下标是否存在节点。不存在节点,就创建新的节点并赋值。
存在节点
节点 key 值是否相等,相等就替换
value
。是否为红黑树,添加数据到红黑树中。
上面都不符合,就是普通链表,遍历链表,如果链表存在相同
key
就替换,否则在链表最后添加数据。
流程图:
putAll(Map<? extends K, ? extends V> m)
putAll
是将集合元素全部添加到HashMap
中,putAll
调用了putMapEntries
方法,putMapEntries
先判断是否需要扩容,然后遍历元素,调用putVal
添加元素,下面是添加元素代码:
获取数据
get(Object key)
通过key
找到哈希表的中Node
节点的value
值。
首先使用hash
方法算出哈希值,然后再调用getNode()
获取数据:
判断哈希数组是否不为
null
并且数组下标(n - 1) & hash
处不为null
,如果都有值,就查询首节点first
,否则返回null
。找到首节点,匹配上相等的
hash
和key
,返回首节点。链表有多个元素,是否为红黑树
是红黑树,在红黑树查找
不是红黑树,就遍历普通链表,直到匹配到相同的
hash
和key
值。
流程图:
resize 扩容
当哈希数组为null
,或元素个数超过了阈值,就调用resize
扩容方法:
原容量是否为空
不为空,是否大于最大容量
大于最大容量,不做扩容
小于最大容量,并且大于默认容量 16。阈值和容量都翻倍。
为空,原阈值大于零, 就阈值赋值给新容量。
原容量和原阈值都小于等于零,赋值默认容量 16 和默认阈值 12。
做完阈值和容量的赋值之后,遍历数组。
有值,是否只有一个元素,如果是就放入新数组
n-1&hash
下标处。如果是红黑树就拆分红黑树。
上面两个都不符合就是普通链表。
遍历链表,如果
hash&数组原长度
为 0放在数组
原下标
处。不为零,放在
原位置+原数组长度
处。
流程图:
总结
本文主要讲解了元素的添加
、查找
、扩容
等主要方法,其中添加
和查询
都需要先获取数组的下标,然后进行对应的操作。
put
添加
首次添加数据需要对数组进行扩容。
对应下标是否有值
没有值,直接赋值
有值
key
一致,替换value
值。key
不一致是红黑树,在红黑树添加数据。
不是红黑树,就是链表,遍历链表,存在相同节点 key,替换。否者添加在链表的尾部。
get
查询
下标是否有值
没有值,返回
null
有值*
hash
和key
相等的话,返回节点。是否是多链表。
不是,返回
null
。是的话,是否是红黑树。
红黑树,在红黑树中查找
否则就是普通链表,遍历链表知道匹配到相同的
hash
和key
。
resize 扩容
容量大于零
大于最大容量值,不再扩容。
介于最大和默认容量之间,阈值和容量都翻倍。
初始化的时候,设置默认容量和默认阈值。
遍历原数组
节点有值,并且只有一个值,赋值给新数组
n-1&hash
处。如果是红黑树,就拆分红黑树。
以上都不符合,就是普通链表,遍历链表。因为数组长度都是 2 的幂次方,扩容后元素的位置*要么是在原位置,要么是在原位置再移动 2 次幂的位置。
hash&与运算原数组长度,等于 0,存在原来的位置。
不等于 0,就存放下标原来位置+原数组长度位置处。
评论