HashMap方法源码分析
本文将分析put(),resize(),get()和remove()方法的源码
putval()方法
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()方法
 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()方法:
 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()方法:
 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方法源码分析的更多相关文章
- Java split方法源码分析
		Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ... 
- invalidate和requestLayout方法源码分析
		invalidate方法源码分析 在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewG ... 
- Linq分组操作之GroupBy,GroupJoin扩展方法源码分析
		Linq分组操作之GroupBy,GroupJoin扩展方法源码分析 一. GroupBy 解释: 根据指定的键选择器函数对序列中的元素进行分组,并且从每个组及其键中创建结果值. 查询表达式: var ... 
- HashMap的源码分析与实现  伸缩性角度看hashmap的不足
		本文介绍 1.hashmap的概念 2.hashmap的源码分析 3.hashmap的手写实现 4.伸缩性角度看hashmap的不足 一.HashMap的概念 HashMap可以将其拆分为Hash散列 ... 
- HashMap的源码分析
		hashMap的底层实现是 数组+链表 的数据结构,数组是一个Entry<K,V>[] 的键值对对象数组,在数组的每个索引上存储的是包含Entry的节点对象,每个Entry对象是一个单链表 ... 
- Java——HashMap底层源码分析
		1.简介 HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap 最多只允许一条记录的key为 nu ... 
- Java中HashMap的源码分析
		先来回顾一下Map类中常用实现类的区别: HashMap:底层实现是哈希表+链表,在JDK8中,当链表长度大于8时转换为红黑树,线程不安全,效率高,允许key或value为null HashTable ... 
- jQuery实现DOM加载方法源码分析
		传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... } 但 ... 
- jQuery.extend()方法和jQuery.fn.extend()方法源码分析
		这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子: html代码如下: <!doctype html> ... 
随机推荐
- java线程实现的三种方式以及静态代理
			线程 一个进程中若开辟多个线程,线程的运行由调度器控制,先后顺序不能人为干预. 实现方式 继承 Thread类 调用run方法,只有主线程一条路 调用start方法,主线程和子线程并行交替执行 pub ... 
- HDU_3359 Kind of a Blur 【浮点型高斯消元+BFS】
			一.题目 Kind of a Blur 二.分析 题目读起来挺费劲的. 主要就是要求一个矩阵,其中每个点及其于这个的曼哈顿距离小于D的点的值总和的平均值就是新生成的矩阵. 给定新生成的矩阵,求初始矩阵 ... 
- 叫练手把手教你读JVM之GC信息
			案例 众所周知,GC主要回收的是堆内存,堆内存中包含年轻代和老年代,年轻代分为Eden和Surivor,如下图所示.我们用案例分析下堆的GC信息[版本:HotSpot JDK1.8]. /** * @ ... 
- PTA 递增的整数序列链表的插入
			6-4 递增的整数序列链表的插入 (15 分) 本题要求实现一个函数,在递增的整数序列链表(带头结点)中插入一个新整数,并保持该序列的有序性. 函数接口定义: List Insert( List ... 
- async await Task 使用方法
			使用概述 C#的使用过程中,除了以前的Thread.ThreadPool等用来开一个线程用来处理异步的内容.还可以使用新特性来处理异步.比以前的Thread和AutoResetEvent.delege ... 
- mybatis-plus的Could not set property 'updateDate' of 'class com.example.pojo.User' with value 'Fri Jul 24 10:29:39 CST 2020' Cause: java.lang.IllegalArgumentException: argument type mismatch解决方案
			按照官网在写mybatis-plus的自动填充功能一直报错,发现官网的解说不全,数据库是datetime类型,java程序又是date,类型不匹配 org.mybatis.spring.MyBatis ... 
- 简易计算器实现:while循环+switch语句
			个人练习: 写一个计算器,要求实现加减乘除功能,并且能循环接收新的数据,通过用户交互实现(即Scanner对象) 用到了 while循环 switch语句,实现了数据的循环输入并计算!!!!妙啊!!! ... 
- 冒泡排序(BubbleSort)
			介绍: 冒泡排序是一种最基础的交换排序(两两比较待排序的关键字,交换不满足次序要求的那对数,直到整个表都满足次序要求为止),工作方式如同碳酸饮料中的二氧化碳气泡最终会上浮到顶端一样,故名"冒 ... 
- Makefile基本用法
			来源 https://www.gnu.org/software/make/manual/make.pdf 简单的例子 其中的cc通过链接,间接指向/usr/bin/gcc. Makefile文件中列出 ... 
- 【2020.8.23NOIP模拟赛】失落
			[ 2020.8.23 N O I P 模 拟 赛 ] 失 落 [2020.8.23NOIP模拟赛]失落 [2020.8.23NOIP模拟赛]失落 题目描述 出题人心情很失落,于是他直接告诉你让你求出 ... 
