
HashMap 源码分析
一个字:干!
HashMap的常量设置
1 | // 默认初始长度 |
哈希桶
在JDK1.7中,HashMap是以 数组 + 链表 的形式组成的(jdk1.8增加了红黑树)。其中数组中的元素我们称之为哈希桶,它的源码如下:
1 | /** |
我们知道红黑树的概念是在JDK1.8中添加的,之所以添加进来是因为链表一旦过长。HashMap的性能就很受影响,而之前我们介绍了红黑树的数据结构具有快速增删改查的特点,这就有效的解决了链表过长所导致的性能问题;
什么时候转换为红黑树?
当链表的长度大于8且容量大于64时,链表结构会转换为红黑树结构;我们来看以下源码:
1 | /** |
1 | final V putVal(int hash, K key, V value, boolean onlyIfAbsent, |
加载因子为何设定为0.75?
出于容量和性能的平衡考虑的结果;
- 假如加载因子设置过大,那么发生扩容的门栏就比较高,那么扩容的频率就很低,发生Hash冲突的概率就会提高。
- 假如加载因子设定的比较小,那么扩容的频率就会变高,那么将会占用更多的空间;并且此时,数据的存储就会比较稀疏,对各种操作的性能要求比较高!
Hash冲突了怎么查找节点?
我们看下获取节点的源码getNode
:
1 | final Node<K,V> getNode(int hash, Object key) { |
当hash冲突了即hash值相等了,还需要判断key值是否相等来确定节点元素;
HashMap是如何扩容的?
我们来看下扩容方法resize
:
1 | /** |
若HashMap 创建时没有指定大小和扩容阀值,则默认为16,负载因子是0.75。那么容量默认为12,当容量大于12时则扩容为原来16的两倍即32;从源码可以看到,扩容后会进行元素的位置调整;因为jdk1.7在迁移过程中会倒置链表,即头插法有可能倒置循环引用问题(HashMap本身即是非线程安全的,所以官方jdk1.7未处理此问题);而jdk1.8使用了尾插法,解决了此问题;但在扩容的时候依然会有线程安全的问题,并且在扩容的过程中若是红黑树则会进行拆分,否则才会进行链表迁移;
左右旋
左右旋的本质是为了包装树的平衡,我们知道红黑树的本质是一颗平衡二叉树!
1 | // 左旋 |
关于左右旋的更形象理解可以查阅《大话数据结构》中关于红黑树的描述;
小结:HashMap 基本数据结构是数组(EntryNode Hash桶)+链表+红黑树;当链表节点的长度大于8,且数组的长度小于64时,对数组扩容;当链表节点长度大于8,数组长度大于64时,链表转换为红黑树;扩容的过程是,数组放在原有的索引处,链表放在原有的索引加上原有的数组长度的地方;扩容后如果树节点小于6,就将树还原成链表;
- Title: HashMap 源码分析
- Author: viEcho
- Created at : 2021-04-23 19:55:09
- Updated at : 2024-06-25 23:00:16
- Link: https://viecho.github.io/2021/0423/hashmap-source-code.html
- License: This work is licensed under CC BY-NC-SA 4.0.