HashMap 一篇讲透:从数组、链表、红黑树到扩容以及退化,面试再也不怕被追问

HashMap 一篇讲透:从数组、链表、红黑树到扩容,面试再也不怕被追问

如果 Java 面试只能挑一个集合类往死里问,那大概率就是 HashMap

很多人背过这样的答案:

JDK 8 的 HashMap 底层是数组 + 链表 + 红黑树,链表长度达到 8 会转红黑树,容量不足 64 会先扩容,负载因子默认 0.75。

这段话没错。

但问题是,面试官通常不会停在这里。

他很可能继续问:

  • 为什么数组长度一定是 2 的幂?
  • 为什么不是链表长度达到 8 就直接树化?
  • HashMap 到底什么时候扩容?
  • 扩容以后原来的节点去哪了?
  • 为什么 JDK 8 扩容不用重新计算完整 Hash?
  • 红黑树什么时候又退化成链表?
  • 为什么树化是 8,退化却是 6?
  • 为什么负载因子偏偏是 0.75?
  • put() 从进去到结束到底经历了什么?

如果这些问题只能靠背,很容易问两层就断了。

其实 HashMap 没那么玄学。

只要把它当成一个不断在"查询效率、空间占用、冲突概率、维护成本"之间做平衡的数据结构,很多设计一下就能串起来。

这篇文章不打算带着你逐行啃源码,而是用一条完整的数据演化链,把 HashMap 真正讲明白。


一、先别管红黑树:HashMap 最核心的东西其实只有一个数组

先看最简单的 HashMap。

java 复制代码
HashMap<String, String> map = new HashMap<>();
map.put("name", "阿豪");
map.put("age", "28");

很多人听到 Map,会下意识觉得它的底层一定存在某种神奇的 Key-Value 映射结构。

其实最核心的东西就是:

java 复制代码
Node<K,V>[] table;

也就是一个数组。

可以先把 HashMap 想象成一排储物柜:

text 复制代码
              HashMap.table

下标
 0    [ null ]
 1    [ null ]
 2    [ Node ]
 3    [ null ]
 4    [ Node ]
 5    [ null ]
 6    [ null ]
 7    [ Node ]
 ...
15    [ null ]

数组里的每一个位置通常称为一个 桶(Bucket)

Node 中则保存:

java 复制代码
static class Node<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

你会发现它除了:

text 复制代码
hash
key
value

之外,还有一个非常关键的东西:

text 复制代码
next

这意味着 Node 天生就可以串成链表。

为什么要有链表?

因为 HashMap 无法保证两个不同的 Key 永远不会进入同一个数组位置。

这就是 Hash 冲突。


二、一个 Key 是怎么找到数组位置的?

假设:

java 复制代码
map.put("name", "阿豪");

HashMap 不可能遍历整个数组寻找空位置。

它首先会根据 Key 得到一个 Hash 值。

JDK 8 中可以简化理解成:

java 复制代码
int hash = key.hashCode();

实际上 HashMap 还会进行一次扰动:

java 复制代码
(h = key.hashCode()) ^ (h >>> 16)

也就是:

text 复制代码
原始 hashCode
      ↓
高 16 位参与扰动
      ↓
得到新的 hash

为什么要这么干?

因为最终计算数组位置的时候使用的是:

java 复制代码
(n - 1) & hash

如果数组比较小,真正决定数组下标的主要是 Hash 值的低位。

如果一个对象:

text 复制代码
高位变化很大
低位却高度相似

直接使用的话就容易发生碰撞。

所以 JDK 8 做了一次:

java 复制代码
hash ^ (hash >>> 16)

让高位信息也参与到低位计算中。

最后定位桶:

java 复制代码
index = (n - 1) & hash;

比如数组容量:

text 复制代码
n = 16

那么:

text 复制代码
index = hash & 15

最终得到一个 0~15 的下标。

整个过程就是:

text 复制代码
              Key
               │
               ▼
          hashCode()
               │
               ▼
       h ^ (h >>> 16)
               │
               ▼
       (n - 1) & hash
               │
               ▼
        得到数组下标
               │
               ▼
          table[index]

这就是 HashMap 为什么平均情况下查询速度能够接近 O(1)。

因为它不是:

text 复制代码
从 table[0] 一路找

而是:

text 复制代码
Key
 ↓
Hash
 ↓
直接定位 table[13]

三、真正的问题来了:两个 Key 算出了同一个位置怎么办?

假设:

text 复制代码
Key A → table[5]
Key B → table[5]
Key C → table[5]

数组的 table[5] 总不能同时保存三个独立数组元素。

于是 HashMap 使用链表解决冲突。

最开始:

text 复制代码
table[5]
   │
   ▼
   A

后来 B 也来了:

text 复制代码
table[5]
   │
   ▼
   A ──→ B

C 又来了:

text 复制代码
table[5]
   │
   ▼
   A ──→ B ──→ C

于是整个 HashMap 就从:

数组

变成了:

数组 + 链表

注意,这不是说 HashMap 整体变成了一条链表。

而是:

数组仍然是主体,只不过发生 Hash 冲突的某个桶内部使用链表保存多个节点。

例如:

text 复制代码
                     HashMap

table[0] ──→ null

table[1] ──→ A

table[2] ──→ null

table[3] ──→ B ──→ C ──→ D

table[4] ──→ E

table[5] ──→ null

table[6] ──→ F ──→ G

所以理解 HashMap 的第一个关键点就是:

数组负责快速定位,链表负责解决 Hash 冲突。


四、put() 一个元素,到底经历了什么?

现在把整个 put() 流程展开。

假设:

java 复制代码
map.put("name", "阿豪");

首先计算:

text 复制代码
key
 ↓
hash
 ↓
数组下标 index

然后查看:

java 复制代码
table[index]

情况一:这个桶是空的

例如:

text 复制代码
table[6] → null

那最简单:

text 复制代码
table[6]
   │
   ▼
 Node(name, 阿豪)

直接创建 Node 放进去。


情况二:桶里面已经有元素

例如:

text 复制代码
table[6]
   │
   ▼
   A

这时候不能直接添加。

HashMap 首先需要判断:

当前 Key 和已有 Key 是不是同一个 Key?

判断会涉及:

java 复制代码
hash

以及:

java 复制代码
equals()

如果 Key 相同:

java 复制代码
map.put("name", "张三");
map.put("name", "李四");

第二次不会创建两个 "name"

而是:

text 复制代码
name → 张三

更新为:

text 复制代码
name → 李四

也就是覆盖旧 Value。


情况三:Key 不相同,只是 Hash 冲突

那就需要继续寻找。

如果当前桶是链表:

text 复制代码
A → B → C

就沿着链表检查。

最终没有找到相同 Key:

text 复制代码
A → B → C → D

JDK 8 使用尾插方式加入新节点。

但是问题很快就来了。

如果冲突越来越严重:

text 复制代码
A → B → C → D → E → F → G → H → I → J → K...

HashMap 的性能是不是又退化了?

没错。


五、为什么链表长了以后必须考虑红黑树?

数组定位非常快。

理论上:

text 复制代码
O(1)

但如果大量 Key 都落在同一个桶:

text 复制代码
table[5]

A
↓
B
↓
C
↓
D
↓
E
↓
F
↓
...

那你虽然 O(1) 找到了:

text 复制代码
table[5]

却还得继续在里面找。

链表查询复杂度:

text 复制代码
O(n)

如果极端情况下 1000 个节点全部进入一个桶:

text 复制代码
A → B → C → ... → 第 1000 个

最坏可能接近遍历 1000 个节点。

这时候:

HashMap 外面看起来还是 HashMap,里面已经快查成 LinkedList 了。

于是 JDK 8 引入了红黑树。

红黑树查找复杂度:

text 复制代码
O(log n)

1000 个节点:

text 复制代码
链表:最坏接近 1000 次

红黑树:
log₂(1000) ≈ 10

这就是非常巨大的差距。

所以 JDK 8 的 HashMap 实际结构应该描述成:

数组 + 链表 + 红黑树。


六、是不是链表达到 8 就一定变红黑树?

这是 HashMap 面试最大的坑之一。

很多人的答案:

链表长度达到 8 就转红黑树。

不完整。

HashMap 中有两个关键参数:

java 复制代码
TREEIFY_THRESHOLD = 8;

MIN_TREEIFY_CAPACITY = 64;

树化除了链表足够长,还必须考虑:

text 复制代码
table.length

完整逻辑应该理解成:

text 复制代码
                  链表变长
                     │
                     ▼
              达到树化条件?
                     │
              ┌──────┴──────┐
              │             │
             否             是
              │             │
           保持链表          ▼
                       数组容量 >= 64?
                         │       │
                        否       是
                         │       │
                         ▼       ▼
                       扩容    红黑树化

也就是说:

链表够长 + 数组容量小于 64

HashMap:

先扩容。

链表够长 + 数组容量至少 64

HashMap:

才真正考虑树化。

为什么?

因为红黑树不是免费的。


七、为什么容量小的时候宁愿扩容,也不愿直接上红黑树?

假设当前容量只有:

text 复制代码
16

结果某个桶已经非常拥挤:

text 复制代码
table[3]

A → B → C → D → E → F → G → H

这时候有两种可能。

第一种:

这些对象的 Hash 真的特别烂。

第二种:

数组本身太小了。

如果是第二种情况,最合理的方法根本不是建红黑树,而是把数组扩大:

text 复制代码
16
 ↓
32
 ↓
64

数组扩大之后,原来挤在一个桶里的节点可能自然分散。

例如:

text 复制代码
扩容前:

table[5]

A → B → C → D → E → F

扩容后:

text 复制代码
table[5]

A → C → F


table[21]

B → D → E

原来的长链表自己就被拆开了。

这时候再建红黑树反而浪费。

所以 HashMap 的思路其实很聪明:

空间不够,优先扩空间;空间已经够大,冲突依然严重,才使用更复杂的数据结构。

这句话比死背:

text 复制代码
8、64

重要得多。


八、红黑树到底解决了什么?

树化前:

text 复制代码
table[5]
   │
   ▼
   A
   │
   ▼
   B
   │
   ▼
   C
   │
   ▼
   D
   │
   ▼
   E
   │
   ▼
   F
   │
   ▼
   G
   │
   ▼
   H

树化后可以抽象成:

text 复制代码
                  D
               /     \
              B       F
             / \     / \
            A   C   E   H
                       /
                      G

链表查询:

text 复制代码
O(n)

红黑树查询:

text 复制代码
O(log n)

但为什么 HashMap 不干脆从第一天开始:

每个桶全部用红黑树?

因为红黑树有成本。

Node 本来只需要:

text 复制代码
hash
key
value
next

而 TreeNode 还需要维护类似:

text 复制代码
parent
left
right
prev
red

插入、删除的时候还可能涉及:

text 复制代码
旋转
变色
重新平衡

因此当一个桶里只有:

text 复制代码
2~3 个节点

的时候,链表反而更加轻量。

这就是一个非常典型的工程思想:

数据少的时候选择简单结构,数据冲突严重的时候再切换复杂结构。


九、HashMap 到底什么时候扩容?

现在讲第二个核心机制:

text 复制代码
resize()

HashMap 不可能一直使用一个长度为 16 的数组。

否则你往里面放:

text 复制代码
10 万个元素

冲突会越来越严重。

所以 HashMap 有一个非常重要的概念:

text 复制代码
threshold

扩容阈值大体可以理解成:

text 复制代码
threshold = capacity × loadFactor

默认负载因子:

java 复制代码
loadFactor = 0.75f;

如果容量:

text 复制代码
16

那么阈值:

text 复制代码
16 × 0.75 = 12

也就是说大体可以理解成:

text 复制代码
容量:16
阈值:12

size 超过 12
       │
       ▼
     resize
       │
       ▼
容量:32

继续:

text 复制代码
32 × 0.75 = 24

于是整个容量变化大致是:

text 复制代码
16
 │
 ▼
32
 │
 ▼
64
 │
 ▼
128
 │
 ▼
256
 │
 ▼
512
...

每次基本翻倍。


十、为什么负载因子偏偏是 0.75?

这是一个非常适合面试继续追问的问题。

为什么不是:

text 复制代码
0.5

也不是:

text 复制代码
1.0

其实它是在:

空间利用率和 Hash 冲突概率之间做平衡。

如果负载因子特别小,比如:

text 复制代码
0.2

16 个位置只放几个元素就扩容。

冲突确实少了。

但是:

text 复制代码
大量数组位置一直是 null

空间浪费严重。

反过来,如果:

text 复制代码
loadFactor = 1

甚至更高:

数组塞得越来越满。

空间利用率高了,但:

text 复制代码
Hash 冲突概率 ↑
链表长度 ↑
查询成本 ↑

所以默认的:

text 复制代码
0.75

就是一个工程上的折中。

可以把它理解成:

text 复制代码
空间浪费 ◄──────────────► Hash 冲突

          0.75
           ↑
        平衡位置

不是一个神秘数字,而是时间和空间之间的 trade-off。


十一、HashMap 为什么要求容量是 2 的幂?

这是 HashMap 最漂亮的设计之一。

假设容量:

text 复制代码
16

二进制:

text 复制代码
0001 0000

那么:

text 复制代码
n - 1 = 15

二进制:

text 复制代码
0000 1111

HashMap 计算位置:

java 复制代码
index = hash & (n - 1);

比如:

text 复制代码
hash:

1011 0101

与:

text 复制代码
0000 1111

进行 AND:

text 复制代码
1011 0101
&
0000 1111
-----------
0000 0101

结果:

text 复制代码
5

于是进入:

text 复制代码
table[5]

这和:

java 复制代码
hash % 16

在这种情况下能达到类似取模效果。

但是:

text 复制代码
位运算通常非常高效

更重要的是,2 的幂还能让 HashMap 扩容时完成一个非常漂亮的优化。


十二、HashMap 扩容为什么不用重新计算完整位置?

假设原数组长度:

text 复制代码
16

扩容后:

text 复制代码
32

原位置:

java 复制代码
hash & 15

新位置:

java 复制代码
hash & 31

二进制看:

text 复制代码
15 = 0000 1111

31 = 0001 1111

你会发现:

新计算只比原来多看了一位。

因此扩容后,一个元素的新位置只有两种情况:

第一种:位置不变

text 复制代码
oldIndex

第二种:移动 oldCap

text 复制代码
oldIndex + oldCap

比如原来:

text 复制代码
table[5]

扩容:

text 复制代码
16 → 32

那么这些节点的新位置只可能:

text 复制代码
5

或者:

text 复制代码
5 + 16 = 21

判断方式就是:

java 复制代码
(hash & oldCap) == 0

十三、扩容时一条链表是怎么被拆开的?

这个地方非常值得画图。

假设扩容前:

text 复制代码
capacity = 16

table[5]

A → B → C → D → E → F

HashMap 遍历这些节点。

根据:

java 复制代码
(hash & oldCap)

将它们分成两组。

第一组:

text 复制代码
lo 链表

第二组:

text 复制代码
hi 链表

于是:

text 复制代码
扩容前:

table[5]
   │
   ▼
A → B → C → D → E → F


             resize:16 → 32


         ┌───────────────┐
         │               │
         ▼               ▼

      lo 链表          hi 链表

     A → C → E        B → D → F
         │                │
         ▼                ▼
     table[5]         table[21]

注意:

text 复制代码
21 = 5 + 16

这就是 JDK 8 resize 很漂亮的地方。

不是重新:

text 复制代码
重新算 Hash
重新取模
重新乱七八糟分配

而是:

根据新增的那一位,把原桶拆成 low 和 high 两组。


十四、红黑树会不会永远都是红黑树?

不会。

HashMap 不是:

text 复制代码
链表 → 红黑树

以后就再也回不去了。

还有一个重要常量:

java 复制代码
UNTREEIFY_THRESHOLD = 6;

可以简单记:

text 复制代码
TREEIFY_THRESHOLD   = 8

UNTREEIFY_THRESHOLD = 6

也就是:

text 复制代码
节点较多
   │
   ▼
红黑树
   │
节点减少
   ▼
重新变回链表

注意,源码中的实际退化判断还会结合删除、扩容拆树后的具体树结构,并不是所有场景都能机械理解成"size 一到 6 立即退化"。

但面试理解层面可以抓住核心:

节点少了以后继续维护红黑树已经不划算,所以 HashMap 会在适当条件下重新链表化。


十五、为什么树化是 8,退化却是 6?

如果两个阈值都是:

text 复制代码
8

会发生什么?

假设节点数量不停变化:

text 复制代码
7
↓
8
↓
7
↓
8
↓
7
↓
8

那么数据结构可能不断:

text 复制代码
链表
 ↓
红黑树
 ↓
链表
 ↓
红黑树
 ↓
链表

这会产生大量没有意义的结构转换。

所以 HashMap 故意留出一个缓冲区:

text 复制代码
6             8
│-------------│
   缓冲区域

可以把它理解成一种"防抖"。

和很多系统设计其实是一个思想。

比如 CPU 温度控制如果:

text 复制代码
80℃ 开风扇
80℃ 关风扇

温度在 79.9 和 80.1 之间波动时,风扇可能不停:

text 复制代码
开
关
开
关

更合理的是:

text 复制代码
80℃ 开

70℃ 再关

HashMap 的:

text 复制代码
8 / 6

本质上也有类似思想:

避免临界值附近反复横跳。


十六、所以 HashMap 的完整数据结构到底是什么?

到这里,我们终于可以把整张图画出来。

text 复制代码
                         HashMap
                            │
                            ▼
                     Node[] table
                            │
       ┌────────────────────┼────────────────────┐
       │                    │                    │
       ▼                    ▼                    ▼
    table[1]             table[5]             table[9]
       │                    │                    │
       ▼                    ▼                    ▼
     Node A              Node B              TreeNode
                            │                    │
                            ▼                    ▼
                         Node C                 D
                            │                 /   \
                            ▼                B     F
                         Node D             / \   / \
                                           A  C  E  G


       单节点               链表               红黑树

因此所谓:

JDK 8 HashMap = 数组 + 链表 + 红黑树

并不是三个东西平级混在一起。

更准确的说法应该是:

HashMap 最外层永远是数组;数组的每一个桶,根据冲突情况,可能是空、单节点、链表或者红黑树结构。

这个理解非常重要。


十七、把 put() 的完整流程一张图串起来

如果只允许背一张图,我建议背下面这张。

text 复制代码
                        map.put(key, value)
                                 │
                                 ▼
                           计算 key 的 hash
                                 │
                                 ▼
                       index = (n - 1) & hash
                                 │
                                 ▼
                         找到 table[index]
                                 │
                    ┌────────────┴────────────┐
                    │                         │
                  桶为空                    桶不为空
                    │                         │
                    ▼                         ▼
                 直接插入                首节点 Key 相同?
                                              │
                                  ┌───────────┴───────────┐
                                  │                       │
                                 是                       否
                                  │                       │
                                  ▼                       ▼
                              覆盖 value            当前是红黑树?
                                                          │
                                               ┌──────────┴──────────┐
                                               │                     │
                                              是                     否
                                               │                     │
                                               ▼                     ▼
                                           树中查找/插入          遍历链表
                                                                     │
                                                          ┌──────────┴─────────┐
                                                          │                    │
                                                      找到相同 Key          没找到
                                                          │                    │
                                                          ▼                    ▼
                                                      覆盖 value            尾插节点
                                                                               │
                                                                               ▼
                                                                       链表达到树化条件?
                                                                               │
                                                                    ┌──────────┴──────────┐
                                                                    │                     │
                                                                   否                     是
                                                                    │                     │
                                                                  完成             table.length >= 64?
                                                                                          │
                                                                               ┌──────────┴─────────┐
                                                                               │                    │
                                                                              否                    是
                                                                               │                    │
                                                                               ▼                    ▼
                                                                             扩容                红黑树化

                                      插入完成
                                         │
                                         ▼
                                 size 是否超过 threshold
                                         │
                              ┌──────────┴──────────┐
                              │                     │
                             否                     是
                              │                     │
                              ▼                     ▼
                            结束                  resize()

你会发现:

HashMap 根本不是一堆毫无关系的八股文。

它实际上是一条非常完整的逻辑链。


十八、真正理解 HashMap,只需要记住三个"矛盾"

如果不想死背源码,可以记住 HashMap 一直在解决三个问题。

第一个矛盾:查询速度 vs Hash 冲突

数组:

text 复制代码
查询快

但是:

text 复制代码
Hash 冲突无法避免

于是:

text 复制代码
数组 + 链表

第二个矛盾:链表简单 vs 查询退化

链表:

text 复制代码
结构简单
空间成本低

但是太长:

text 复制代码
O(n)

于是:

text 复制代码
链表 → 红黑树

把极端查询优化到:

text 复制代码
O(log n)

第三个矛盾:空间利用率 vs 冲突概率

数组太空:

text 复制代码
浪费内存

数组太满:

text 复制代码
Hash 冲突严重

所以:

text 复制代码
loadFactor = 0.75

在二者之间找平衡。

所以你会发现:

HashMap 几乎所有设计,都不是为了追求某一个指标的极致,而是在时间、空间和实现复杂度之间做工程折中。

这才是理解 HashMap 最重要的地方。


十九、面试官问"HashMap 底层原理",怎么回答最漂亮?

如果面试让我回答,我不会一上来背源码。

我会这么说:

JDK 8 的 HashMap 最外层本质上是 Node 数组,通过 Key 的 hash 值与数组长度减一进行位运算来确定桶的位置,所以正常情况下能够实现接近 O(1) 的查询。

当不同 Key 定位到同一个桶时就产生 Hash 冲突,HashMap 会通过链表保存这些节点。随着冲突增加,如果链表过长,链表查询会退化到 O(n),所以 JDK 8 引入了红黑树,将极端情况下的查询复杂度优化到 O(log n)。

不过 HashMap 并不是链表一长就直接树化。当达到树化阈值时,如果数组容量还小于 64,会优先扩容,因为很多冲突可能只是数组容量太小造成的;只有容量足够大而冲突仍然严重时,才值得维护红黑树。

HashMap 默认负载因子是 0.75,当 size 超过扩容阈值后会进行 resize,容量通常扩大为原来的两倍。因为容量始终保持为 2 的幂,扩容以后节点的新位置只可能是原位置或者原位置加 oldCap,因此 JDK 8 可以通过 (hash & oldCap) 高效完成节点迁移。

红黑树节点减少后,在合适条件下又可以退化为链表,从而避免节点很少时仍承担红黑树额外的空间和维护成本。

所以 HashMap 的整个设计,本质上是在查询效率、Hash 冲突、空间利用率以及复杂数据结构维护成本之间不断做平衡。

如果这一段能真正理解,而不是背出来,HashMap 基础原理这一轮基本就站住了。


二十、最后用 10 句话彻底记住 HashMap

面试前没时间了,就看这里:

  1. HashMap 最外层本质是 Node 数组。
  2. Key 经过 Hash 扰动后,通过 (n - 1) & hash 定位桶。
  3. 不同 Key 进入同一个桶就是 Hash 冲突。
  4. JDK 8 使用链表保存同一个桶里的多个普通节点。
  5. 链表过长会导致查询从理想 O(1) 向 O(n) 退化。
  6. 达到树化条件且数组容量至少为 64 时,可以转换为红黑树,把桶内查询优化到 O(log n)。
  7. 容量不足 64 时优先扩容,而不是急着树化。
  8. 默认负载因子是 0.75,size 超过 threshold 后通常触发扩容。
  9. 容量通常按 2 倍增长,节点扩容后只需要判断留在 oldIndex,还是移动到 oldIndex + oldCap。
  10. 红黑树节点减少后可在适当条件下退化为链表,树化阈值 8、退化参考阈值 6,避免频繁转换。

最后你再回头看这几个数字:

text 复制代码
默认初始容量:16

默认负载因子:0.75

树化阈值:8

退化阈值:6

最小树化容量:64

扩容倍率:2 倍

这时候它们就不应该再是六个需要死记硬背的数字了。

它们背后其实只有一个设计原则:

text 复制代码
                     HashMap

                        │
          ┌─────────────┼─────────────┐
          │             │             │
          ▼             ▼             ▼
       查询效率       空间利用率      冲突处理
          │             │             │
          └─────────────┼─────────────┘
                        │
                        ▼
                     动态平衡
                        │
        ┌───────────────┼───────────────┐
        ▼               ▼               ▼
       数组            链表           红黑树
      O(1)            O(n)          O(log n)
        │               │               │
        └───────────────┼───────────────┘
                        ▼
             resize / treeify / untreeify

HashMap 真正厉害的地方,从来不是"用了红黑树"。

而是它知道:

什么时候数组最划算,什么时候链表够用,什么时候应该花成本上红黑树,又什么时候应该扩容让数据重新分散。

这其实也是很多优秀系统设计的共同思想:

简单结构能解决的问题,不要过早复杂化;当规模真正上来以后,再用更高成本的结构换取稳定的性能。

理解了这一点,HashMap 就不再是一道需要背几十个源码细节的八股文,而是一套非常漂亮的工程设计。

相关推荐
用户8132679332538 分钟前
Python 计算盘中 VWAP:为什么 1 分钟 K 线是量化工程中的高性价比选择
后端·算法·github
风曳丷40 分钟前
07|Instruction Hierarchy 不是安全边界
后端
灯澜忆梦41 分钟前
【基于GO的Web开发11】gin获取URL‑Path 路径参数
前端·后端·golang·html·gin
掘金者阿豪41 分钟前
密码不想继续交给浏览器保存?群晖部署 Vaultwarden,搭建自己的密码库
后端
高频因子挖掘机42 分钟前
Python批量获取1000只ETF 5分钟K线:从分页到吞吐量优化的QuantDash实践
后端·算法·github
Rain的Java大神之路42 分钟前
SkyWalking从0-1部署成功实战
java·后端·架构
就叫飞六吧43 分钟前
Spring 动态注册与移除 Bean 科普
java·后端·spring
用户81818706274644 分钟前
第11章 一次线上Full GC频繁的完整排查记录:从监控告警到根因定位
后端