HashMap的基本概念
HashMap是 Java 中最常用的数据结构之一,属于 java.util 包,实现了 Map<K, V> 接口,用于存储 键值对(Key-Value Pair)。它的核心特点是:通过键(Key)快速查找对应的值(Value),平均时间复杂度为 O(1)。
HashMap的内部结构
众所周知,HashMap底层结构,1.8之前是数组+链表,从1.8开始则是数组+链表+红黑树(链表长度大于8),线程不安全。
数组+链表
水平线上,每个 key 经过 hash 计算后得到一个数组下标 index,这个 index 就代表 一个桶(bucket)。
所有 hash 后落在 同一个 index 的 key-value 对,都会被组织在这个桶里 ------ 初始是链表,冲突多时可能转为红黑树。
当 这个桶里的节点数 ≥ 8,并且整个 HashMap 底层数组的长度(即桶的总数)≥ 64 时,才会将该链表 转换为红黑树
同一个 index 是一个桶,桶内链表 ≥8 且总桶数 ≥64 才转红黑树
某个桶(bucket)中的链表长度 ≥ 8
→ 即:有 8 个或更多 key 经过 hash 计算后落在同一个 index,形成链表。
HashMap 底层数组的长度(桶的总数)≥ 64
→ 即:table.length >= MIN_TREEIFY_CAPACITY(默认 64)
- 💡 举个形象比喻:
想象一条 64 个车位的停车场(水平数组):
- 每个车位是一个 桶(bucket)
- 车牌号(key)经过某种规则(hash)分配到某个车位
- 如果多个车被分到同一个车位(hash 冲突),就 垂直叠起来停(链表)
- 当某个车位叠了 8 辆车以上,且 整个停车场 ≥64 个车位 → 管理员就把这个车位改成 立体车库(红黑树),方便快速找车
- 但如果停车场只有 32 个车位,哪怕某个车位堆了 10 辆车 → 管理员会说:"车位太少才导致拥堵,先扩建停车场!"(扩容),而不是建立体车库
哈希冲突
hash冲突:HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。详情如下:

随着存储的对象数量不断增加,可能会导致以下几种情况:
1.存储对象key 值相同(hash值一定相同),导致冲突;
2.存储对象key 值不同,由于 hash 函数的局限性导致hash 值相同,冲突;
3.存储对象key 值不同,hash 值不同,但 hash 值对数组长度取模后相同,冲突;
HashMap里面解决hash冲突的方法就是链地址法,
链地址法的基本思想是:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向 链表连接起来,如: 键值对k2, v2与键值对k1, v1通过计算后的索引值都为2,这时及产生冲突,但是可以通道next指针将k2, k1所在的节点连接起来,这样就解决了哈希的冲突问题 。

扩容问题
List 的继承关系图
实线代表继承 虚线代表实现接口

List 的特性比较
| 特性 | ArrayList | LinkedList | Vector |
|---|---|---|---|
| 底层结构 | 动态对象数组 Object[] elementData |
双向链表 Node<E>(包含前驱、后继指针) |
动态对象数组 Object[] elementData |
| 线程安全 | 非线程安全(多线程并发读写可能抛出 ConcurrentModificationException) |
非线程安全(同上) | 线程安全 (关键方法如 add()、get() 使用 synchronized 修饰) |
| 扩容机制 | - 触发条件:添加元素时容量等于元素个数 - 默认初始容量:10(空参构造) - 扩容规则:newCapacity = oldCapacity + (oldCapacity >> 1)(即 1.5 倍) - 无负载因子概念 |
不涉及传统扩容。新增元素时动态创建节点,仅调整前后指针。内存开销包含元素本身 + 两个指针 | - 触发条件:添加元素时容量等于元素个数 - 默认初始容量:10 - 扩容规则:若未设置 capacityIncrement,则 newCapacity = oldCapacity * 2(2 倍);否则按增量扩展 - 无负载因子概念 |
| 核心对比点 | - 随机访问快 :基于索引,时间复杂度 O(1) - 尾部增删快 :接近 O(1) - 中间增删慢 :需移动后续元素,O(n) - 内存紧凑:仅存对象,无额外指针 | - 随机访问慢 :需从头/尾遍历,平均 O(n) - 任意位置增删快 (已知节点):仅改指针,O(1);但查找位置仍需 O(n) - 内存开销大 :每个节点含两个指针 - 实现 Deque 接口:可作双端队列使用 |
- 历史遗留类 :JDK 1.0 引入,设计较陈旧 - 同步开销大 :方法级 synchronized 导致高并发性能差 - 现代替代方案 :推荐使用 CopyOnWriteArrayList 或 Collections.synchronizedList(new ArrayList<>()) |
HashMap的扩容
HashMap、Hashtable、ConcurrentHashMap
| 特性 | HashMap | Hashtable | ConcurrentHashMap |
|---|---|---|---|
| 线程安全 | ❌ 非线程安全 | ✅ 线程安全(方法级 synchronized) |
✅ 线程安全(JDK 8+:synchronized + CAS;JDK 7:分段锁) |
| 性能 | 🚀 高(无锁开销) | 🐌 低(全表加锁,高并发下严重阻塞) | ⚡ 高(读操作几乎无锁,写操作锁粒度细) |
| Key/Value 空值 | ✅ 允许 1 个 null 键 ,多个 null 值 |
❌ 不允许 null 键或值 (会抛 NullPointerException) |
❌ 不允许 null 键或值(避免并发语义歧义) |
| 继承体系 | 继承 AbstractMap |
继承 Dictionary(已过时的抽象类) |
继承 AbstractMap,实现 ConcurrentMap 接口 |
| 默认初始容量 | 16 | 11 | 16 |
| 扩容负载因子 | 0.75(默认) | 0.75(默认) | 0.75(默认) |
| 扩容机制 | 2 倍扩容(newCap = oldCap << 1) |
2 倍 +1 扩容(newCap = oldCap * 2 + 1) |
分段并发扩容,多个线程可协助迁移数据(JDK 8+) |
| 负载因子原因 | 0.75 是空间与时间的最佳权衡: • 过低(如 0.5)→ 内存浪费 • 过高(如 0.9)→ 哈希冲突激增 | 同 HashMap,但因初始容量为 11(质数),可更好分散哈希冲突 | 同 HashMap,但并发环境下扩容逻辑更复杂,需保证线程安全 |
| 遍历方式 | Iterator(fail-fast:并发修改会抛异常) |
Enumeration(古老接口,非 fail-fast) |
Iterator(弱一致性:不抛并发异常,可能看不到最新修改) |
| 并发机制 | 无 | 全表锁(每个 public 方法加 synchronized) |
JDK 7:分段锁(Segment) JDK 8+:synchronized 锁桶头 + CAS + 红黑树 |
| 并发度 | 不适用 | 1(整张表一把锁) | 默认 16(JDK 7 可配置;JDK 8+ 动态调整,最大 65535) |
| 可代替度 | ✅ 单线程场景的标准选择 | ⚠️ 已过时,不应在新项目中使用 | ✅ Hashtable 的现代替代者,高并发首选 |
| 应用场景 | • 单线程缓存(如 Spring 单例 Bean 缓存) • 系统配置参数存储 • 单体应用中的 Session 属性管理 | • 仅存在于老旧遗留系统 中 • 新项目应避免使用 | • 高并发缓存(如电商商品库存) • 实时计数器(UV/PV 统计) • 分布式会话共享(配合 Redis 等) |
红黑树 (自平衡二叉搜索)

结构特点
- 每个节点非红即黑
- 根节点必须是黑色
- 红色节点的子节点必须是黑色(不能连续红)
- 从任一节点到其所有叶子的路径,黑色节点数相同 --->(红黑树的"叶子" = 黑色 NIL 节点)
为什么用它?
插入/删除时通过 旋转 + 变色 保持平衡 调整的次数相对较少,通常最多需要 3 次旋转
保证树高 ≤ 2 log₂(n+1) → 操作稳定在 O(log n)
红黑树的操作
添加(找到合适图,我再加上去)
先查找,确定插入位置(原理同二叉排序树)插入新节点
- 如果新节点是根,则染为Blank(黑色)
- 如果新节点非根,则染为Red(红色)
- 如果插入新节点,满足红黑树定义,则插入结束
- 如果插入新节点,不满足红黑树定义,则需要调整(调整的依据:看新节点叔叔的脸色)
1、黑叔 旋转 + 染色
旋转类型 LL、RR、LR、RL
关键就看 你(N)、你爸(P)、你爷(G)三人的站位关系:
| 你和你爸的位置 | 旋转类型 | 口诀关键词 | 染色(只限插入) |
|---|---|---|---|
| 你和你爸都在爷爷 左边 | LL | "左左" | 父升爷,爷降兄,新根染黑两娃红 |
| 你和你爸都在爷爷 右边 | RR | "右右" | 父升爷,爷降兄,新根染黑两娃红 |
| 你在爷爷 左边,但你是你爸的 右娃 | LR | "左右" | 左旋爸,右旋爷,你上C位穿黑靴,左爸右爷穿红鞋 |
| 你在爷爷 右边,但你是你爸的 左娃 | RL | "右左" | 左旋爸,右旋爷,你上C位穿黑靴,左爷右爸穿红鞋 |
先看 你爸在爷爷哪边?(左 or 右)
再看 你在你爸哪边?(左 or 右)
拼起来就是类型:(爸的位置)+(你的位置)
2、红叔 染色 + 变新(叔、父、爷 染色,爷变新节点 )
顺口溜帮助牢记:父叔红,黑黑红,上冒根黑终告成
父叔红 → 爸爸和叔叔都是红色(违规条件)
黑黑红 → 爸爸变黑、叔叔变黑、爷爷变红(核心变色)
上冒根黑成 → 向上冒泡修复,直到根为黑色,大功告成!
删除
后面再补,太复杂了,耽误时间。