HashMap详解
HashMap
目录
1. 基本介绍
基于哈希表的 Map 接口的实现。
继承结构:
- java.lang.Object
- java.util.AbstractMap<K,V>
- java.util.HashMap<K,V>
- java.util.AbstractMap<K,V>
内部存储结构
实际是一个链表的数组
结构
- table数组存放链表的头结点
- 该数组的下标索引为(table.length - 1) & hash
- 链表过大时,会自动转成树结构(TreeNode),结果类似TreeMap;(Tree bins主要通过hashCode排序,但是如果两个元素继承了Comparable接口可比较,那么就会用compareTo方法比较-反射机制验证)
重要的成员变量
Node<K,V>[]
table
: 哈希表,第一次使用时初始化;需要时自动调整大小;大小总是2的n次方int
size
: 该Map中键值对的数量int
threshold
: 阈值,threshold = capacity * loadFactor,当size>=threshold时,HashMap的容量翻倍int
loadFactor
: 加载因子
重要的常量
- DEFAULT_INITIAL_CAPACITY = 16 : 默认初始容量
- MAXIMUM_CAPACITY = 1<< 30 : 带参的构造函数显式声明一个比它大的容量时使用,2的n次方,<= 1<<30
- DEFAULT_LOAD_FACTOR = 0.75 : 默认加载因子
- TREEIFY_THRESHOLD = 8 : 默认链转树的阈值
- UNTREEIFY_THRESHOLD = 6 : 默认树转链的阈值
- MIN_TREEIFY_CAPACITY = 64 : 最小链转树的容量,当小于这个值时,采用扩容,而不是链转树的方式降低链的大小
特点:
- 允许使用 null 值和 null 键
- 不保证映射的顺序,特别是它不保证该顺序恒久不变
- 不是同步的
对HashMap如何实现同步
- 通过对自然封装该映射的对象进行同步操作
- 使用 Collections.synchronizedMap 方法来“包装”该映射
Map m = Collections.synchronizedMap(new HashMap(...));
性能:
1.影响因素
- 初始容量
- 加载因子
2.迭代性能
迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。为什么
所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)
2. 初始化过程
1. 初始化参数
- initialCapacity
- loadFactor
2. 初始化过程
- 验证initialCapacity:小于0,抛出异常;大于MAXIMUM_CAPACITY,使用MAXIMUM_CAPACITY作为初始容量。
- 验证loadFactor:小于等于0,抛出异常;isNaN,抛出异常(Float.NaN = 0.0f / 0.0f,它与任何其他浮点数不相等,甚至和自己不相等)
- 设置loadFactor
- 设置阈值threshold = tableSizeFor(initialCapacity)。
NOTES:
- 链表数组table未初始化
- threshold设置为大于等于initialCapacity的、2的n次方的最小值
3. put过程
- 判断hash表是否为空(null或length==0):如果是,resize -> 初始化hash表
- 找出key的索引 i = (capacity - 1)& hash(key),判断table[i]是否为null:如果是,在该位置新建链表
- 判断key与链表头结点key是否相等,方式:先判断hash值是否相等,再判断==、equals,只要hash相等,并且后面两个条件满足其一,则认为是相同的key值 -> 替换该位置的value(!onlyIfAbsent情况下)
- 判断hash表中该节点是否是树节点类型,如果是,putTreeVal
- 不是树节点类型,遍历该链表,如果遇到key相等,则替换;链表中没有key相等的,则插入到末尾。binCount(单个链表的长度)大于TREEIFY_THRESHOLD时,尝试链转树,当capacity大于等于MIN_TREEIFY_CAPACITY时,执行链转树,否则,resize
- 过程中不是替换操作,更新modCount;put后size>threshold,resize;返回null
4. rehash过程(resize 重建数据结构)
- 计算扩容后的容量,临界值。
- 将hashMap的临界值修改为扩容后的临界值
- 根据扩容后的容量新建数组,然后将hashMap的table的引用指向新数组。
- 将旧数组的元素复制到table中。
5. 与其他集合类的比较
与HashTable的区别
- 非同步
- 允许使用 null作为键值