文章标题
来源:互联网 发布:淘宝直播中控台是什么 编辑:程序博客网 时间:2024/06/07 03:26
HashMap基础知识
–来源:伯乐专栏作者/玻璃猫,微信公众号 - 程序员小灰
简述
HashMap是由一个个键值对的集合,每个键值对也叫Entry,分散存储在一个数组中。
HashMap数组每一个元素的初始值都是Null。
常用操作:Get和Put
Put原理
hashMap.put(“apple”, 0)
利用一个哈希函数来确定Entry的插入位置(index)
index = Hash(“apple”)
HashMap的长度是有限的,难免会出现index冲突的情况,这时利用链表来解决。
每个Entry对象也是一个链表的头节点
每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可
需要注意的是,新来的Entry节点插入链表时,使用的是“头插法”。至于为什么不插入链表尾部,后面会有解释。头插法:
index=2
Entry6.next = table[index]
table[index]=Entry6![](http://mmbiz.qpic.cn/mmbiz_png/NtO5sialJZGoXt6UNkvyibQ8ufeF48pvm29aBmKLDfHETNia2Lzpuia9tm9IDX5XXue0nGoSgFpUGT0crAS45zICQQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx _lazy=1)
Get方法的原理
index = Hash(“apple”)
由于可能冲突,所以需要顺着链表头节点一个个向下查找,匹配每个节点中key和要查找的key是否一致,一致就返回对应的value
之所以把Entry6放在头节点,是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大。
关键问题
- HashMap默认初始长度是多少?
默认长度为16,且每次自动扩展或手动初始化时,长度必须为2的幂
原因:
为了服务于从Key映射到index的Hash算法
index = Hash(“apple”)中需要Hash函数是尽量均匀分布的,需要利用Key的HashCode值来做某种运算来实现。
为了高效实现Hash算法,采用了位运算的方式:
index = HashCode(Key) & (Length - 1)
就是直接取HashCode的最后几位二进制数
- 高并发下为什么HashMap可能会出现死锁?
在ReHash时,当属于一个链表的连续节点在重映射后又同时映射到一个index时,会使得新HashMap中这两个节点产生循环引用,此时当要查询一个不存在的键值对,而这个键的映射index又正好是产生循环引用的节点所在的index时,就会出现死循环,从而导致查询无法结束,造成死锁现象。
原因:ReHash是在HashMap的容量达到最大容量的一定比例时,需要进行的操作,该操作是为了减小冲突发生的几率。
衡量HashMap是否进行Resize的条件如下:
HashMap.Size >= Capacity * LoadFactor
大致的过程就是,申请新数组,将旧数组中的所有Entry重新计算HashCode,使用新数组长度重新计算对应的index,然后依次放入新的数组中。
ReHash的Java代码如下:
/** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { 1 Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); 2 e.next = newTable[i]; 3 newTable[i] = e; 4 e = next; } } }
该流程在单线程下没有问题,但是在多线程时就有可能出现死锁现象。
当两个线程A和B,在同一时刻对HashMap进行Put,且此时HashMap已经到了Resize的临界点时,两个线程都开始Resize
old HashMap为:
当线程B执行完1,线程就被挂起。对于线程B来说:e = Entry3 ;next = Entry2
线程A此时开始执行并全部执行完Resize操作,此时A_New_HashMap为:
其中e和next为线程B中的引用示意
此时执行线程B,仔细分析代码,会发现当线程B执行完Resize操作后,A_New_HashMap和B_New_HashMap变为:
此时形成了循环链表,但是还暂时不会引起死锁问题。
但是当调用Get查找一个不存在的Key,而这个Key的Hash结果恰好等于3的时候,由于位置3带有环形链表,所以程序将会进入死循环!解决方案:
一种常用的方案是,使用另一个集合类ConcurrentHashMap,这个集合类兼顾了线程安全和性能。
总结
- HashMap插入冲突时,使用头插法
- HashMap默认初始长度为16
- 扩展长度时,长度为2的幂
- Hashmap在插入元素过多的时候需要进行Resize,Resize的条件是
HashMap.Size >= Capacity * LoadFactor。- Hashmap的Resize包含扩容和ReHash两个步骤,ReHash在并发的情况下可能会形成链表环。
参考:
漫画:什么是HashMap?
漫画:高并发下的HashMap
- 文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题 文章标题 文章标题 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 文章标题
- 安卓剪裁图片(直接操作bitmap)
- 重磅消息!微信或为确保用户账户安全或关停这一功能…
- Idea 启动Sringboot 报错 Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletCont
- 关于召开“2017年BIM及智慧管理技术应用交流会(江苏)”的通知
- idea部署项目到远程tomcat——配置本地idea+远程端
- 文章标题
- JAVA的一次编译,到处运行,你知道多少?
- 【海报故事汇】一杆制胜的TopGolf
- Keras学习笔记二:卷积神经网络相关层
- 图的基本概念及其抽象数据类型
- R语言笔记
- 【CIO早班车】快上车,给你F1级别的基础架构服务体验!
- 判断是否执行过onSaveInstanceState()
- linux查看文件的某一行命令