哈希表 Hashmap
本文中贴出的代码为jdk 1.8
重要参数
1 | /** |
HashMap在new的时候,有3种方式,可以指定初始容量和加载因子,也可以不指定,默认即可。
如果在创建HashMap实例时没有给定capacity、loadFactor则默认值分别是16和0.75。
- 当好多bin被映射到同一个桶时,如果这个桶中bin的数量小于
TREEIFY_THRESHOLD当然不会转化成树形结构存储。 - 如果这个桶中bin的数量大于了
TREEIFY_THRESHOLD,但是capacity小于MIN_TREEIFY_CAPACITY则依然使用链表结构进行存储,此时会对HashMap进行扩容。 - 如果capacity大于了
MIN_TREEIFY_CAPACITY,则会进行树化。
1 | /** |
get
1 | public V get(Object key) { |
- 对 key 取 hash ,定位桶的位置。
- 如果桶为空,返回 null 。
- 判断桶的 first 的 key 是否为查询的 key,是就返回 value。
- 桶的 first 不匹配,则判断 first 是红黑树 or 链表。
- 红黑树就则 getTreeNode。
- 或者链表则遍历判断 next。
如何计算 hash
解决哈希碰撞时,取模是一个常用方式,比如 n%k
当且仅当k为2的整数幂时,n%k==n&(k-1)
通过位运算来进行快速取模,因此必须要求容量为2的整数幂

put
1 | public V put(K key, V value) { |
put 操作复杂一些,要判断容量,是否扩容,还要判断桶中bin数量,来决定是否转换为红黑树或者链表
- 判断当前桶是否为空,空就需要初始化(resize 中会判断是否进行初始化)。
- 根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空表明没有 Hash 冲突就直接在当前位置创建一个新桶。
- 如果当前桶不为空(Hash 冲突),需要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进行赋值及返回。
- 如果当前桶为红黑树,按照红黑树的方式写入数据。
- 如果是链表,就需要将当前的 key、value 封装成一个新节点写入到当前桶的后面(形成链表)。
接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。 - 如果在遍历过程中找到 key 相同时直接退出遍历。
- 如果 e != null 就相当于存在相同的 key,那就需要将值覆盖。
最后判断是否需要进行扩容。
resize
1 | final Node<K,V>[] resize() { |
如何进行 rehash
get方法可以使用位运算快速取模,同样的,我们在扩充HashMap的时候,不需要重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”
因此,resize每次扩容1倍


