本文将分析put(),resize(),get()和remove()方法的源码

putval()方法

  大致步骤:计算key的hash值;根据hash值计算数组下标;判断下标处是否有节点,无节点则直接插入,有则根据是链表还是红黑树进行对应操作。
  这里需要注意的是如果插入链表后长度达到了8,且table数组长度达到64,则会对链表进行树化转为红黑树;如果长度达到了8,且table数组长度小于64,则会进行resize()对table数组扩容,目的是将链表各节点rehash分成高低位两个链表以减少链表长度。
 1 // 内部节点数组
2 transient Node<K,V>[] table;
3
4 // 对外界提供的
5 public V put(K key, V value) {
6 return putVal(hash(key), key, value, false, true);
7 }
8 // 内部实现
9 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
10 boolean evict) {
11 Node<K,V>[] tab; Node<K,V> p; int n, i;
12 // 如果节点数组未初始化或为空,则进行初始化操作
13 if ((tab = table) == null || (n = tab.length) == 0)
14 n = (tab = resize()).length;
15 // 如果根据hash值得到的数组对应位置还没有元素,则直接插入
16 if ((p = tab[i = (n - 1) & hash]) == null)
17 tab[i] = newNode(hash, key, value, null);
18 // 如果有元素
19 else {
20 // hash冲突
21 Node<K,V> e; K k;
22 // 如果hash值和key值相同,则直接替换原值
23 if (p.hash == hash &&
24 ((k = p.key) == key || (key != null && key.equals(k))))
25 e = p;
26 // 如果节点已经是树节点,进行树模式的插入
27 else if (p instanceof TreeNode)
28 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
29 else {
30 // 如果还是链表,则遍历链表插入数据
31 for (int binCount = 0; ; ++binCount) {
32 // 这里采用的是尾插法,如果遍历链表过程中没发现key相同节点,则在链表尾部新建节点
33 if ((e = p.next) == null) {
34 p.next = newNode(hash, key, value, null);
35 // 如果链表的长度达到了8,且数组cap大小>=64则转为红黑树
36 // 如果链表长度达到了8,但数组cap大小<64则resize()扩容
37 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
38 treeifyBin(tab, hash);
39 break;
40 }
41 // 如果存在key相同的节点,则不插入,退出循环
42 if (e.hash == hash &&
43 ((k = e.key) == key || (key != null && key.equals(k))))
44 break;
45 p = e;
46 }
47 }
48 if (e != null) { // existing mapping for key
49 V oldValue = e.value;
50 if (!onlyIfAbsent || oldValue == null)
51 e.value = value;
52 afterNodeAccess(e);
53 return oldValue;
54 }
55 }
56 ++modCount;
57 // 数量超过阈值,进行一次扩容操作
58 if (++size > threshold)
59 resize();
60 afterNodeInsertion(evict);// 回调
61 return null;
62 }

resize()方法

大致步骤:
  判度当前容量cap和当前阈值thr是否符合扩容条件(翻倍扩容后不超过int最大值)并更新cap和thr的值;
  如果可以扩容,则根据新cap新建一个数组;
  对原数组进行遍历,复制元素到新数组,有三种情况:无后继节点;链表;红黑树。具体复制细节可以参考作者另一篇博客:https://www.cnblogs.com/ningbing/p/14599571.html
 1 final Node<K,V>[] resize() {
2 // 获取原有table
3 Node<K,V>[] oldTab = table;
4 int oldCap = (oldTab == null) ? 0 : oldTab.length;
5 int oldThr = threshold;
6 // 新容量、新阈值
7 int newCap, newThr = 0;
8 if (oldCap > 0) {
9 // 如果原有容量超过设定最大容量,则不进行扩容
10 if (oldCap >= MAXIMUM_CAPACITY) {
11 threshold = Integer.MAX_VALUE;
12 return oldTab;
13 }
14 // 如果原容量翻倍后不超过最大容量且原容量超过16,则进行翻倍扩容
15 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
16 oldCap >= DEFAULT_INITIAL_CAPACITY)
17 newThr = oldThr << 1; // double threshold
18 }
19 else if (oldThr > 0) // 使用阈值初始化容量,对应的是初始化hashmap带了容量cpacity参数。
20 newCap = oldThr;
21 else { // zero initial threshold signifies using defaults
22 // 原容量和阈值都<=0,则用默认值初始化,默认容量16,负载因子0.75,阈值12,对应的是hashmap没带参数初始化。
23 newCap = DEFAULT_INITIAL_CAPACITY;
24 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
25 }
26 if (newThr == 0) {
27 // 如果新阈值为0则赋值为新容量*负载因子(默认是0.75)
28 float ft = (float)newCap * loadFactor;
29 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
30 (int)ft : Integer.MAX_VALUE);
31 }
32 threshold = newThr;// 更新阈值
33 // 基于新容量重新实例化一个node数组
34 @SuppressWarnings({"rawtypes","unchecked"})
35 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
36 table = newTab;// 更新数组
37 if (oldTab != null) {
38 // 遍历数组的每个节点元素
39 for (int j = 0; j < oldCap; ++j) {
40 Node<K,V> e;
41 // 如果节点不为空
42 if ((e = oldTab[j]) != null) {
43 oldTab[j] = null;// 将原数组节点指向空
44 // case1:节点只有一个元素,直接根据hash值计算新位置
45 if (e.next == null)
46 newTab[e.hash & (newCap - 1)] = e;
47 // case2:节点为红黑树节点,进行红黑树的复制操作
48 else if (e instanceof TreeNode)
49 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
50 // case3:节点为链表节点,进行链表的赋值操作
51 else { // preserve order
52 // 低位Node链表头节点和尾节点
53 Node<K,V> loHead = null, loTail = null;
54 // 高位Node链表头节点和尾节点
55 Node<K,V> hiHead = null, hiTail = null;
56 Node<K,V> next;
57 // 遍历原链表,拆分成低位链表和高位链表
58 do {
59 next = e.next;
60 // 如果是在原位置,则加入低位链表
61 if ((e.hash & oldCap) == 0) {
62 if (loTail == null)
63 loHead = e;
64 else
65 loTail.next = e;
66 loTail = e;
67 }
68 else {
69 // 如果不在原位置,加入高位链表
70 if (hiTail == null)
71 hiHead = e;
72 else
73 hiTail.next = e;
74 hiTail = e;
75 }
76 } while ((e = next) != null);
77 // 如果低位链表不为空
78 if (loTail != null) {
79 // 尾部节点赋空并将头部节点放入数组指定位置
80 loTail.next = null;
81 newTab[j] = loHead;
82 }
83 // 如果高位链表不为空
84 if (hiTail != null) {
85 // 尾部节点赋空并将头部节点放入数组指定位置
86 hiTail.next = null;
87 newTab[j + oldCap] = hiHead;
88 }
89 }
90 }
91 }
92 }
93 return newTab;
94 }

TreeNode split()方法:map扩容时红黑树的拆分+重新存储

 1 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
2 // 获取自身树节点
3 TreeNode<K,V> b = this;
4 // Relink into lo and hi lists, preserving order
5 // 低位TreeNode链表的头尾节点
6 TreeNode<K,V> loHead = null, loTail = null;
7 // 高位TreeNode链表的头尾节点
8 TreeNode<K,V> hiHead = null, hiTail = null;
9 // 低位链表节点数量、高位链表节点数量
10 int lc = 0, hc = 0;
11 for (TreeNode<K,V> e = b, next; e != null; e = next) {
12 next = (TreeNode<K,V>)e.next;
13 // 这步操作不是多余的,在e为低位或高位链表最终尾节点时起到赋空作用
14 e.next = null;
15 // 如果仍然在原位置,则加入低位链表
16 if ((e.hash & bit) == 0) {
17 if ((e.prev = loTail) == null)
18 loHead = e;
19 else
20 loTail.next = e;
21 loTail = e;
22 ++lc;//低位链表数量+1
23 }
24 else {
25 // 如果是在新的位置(原索引值+oldcap),加入高位链表
26 if ((e.prev = hiTail) == null)
27 hiHead = e;
28 else
29 hiTail.next = e;
30 hiTail = e;
31 ++hc;// 高位链表数量+1
32 }
33 }
34 // 低位链表不为空
35 if (loHead != null) {
36 // 低位链表数量不超过6,则深拷贝低位链表并将新链表头部放入数组
37 if (lc <= UNTREEIFY_THRESHOLD)
38 tab[index] = loHead.untreeify(map);
39 else {
40 tab[index] = loHead;
41 // 如果高位链表为空,说明全部元素都在低位链表中,因为原链表已经是树化的了,所以不用再转为红黑树
42 if (hiHead != null) // (else is already treeified)
43 loHead.treeify(tab);
44 }
45 }
46 // 高位链表不为空
47 if (hiHead != null) {
48 // 高位链表数量不超过6,则深拷贝高位链表并将新链表头部放入数组
49 if (hc <= UNTREEIFY_THRESHOLD)
50 tab[index + bit] = hiHead.untreeify(map);
51 else {
52 tab[index + bit] = hiHead;
53 // 如果低位链表为空,说明全部元素都在高位链表中,因为原链表已经是树化的了,所以不用再转为红黑树
54 if (loHead != null)
55 hiHead.treeify(tab);
56 }
57 }
58 }

get()方法:

大致步骤:
  根据key的hashcode计算出hash值(高16位异或低16位填入低16位);
  然后将hash值&(cap-1)计算出节点数组下标;
  如果没有元素,返回null,有元素则查找链表或查找红黑树判断是否有equals的节点。
 1 public V get(Object key) {
2 Node<K,V> e;
3 return (e = getNode(hash(key), key)) == null ? null : e.value;
4 }
5
6 final Node<K,V> getNode(int hash, Object key) {
7 Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
8 // 如果节点数组不为空且数组长度不为0且hash值计算出下标有元素,则继续判断
9 if ((tab = table) != null && (n = tab.length) > 0 &&
10 (first = tab[(n - 1) & hash]) != null) {
11 // 如果第一个节点就是要找的,直接返回
12 if (first.hash == hash && // always check first node
13 ((k = first.key) == key || (key != null && key.equals(k))))
14 return first;
15 // 后继有节点
16 if ((e = first.next) != null) {
17 // 如果是树,则根据hash值和key对红黑树查找
18 if (first instanceof TreeNode)
19 return ((TreeNode<K,V>)first).getTreeNode(hash, key);
20 // 如果是链表,则遍历各个节点
21 do {
22 if (e.hash == hash &&
23 ((k = e.key) == key || (key != null && key.equals(k))))
24 return e;
25 } while ((e = e.next) != null);
26 }
27 }
28 return null;
29 }
30
31 // 树的查找
32 final TreeNode<K,V> getTreeNode(int h, Object k) {
33 // 有根节点从根节点开始找,没根节点从first节点开始找
34 return ((parent != null) ? root() : this).find(h, k, null);
35 }
36 // 红黑树查找
37 final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
38 // 获取调用的节点(根节点或first节点)
39 TreeNode<K,V> p = this;
40 do {
41 int ph, dir; K pk;
42 TreeNode<K,V> pl = p.left, pr = p.right, q;
43 // 当前节点hash值比待找key的hash值大,则进入左子树
44 if ((ph = p.hash) > h)
45 p = pl;
46 // 当前节点hash值比待找key的hash值小,则进入右子树
47 else if (ph < h)
48 p = pr;
49 // 相同,则比较当前节点是否是待找节点
50 else if ((pk = p.key) == k || (k != null && k.equals(pk)))
51 return p;
52 else if (pl == null)
53 p = pr;
54 else if (pr == null)
55 p = pl;
56 else if ((kc != null ||
57 (kc = comparableClassFor(k)) != null) &&
58 (dir = compareComparables(kc, k, pk)) != 0)
59 p = (dir < 0) ? pl : pr;
60 else if ((q = pr.find(h, k, kc)) != null)
61 return q;
62 else
63 p = pl;
64 } while (p != null);
65 return null;
66 }

remove()方法:

大致步骤和get()方法类似:
  根据key的hashcode计算出hash值(高16位异或低16位填入低16位);
  然后将hash值&(cap-1)计算出节点数组下标;
  如果没有元素,返回null,有元素则查找链表或查找红黑树判断是否有equals的节点;
  如果找到了节点再根据节点类型和位置执行对应删除操作。
 1 public V remove(Object key) {
2 Node<K,V> e;
3 // 调用removeNode方法进行删除
4 return (e = removeNode(hash(key), key, null, false, true)) == null ?
5 null : e.value;
6 }
7 /**
8 * @param hash hash for key
9 * @param key the key
10 * @param value the value to match if matchValue, else ignored
11 * @param matchValue if true only remove if value is equal (如果为true:只有value也相同才移除key相同的节点)
12 * @param movable if false do not move other nodes while removing (如果为false:在删除节点时不能移除其它节点)
13 * @return the node, or null if none
14 */
15 final Node<K,V> removeNode(int hash, Object key, Object value,
16 boolean matchValue, boolean movable) {
17 // p是要删除节点的父节点(如果是链表结构)
18 Node<K,V>[] tab; Node<K,V> p; int n, index;
19 // 如果table数组不为空且hash对应索引位置有元素
20 if ((tab = table) != null && (n = tab.length) > 0 &&
21 (p = tab[index = (n - 1) & hash]) != null) {
22 Node<K,V> node = null, e; K k; V v;
23 // 如果第一个node的hash值和key都与输入参数相同,则为要找的目标
24 // 先比较hash值是为了先用简单条件过滤,equals方法的复杂度要比hash值大多了
25 if (p.hash == hash &&
26 ((k = p.key) == key || (key != null && key.equals(k))))
27 node = p;
28 // 后继有节点
29 else if ((e = p.next) != null) {
30 // 如果是树化的,通过hash值和key对红黑树进行查找
31 if (p instanceof TreeNode)
32 node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
33 // 如果是链表,则遍历各个节点
34 else {
35 do {
36 if (e.hash == hash &&
37 ((k = e.key) == key ||
38 (key != null && key.equals(k)))) {
39 node = e;
40 break;
41 }
42 p = e;
43 } while ((e = e.next) != null);
44 }
45 }
46 // 如果找到了待删除的节点(node不为null,存在)
47 if (node != null && (!matchValue || (v = node.value) == value ||
48 (value != null && value.equals(v)))) {
49 // 如果是树化的,删除红黑树节点
50 if (node instanceof TreeNode)
51 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
52 // node=p,说明首节点就是待找节点
53 else if (node == p)
54 tab[index] = node.next;
55 // 将待找节点node的父节点p指向node下一个节点。之后node没有任何对象指向它,会被垃圾回收器回收
56 else
57 p.next = node.next;
58 ++modCount;
59 --size;
60 afterNodeRemoval(node);
61 return node;
62 }
63 }
64 return null;
65 }

HashMap方法源码分析的更多相关文章

  1. Java split方法源码分析

    Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...

  2. invalidate和requestLayout方法源码分析

    invalidate方法源码分析 在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewG ...

  3. Linq分组操作之GroupBy,GroupJoin扩展方法源码分析

    Linq分组操作之GroupBy,GroupJoin扩展方法源码分析 一. GroupBy 解释: 根据指定的键选择器函数对序列中的元素进行分组,并且从每个组及其键中创建结果值. 查询表达式: var ...

  4. HashMap的源码分析与实现 伸缩性角度看hashmap的不足

    本文介绍 1.hashmap的概念 2.hashmap的源码分析 3.hashmap的手写实现 4.伸缩性角度看hashmap的不足 一.HashMap的概念 HashMap可以将其拆分为Hash散列 ...

  5. HashMap的源码分析

    hashMap的底层实现是 数组+链表 的数据结构,数组是一个Entry<K,V>[] 的键值对对象数组,在数组的每个索引上存储的是包含Entry的节点对象,每个Entry对象是一个单链表 ...

  6. Java——HashMap底层源码分析

    1.简介 HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap 最多只允许一条记录的key为 nu ...

  7. Java中HashMap的源码分析

    先来回顾一下Map类中常用实现类的区别: HashMap:底层实现是哈希表+链表,在JDK8中,当链表长度大于8时转换为红黑树,线程不安全,效率高,允许key或value为null HashTable ...

  8. jQuery实现DOM加载方法源码分析

    传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... }  但 ...

  9. jQuery.extend()方法和jQuery.fn.extend()方法源码分析

    这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子: html代码如下: <!doctype html> ...

随机推荐

  1. JSP、EL表达式、JSTL标签库干货(建议收藏)

    JSP(Java Server Pages)类似于ASP技术,它是在传统的网页HTML文件(.htm,.html)中插入Java程序段(Scriptlet)和JSP标记(tag),从而形成JSP文件, ...

  2. dex、apk完整性校验

    对Dex进行完整性的检查,可通过CRC,或者Hash值.可将校验值放到String资源文件里,或者放到服务器中. 在代码中完成校验值对比逻辑,此部分代码后续不能再改变,否则CRC值会发生变化: 从生成 ...

  3. Dart 学习

    语言特性 Dart所有的东西都是对象, 即使是数字numbers.函数function.null也都是对象,所有的对象都继承自Object类. Dart动态类型语言, 尽量给变量定义一个类型,会更安全 ...

  4. Bug调试专项练习三笔记

    前言:大家需要将文件夹中"有问题的代码" 导入到自己的工作空间中一. 训练一: 正确效果:首先要求大家导入给大家的项目, 给项目的"虚拟路径" 设定为" ...

  5. css导航条的设计

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  6. Reverse 高校网络信息安全运维挑战赛

    Reverse 高校网络信息安全运维挑战赛 1 signed int sub_403CC0() 2 { 3 unsigned int v0; // eax 4 int key_lens; // eax ...

  7. Dynamics CRM 在表单上显示更改历史记录(审核历史记录)

    前言 虽然Dynamics CRM自带的审计很好,但是对于缺乏使用CRM经验的用户来说,自带的UCI界面实在是太隐藏了: 于是乎就出现了需求:想通过在表单上直接看到看审计历史记录: 在网上搜索了很多中 ...

  8. DAOS 分布式异步对象存储|存储模型

    概述 DAOS Pool 是分布在 Target 集合上的存储资源预留.分配给每个 Target 上的 Pool 的实际空间称为 Pool Shard. 分配给 Pool 的总空间在创建时确定,后期可 ...

  9. SpringBoot整合Swagger2及使用

    简介 swagger是一个流行的API开发框架,这个框架以"开放API声明"(OpenAPI Specification,OAS)为基础, 对整个API的开发周期都提供了相应的解决 ...

  10. 带你全面认识CMMI V2.0(二)

    CMMI V2.0成熟度等级 CMMI V2.0的一大变化是,所有实践领域均适用于成熟度三级(ML3),并具有特定的附加必需实践水平. 例如,在ML3上需要进行因果分析和解决,但在CMMI成熟度四级( ...