1、 初始容量
1. 初始大小16 ,扩容因子是0.75 ,比如16的大小,达到12就要扩容
2. 在new HashMap()如果设置默认大小,会在第一次put的时候进行设置容量大小
4. 扩容操作是 HashMap 中最耗时的操作之一,因为它涉及到重新计算所有元素的存储位置。
3. 扩容时,HashMap的容量通常会增加到原来的两倍。
2、 数据结构
HashMap 基于数组和链表(或红黑树)实现,数组中的每个元素是一个链表(在Java 8及以后版本中,当链表长度超过一定阈值时,链表会转换成红黑树)。
2.1、 各种map对比
| Map | 数据结构 | 描述 |
|---|---|---|
| HashMap | 数组+链表/红黑树 | 非线程安全 |
| LinkedHashMap | 数组+双向链表 | 非线程安全,有序 |
| ConcurrentHashMap | 数组+链表/红黑树 | 线程安全 |
2.2、 hash key 处理
容量是2次幂,比如 一个16 的大小容量 在key 得到一个hashCode 后在进行 (16-1)& hashCode = index(存放的位置)
位运算 15 的二进制 1111 位运算 就是相同位都是1 则为1 否则为0 比如 15 & 2 = 2 ,15 & 17 = 1
2.3、 链表和红黑树转换
hash冲突当 链表数据达到8会转换成红黑树数据结构,当红黑树退到大小为6时转换成链表 红黑树(O(logN))
2.4、 线程安全ConcurrentHashMap
ConcurrentHashMap 锁的维度是每个数组元素,每个entity都是用 volatile 进行修饰
3、 性能
HashMap 的平均时间复杂度为O(1),但在最坏情况下(所有元素都映射到同一个桶中)会退化为O(n)。• 使用合适的初始容量和负载因子可以减少扩容次数,提高性能。
4、 哈希算法
4.1、 HashMap 使用哈希算法来计算每个键值对的存储位置。这个过程包括两个步骤:
1. 哈希函数: HashMap 通过键对象的 hashCode() 方法获取哈希码,然后通过自己的哈希函数计算出一个数组索引,这个索引就是键值对将要存储的位置。 HashMap 的哈希函数会尽量减少哈希冲突,即不同的键哈希到同一个位置的情况。
2. 冲突解决:即使哈希函数设计得再好,也不能完全避免哈希冲突。 HashMap 使用链表(或红黑树)来解决冲突。当两个键的哈希值相同(即哈希冲突),它们会以链表的形式存储在同一个数组位置。
4.2、 链表和红黑树
链表:在 HashMap 的数组中,每个位置都对应一个链表。当发生哈希冲突时,新的元素会被添加到链表的头部。这意味着,如果一个桶(数组位置)中的元素很多,那么查找特定元素的时间复杂度会退化到O(n)。
红黑树:从Java 8开始,当链表的长度超过一定阈值(TREEIFY_THRESHOLD,默认为8)时,链表会转换成红黑树。红黑树是一种自平衡的二叉搜索树,它可以保证在最坏情况下的查找、插入和删除操作的时间复杂度为O(log n)。