JDK1.7的HashMap的put(key, value)源码剖析
目录
- HashMap的put操作源码解析
- 1、官方文档
- 2、put(key, value)
- 3、完结
HashMap的put操作源码解析
1、官方文档
1.1、继承结构
java.lang.Objectjava.util.AbstractMap<K,V>java.util.HashMap<K,V>
1.2、类型参数:
K - 此映射所维护的键的类型
V - 所映射值的类型
2、put(key, value)
HashMap
是一种以键——值
对的形式来存储数据的数据结构。HashMap
允许使用 null 值和 null 键,它并不能保证你存放数据和取出的顺序是一致的。
接下来就以下面的代码来看一下put
是怎么将数据存放到map
中的。
public class HashMapTest {public static void main(String[] args) {Map<String, Object> map = new HashMap<String, Object>();map.put(null, "map-value");map.put(map-key", "map-value");System.out.println(map);}
}
2.1、重点源码部分截取
在map.put()
这里打个断点F5(我用的eclipse)
跟踪进去。我们就会进到put
方法中:
public V put(K key, V value) {if (table == EMPTY_TABLE) {inflateTable(threshold);}if (key == null)return putForNullKey(value);int hash = hash(key);int i = indexFor(hash, table.length);for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(hash, key, value, i);return null;
}
这里的EMPTY_TABLE
是HashMap
的一个静态常量,是一个Entry数组,默认值是空数组,table
是HashMap
的一个属性且其默认值就是EMPTY_TABLE
,这个table也就是我们数据存放的地方,至此为止可以知道,HashMap其实是一个数组,但它又不是一个纯粹的数组。下面会进行解释。
static final Entry<?,?>[] EMPTY_TABLE = {};transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
而这个Entry
其实是HashMap的一个内部类,定义如下(仅截取部分代码),记住这个类,记住这个构造方法:它在new Entry
的时候接收了一个Entry
对象,并将自己的next
指向了传入的Entry
对象形成一个链表,其自身是表头。
static class Entry<K,V> implements Map.Entry<K,V> {final K key;V value;Entry<K,V> next;int hash;Entry(int h, K k, V v, Entry<K,V> n) {value = v;next = n;key = k;hash = h;}
}
从上面我们可以看出来这个Entry
其实是一个链表,它存放了 key 和 value 并且还有一个指向下一个节点的引用 Entry next
, 剩下的这个 hash
就是 key 的哈希值。
现在我们可以捋一捋HashMap的结构了。首先HashMap
是一个Entry
数组,而这个Entry
是一个单向链表,我们大致可以将其结构画成如下图所示:
2.2、put(key, value)源码分析
public V put(K key, V value) {if (table == EMPTY_TABLE) {inflateTable(threshold);}if (key == null)return putForNullKey(value);int hash = hash(key);int i = indexFor(hash, table.length);for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(hash, key, value, i);return null;
}
- 因为我们在实例化HashMap的时候使用的是无参构造方法,所以第一次
put
数据的时候table
为空
if (table == EMPTY_TABLE) {inflateTable(threshold);
}
上面这段代码会被执行,inflateTable(threshold)
会将table
初始化为一个长度为16
的Entry
数组。
- 它会对我们的
key
进行空判断,如果是空就会执行下面的代码:
if (key == null)return putForNullKey(value);
putForNullKey(value)
的实现如下:
private V putForNullKey(V value) {for (Entry<K,V> e = table[0]; e != null; e = e.next) {if (e.key == null) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(0, null, value, 0);return null;
}void addEntry(int hash, K key, V value, int bucketIndex) {if ((size >= threshold) && (null != table[bucketIndex])) {resize(2 * table.length);hash = (null != key) ? hash(key) : 0;bucketIndex = indexFor(hash, table.length);}createEntry(hash, key, value, bucketIndex);
}void createEntry(int hash, K key, V value, int bucketIndex) {Entry<K,V> e = table[bucketIndex];table[bucketIndex] = new Entry<>(hash, key, value, e);size++;
}
2.2.1 、key
为null
的情况
从上面可以看出来,如果key
为null
的话,它会从table
中取出下标为0也就是第一个元素,没忘记的话我们应该还知道它一个Entry
,是一个链表,如果这个元素不是null
,那么就会遍历这个链表,并判断当前这个Entry
节点对象的key
是不是null
。
- 如果是
null
(key
相同了): 使用oldValue
来存放当前这个Entry
节点对象的value
,然后将我们新的值(map-value
)赋给当前节点,再将原值oldValue
返回回去。 - 如果遍历完链表的所有节点都没有找到
key
为null
的节点就会调用addEntry(0, null, value, 0)
,这个方法前面的if(){***}
这块代码是判断当前table是否要进行扩容。这里只做简单讲述。
size
是当前table
存放的Entry链表的个数,拿我上面画的那个HapshMap
结构来看就是4。
如果我们实例化HashMap的时候没有给大小那么:threshold
=loadFactor
(负载因子默认为0.75f
) *DEFAULT_INITIAL_CAPACITY
(HashMap默认大小也就是table长度为16
),所以threshold
=12
。
如果我们给了大小为initialCapacity
,那么负载因子还是默认的0.75f
,但是threshold不需要算了,值就是initialCapacity
。如果我们同时给了HashMap
的大小initialCapacity
和负载因子loadFactor
,那么HashMap
就使用我们给定的负载因子值作为新的负载因子,给定的HashMap
大小作为threshold
。ok第一个条件结束。
null != table[bucketIndex]
就很好理解了,就是我当前这个节点要存放的位置是空的。
满足上面两个条件,HashMap
就会进行扩容,扩容后的大小为扩容前的2倍,然后对key
重新计算它的hash
值以及数组下标。 - 继续
put
内容,从上面源码我们可以知道key
为null
的情况下它的hash
值是0,至于bucketIndex
的计算是这样的h & (length-1)
,也是将hash
值与table
的长度按位相与值也是。至此也就是确定了key
为null
的这个节点将存放在table的第一个位置上。然后就会调用createEntry(0, null, "map-value", 0);
- 在
createEntry(int hash, K key, V value, int bucketIndex)
这个方法里首先拿到table
中下标为bucketIndex
的链表的表头:Entry<K,V> e = table[bucketIndex];
然后再用Entry
对象的构造方法new
一个Entry
将我们的hash
值,key
,value
,链表的表头作为参数传入:table[bucketIndex] = new Entry<>(hash, key, value, e);
就这样我们的这个新节点就放在了原来的表头的前面作为新的表头了。没看懂的再回到上面看一下Entry
的构造方法,我有重点标注的。
2.2.2、 key
不为null
的情况
源码依旧拿下来
public V put(K key, V value) {if (table == EMPTY_TABLE) {inflateTable(threshold);}if (key == null)return putForNullKey(value);int hash = hash(key);int i = indexFor(hash, table.length);for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(hash, key, value, i);return null;
}
- 首先说一下
hash
值:对于相同的key
它们的hash
值是相同的。但是hash
值相同,它们的key
却不一定是相同的,这就是哈希碰撞。 key
不为null
的话它会根据key
算出这个key
对就的hash
值以及它的bucketIndex
,然后拿到table
中下标为bucketIndex
的这个Entry
链表,然后遍历这个链表,判断当前节点的hash
其实也就是当前节点的key
的hash
是否等于我们传入的map-key
的hash
,然后判断当前节点的key
是否与我们传入的key
相同。
如果以上条件都满足了,那么就是key
相同了,就会跟Key
为null
的分析中的第一条一样将新值覆盖旧值,并将旧值返回回去。
如果遍历完这个链表以上条件没有得到满足,那么就会跟key
为null
的分析中的第四条一样,获得table
下标为i
的链表的表头e
,然后将我们的map-key
,map-value
,hash
以及表头e
作为参数new
一个新的Entry
对象并将它的next
指向原来的表头e
,它也就变成了新的表头了。
3、完结
最怕你的能力配不上你的野心。
转载于:https://www.cnblogs.com/zc-programer/p/9940591.html
JDK1.7的HashMap的put(key, value)源码剖析相关推荐
- Java HashSet和HashMap源码剖析
转载自 Java HashSet和HashMap源码剖析 总体介绍 之所以把HashSet和HashMap放在一起讲解,是因为二者在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也就是说Ha ...
- 【转】HashMap,ArrayMap,SparseArray源码分析及性能对比
HashMap,ArrayMap,SparseArray源码分析及性能对比 jjlanbupt 关注 2016.06.03 20:19* 字数 2165 阅读 7967评论 13喜欢 43 Array ...
- 隔一段时间撸一次,特别香,HashMap中remove、getOrDefault源码,一遍一遍、又一遍
前言 点赞在看,养成习惯. 点赞收藏,人生辉煌. HashMap系列文章 第一篇 HashMap源码中的成员变量你还不懂? 来来来!!!整理好的成员变量源码解析 第二篇 撸啊撸,再次撸HashMap源 ...
- 【Java集合源码剖析】HashMap源码剖析
转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955 您好,我正在参加CSDN博文大赛,如果您喜欢我的文章,希望您能帮我投一票,谢 ...
- 字节跳动Android三面视频解析:framework+MVP架构+HashMap原理+性能优化+Flutter+源码分析等
前言 对于字节跳动的二面三面而言,Framework+MVP架构+HashMap原理+性能优化+Flutter+源码分析等问题都成高频问点!然而很多的朋友在面试时却答不上或者答不全!今天在这分享下这些 ...
- Java集合:HashMap源码剖析
一.HashMap概述 二.HashMap的数据结构 三.HashMap源码分析 1.关键属性 2.构造方法 3.存储数据 4.调整大小 5.数据读取 ...
- Java HashMap源码剖析
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- 详细解析:HashMap源码剖析
目录: 一.HashMap概述二.HashMap的数据结构三.HashMap源码分析 1.关键属性 2.构造方法 3.存储数据 4.调整大小 5.数据读取 6.HashMap的性能参数 7.Fail- ...
- HashMap红黑树原理及源码分析---图形、注释一应俱全
目录 一.红黑树定义 二.节点新增原理: 三.红黑树的生成 2.1 一个节点 2.2 两个节点 2.3 三个节点 2.3.1 第二个节点作为root右子树情况下 2.3.2 第二个节点作为root左子 ...
最新文章
- 51nod 1617 奇偶数组
- 动作捕捉技术,VR体验沉浸感的“助燃剂”
- python自动测试p-python网络爬虫之自动化测试工具selenium[二]
- JDK5.0环境下配置PKCS#11
- python内存管理说法错误_python面试题总结1-内存管理机制
- “疫”外爆发:没那么简单的视频会议
- 程序员必读的涨薪指南
- 收缩sqlserver事务日志
- dubbo和zookeper使用_Dubbox与Zookeeper简介及入门小案例
- [转载+整理]Nginx Location匹配规则
- Dijkstra及其堆优化
- c语言编程 进制转换,c语言中的进制转换
- 泛微oa系统什么框架_泛微OA ecology 二次开发实例 开发完整说明
- android自动点击相应位置脚本,轻易连使用说明-自动连点器-安卓自动点击脚本 | MOS86...
- 今天花了点时间详细了解了一下WiFi
- C++程序员应了解的那些事(19)C++ trivial(平凡的)和non-trivial(非平凡的)
- manjaro wechat
- 外贸常用邮箱有哪些?163mail邮箱适合外贸用吗?
- UnityShader屏幕后处理-Bloom效果(朦胧模糊)
- 简单的html图片上传工具
热门文章
- 拳王虚拟项目公社:如何通过知识付费赚钱,知识付费搬运赚钱,虚拟资源付费项目
- 厉害了!春节不打烊年货30分钟即买即送 饿了么新上线650多家超市
- 拳王虚拟项目公社:最新创业好项目有哪些,90后创业好项目,虚拟资源兼职副业好项目
- 给我一个小碗,还你一个奇迹——结构工程师教你吃垮必胜客(有图,附论文)【ZT】...
- mysql启动提示 access denied for user root@localhost(using password:YES) 解决办法总结
- 晨哥真有料丨我们要为了对方改变自己吗?
- C/C++中static关键字的作用
- java邮箱代码_java邮箱开发代码——发邮件
- 循环单链表 python_循环单链表报错
- 【java】理解和运用Java中的Lambda