一文理清HashMap的实现及细节
前言
最近阅读了许多HashMap实现及源码分析的文章,特意此文记录HashMap的知识点。
HashMap 底层由 数组 + 链表 组成,在 jdk1.7 和 1.8 中具体略有不同。
JDK1.7的HashMap
数据结构:图片来源
核心成员变量
图片来源
- 初始化桶大小(1<<4,即:16),因为底层是数组,所以这是数组默认的大小。
- 桶容量最大值。
- 默认的负载因子(0.75)
- table 真正存放数据的数组。
- Map 中存放元素数量。
- 桶的容量大小,可在初始化时显式指定。
- 负载因子,可在初始化时显式指定。
负载因子
当 存放的键值对数量(size) = 桶容量(threshold) * 负载因子(loadFactor)
时,会发生扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。因此最好提前预估 HashMap 的大小,尽量的减少扩容带来的性能损耗。
Entry
Entry是HashMap的一个内部类,用于保存键值对,实现HashMap中的链表,主要成员变量:
- key:写入的键。
- value: 写入的值。
- next:开始的时候就提到 HashMap 是由数组和链表组成,所以这个 next 就是用于实现链表结构。
- hash: 存放的是当前 key 的 hashcode。
桶初始大小为16的原因
要解释这个问题,首先要知道这个容量的用途。容量就是一个HashMap中"桶"的个数(数组的大小),当想要往一个HashMap中put一个元素的时候,需要通过一定的算法计算出应该把他放到哪个桶中。HashMap中通过以下两个方法实现计算一个元素对应的桶(数组的索引)
int hash(Object k)
:该方法主要是将Object转换成一个整型。int indexFor(int h, int length)
:该方法主要是将hash生成的整型转换成链表数组中的下标。jdk1.8没有此方法,不过计算的方式相同。
static int indexFor(int h, int length) {return h & (length-1);
}
在保证length(容量)是2^n 的前提下,h & (length-1)
相当于h % (length-1)
,即用位运算(&)代替取模运算(%)
Java之所有使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。
位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。
为什么保证容量为2^n即使用位运算(&)来实现取模运算(%)
总结:因为位运算直接对内存数据进行操作,不需要转成十进制,所以位运算要比取模运算的效率更高,所以HashMap在计算元素要存放在数组中的index的时候,使用位运算代替了取模运算。而等价代替,前提是要求HashMap的容量一定要是2^n。
由上述分析,容量只要为2^n即可,HashMap选择16的原因可能是个经验值。
既然一定要设置一个默认的2^n 作为初始值,那么就需要在效率和内存使用上做一个权衡。这个值既不能太小,也不能太大。太小了就有可能频繁发生扩容,影响效率。太大了又浪费空间,不划算。(官方未给出原因)
扩容
由上述分析:HashMap必须保证容量为2^n。因此在扩容时,HashMap会进行成倍的扩容(容量变为原来的2倍)。
扩容的步骤为:
- 新建数组:创建一个新的Entry空数组,长度是原数组的2倍。
- 重新计算hash:遍历原Entry数组,把所有的Entry重新Hash到新数组。
Put方法(头插法)
JDK1.7下的put方法添加新元素时使用头插法:即新来的值会成为头节点。
public V put(K key, V value) {//判断当前数组是否需要初始化if (table == EMPTY_TABLE) {inflateTable(threshold);}//如果 key 为空,则 put 一个空值进去。if (key == null)return putForNullKey(value);//计算键值hash值int hash = hash(key);//查找对应的桶的索引int i = indexFor(hash, table.length);//遍历链表for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;//遍历判断里面的 hashcode、key 是否和传入 key 相等,//如果相等则进行覆盖,并返回原来的值。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;
}
头插法的问题
使用头插法,在扩容时会反转链表上元素的顺序。在多线程及需要扩容的条件下,可能出现环形链表,造成死循环。
jdk1.7HashMap出现环路(有个例子,但我感觉不是特别清楚)
JDK1.8的HashMap
JDK1.7的HashMap在 Hash 冲突严重时,桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 O(N)。
因此JDK1.8重点解决的此问题。
数据结构:图片来源
主要区别
- 新的成员变量
TREEIFY_THRESHOLD
用于判断是否需要将链表转换为红黑树的阈值。链表长度大于等于该值时,会尝试转为红黑树(还需判断数组长度是否大于MIN_TREEIFY_CAPACITY
) - 新的成员变量
UNTREEIFY_THRESHOLD
用于判断是否需要红黑树转为链表的阈值。 - 用Node代替Entry,在达到红黑树阈值时,将链表转为红黑树提高查询效率。
- put方法添加新的元素时,由头插法改为尾插法。使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题。但在 HashMap 扩容的时候会调用 resize() 方法,此时并发操作仍然可能在一个桶上形成环形链表。
JDK1.8下的HashMap依旧是线程不安全的,只是用尾插法代替头插法解决了JDK1.7时,容易出现环形链表的问题。
转为红黑树的条件
默认情况下:链表长度大于 8(TREEIFY_THRESHOLD), 表的长度大于 64(MIN_TREEIFY_CAPACITY) 的时候会转化红黑树。
参考
- HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!
- 《吊打面试官》系列-HashMap
- HashMap 为什么线程不安全?
一文理清HashMap的实现及细节相关推荐
- 一文理清散乱的物联网里开发者必须关注的技术!
一文理清散乱的物联网里开发者必须关注的技术! 不管是从商业模式导出的业务模型,还是从技术发展的角度看,文本都倾向于将物联网技术构架看作是互联网技术构架的延展.而与这个观念对立的,是传统嵌入式软件开发的 ...
- MLK | 一文理清集成学习知识点(BoostingBagging)
???? 前情回顾 MLK | 那些常见的特征工程 MLK | 模型评估的一些事 MLK | 机器学习的降维"打击" MLK | 非监督学习最强攻略 MLK | 机器学习采样方法大 ...
- MLK | 一文理清深度学习循环神经网络
MLK,即Machine Learning Knowledge,本专栏在于对机器学习的重点知识做一次梳理,便于日后温习,内容主要来自于<百面机器学习>一书,结合自己的经验与思考做的一些总结 ...
- 一文理清Mybatis中resultType与resultMap之间的关系和使用场景
点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 1.概述 Mybatis ORM半自动映射框架对java开发工程师来说应该是必会的框架之一. ...
- 一文理清面向对象(封装、继承、多态)+ 实战案例
python是一门面向对象编程语言,对面向对象语言编码的过程叫做面向对象编程. 面向对象是一种思想,与之相对的是面向过程.我们先简单说一下面向过程. 面向过程其实就是把过程当做设计核心,根据问题的发展 ...
- 一文理清Cookie、Session、Token
发展史 1.很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, 不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议, 就是请求加响应, 尤其是我不用记 ...
- 一文理清区块链里那些容易混淆的概念
本文为智链ChainNova投稿文章. 区块链技术分享.行业分析等文章投稿可邮件至 jiawd@csdn.net或微信联系 jiaweidi1214 我们在研究区块链的过程中发现,区块链的发展和云计算 ...
- 一文理清数据仓库实施方法论
关键的原则包括: 以数据仓库技术为核心平台 数据平台的设计必须解决现有问题,同时着眼于未来 促进一致性和跨部门的整合 剔除重复的数据 保留事件的历史和事件的相关内容 收集和保存最原始的数据 收集满足现 ...
- 一文理清H5调起App那些事
以安卓为例,实现h5调起app步骤: 在安卓AndroidManifest.xml中,启动Activity下添加属性: <intent-filter><action android: ...
最新文章
- 怎么样才能快速的把淘宝店铺推广出去
- 百度大脑发挥AI“头雁效应” 王海峰:在AI时代共同推动社会智能化升级
- 全球人工智能战略与政策观察(2019)
- POS 收款机资料整理
- python下载大文件-python-Django:允许用户下载大文件
- Android Activity动画属性简介
- h桥控制电机刹车_082 电机驱动桥集成式的结构,定速比10左右,松油门或轻踩刹车瞬间,出现齿轮撞击的情况,是什么原因导致的?应该采取什么措施?...
- python运行结果闪退_Pyhton TestCase运行闪退与失败,原因不详。。。
- oracle审计实施
- CGContextRef CIImageRef详解
- 码农你会搜“Win10 破解版”吗?
- linux tar 命令使用
- linux服务sendmail邮件服务
- Default process group has not been initialized, please make sure to call init_process_group
- [Swift]LeetCode1023. 驼峰式匹配 | Camelcase Matching
- 通过names.index()方法找到第2个eva值 ,并将其改成EVA
- MATLAB简介与基础知识
- JavaScript(十二)常见js特效
- 那些年,我们信了课本里的那些鬼话
- 计算机网络回顾之计算机网络概述