Java 集合Collection—Map
HashMap
HashMap = 哈希表实现的键值对集合,key 唯一、value 可重复、允许 null key/value,非线程安全,增删查平均 O(1)。
创建(3 种)
// 1. 默认初始容量 16,负载因子 0.75
Map<String, Integer> map = new HashMap<>();
// 2. 指定初始容量(减少 rehash)
Map<String, Integer> map = new HashMap<>(100);
// 3. 通过已有 Map 快速构造
Map<String, Integer> map = new HashMap<>(existingMap);
基本 CRUD
map.put("apple", 3); // 增/改(key 存在则覆盖)
map.get("apple"); // 查,返回 value 或 null
map.containsKey("apple"); // 判断 key 存在
map.containsValue(3); // 判断 value 存在
map.remove("apple"); // 删,返回旧值或 null
map.size(); // 键值对个数
map.isEmpty(); // 是否空
map.clear(); // 清空
遍历(4 样)
// 1. entrySet(最常用,一次拿 key+value)
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
// 2. keySet / values
for (String k : map.keySet()) System.out.println(k);
for (Integer v : map.values()) System.out.println(v);
// 3. Lambda(Java 8+)
map.forEach((k, v) -> System.out.println(k + "=" + v));
// 4. Iterator(遍历中删除安全)
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> e = it.next();
if (e.getValue() < 1) it.remove();
}
Java 8+ 常用快捷 API
map.getOrDefault("orange", 0); // 不存在返回默认值
map.putIfAbsent("orange", 0); // 不存在才 put
map.merge("apple", 1, Integer::sum); // value 累加:apple=旧值+1
map.computeIfAbsent("banana", k -> loadFromDB(k)); // 不存在则计算并放入
map.computeIfPresent("apple", (k, v) -> v > 5 ? null : v + 1); // 存在则更新/删除
批量操作
Map<String, Integer> map2 = Map.of("k1", 1, "k2", 2); // Java 9 不可变 map
map.putAll(map2); // 批量放入
线程安全替代
// 1. 同步包装(读写全串行)
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// 2. 高并发
ConcurrentHashMap<String, Integer> cmap = new ConcurrentHashMap<>();
cmap.merge("key", 1, Integer::sum); // 原子复合操作
背口诀
“HashMap 键值 O(1),put/get 常用 API;遍历 entrySet 最高效,Java 8 merge 计算快;线程安全用 ConcurrentHashMap”。
HashMap源码
HashMap 的底层逻辑 = “数组+链表+红黑树+哈希扰动+扩容重散列” 五件套,
目标:O(1) 平均读写、高空间利用率、线程不安全、可动态长大。
下面按 哈希→寻址→冲突→树化→扩容 一路跟踪 putVal 源码
哈希扰动:让高位也参与寻址,减少碰撞
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
^ (h >>> 16) 把高 16 位拉下来与低 16 位异或,散列更均匀
成员变量:一眼看懂容量/阈值/树化门槛
transient Node<K,V>[] table; // 哈希桶数组
transient int size; // 实际键值对数
int threshold; // 扩容阈值 = capacity * loadFactor
final float loadFactor; // 默认 0.75
static final int TREEIFY_THRESHOLD = 8; // 链表转红黑树门槛
static final int UNTREEIFY_THRESHOLD = 6; // 退化门槛
static final int MIN_TREEIFY_CAPACITY = 64; // 树化前必须先扩容到 64
懒初始化:第一次 put 才扩容(省内存)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// (1) 空表→扩容到 16(或构造指定值)
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// (2) 寻址:(n - 1) & hash 永远落在 [0, n-1]
i = (n - 1) & hash;
p = tab[i];
冲突处理:链表尾插 → 红黑树
if (p == null) { // 桶空
tab[i] = newNode(hash, key, value, null);
} else { // 冲突
Node<K,V> e; K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p; // 头节点就相等
else if (p instanceof TreeNode) // 已是树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { // 链表尾插
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { // 到达尾部
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // 长度 ≥ 8
treeifyBin(tab, hash); // 树化(≥64 才转树)
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break; // 找到相同 key
p = e;
}
}
if (e != null) { // 旧 key 存在 → 替换 value
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); // 给 LinkedHashMap 用的回调
return oldValue;
}
}
++modCount;
if (++size > threshold) // 元素个数 > 阈值
resize(); // 2 倍扩容 + 重散列
afterNodeInsertion(evict);
return null; // 返回 null 表示新增
}
扩容:2 倍连续数组 + 重散列(高位拆分技巧)
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int newCap = oldCap << 1; // *2
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
threshold = (int)(newCap * loadFactor);
table = newTab;
if (oldTab != null) {
for (Node<K,V> e : oldTab) { // 遍历旧桶
while (e != null) {
Node<K,V> next = e.next;
// 高位拆分:同一条链表分成“低半段”和“高半段”两个桶
if ((e.hash & oldCap) == 0) // 原索引
e.next = newTab[i];
else // 原索引 + oldCap
e.next = newTab[i + oldCap];
newTab[i] = e;
e = next;
}
}
}
return newTab;
}
不需要重新计算 hash,用 (hash & oldCap) 判断高位是 0 还是 1,
一条链表直接拆成两条,CPU 缓存友好。
红黑树退化
-
链表长度 ≤ 6 且 resize 时 退成链表
-
防止 哈希攻击(大量同 hash 字符串)时链表过长
线程安全 & Fail-Fast
-
全程 无锁,多线程并发 put 会 死链/丢数据 → 用 ConcurrentHashMap
-
modCount 计数,迭代器发现变动抛
ConcurrentModificationExceptio
设计思想一句话
“哈希扰动降冲突,数组定位 O(1);链表尾插防死链,≥8&≥64 才树化;2 倍扩容高位拆,重散列缓存友好;无锁快速失败,多线程请换 ConcurrentHashMap
LinkedHashMap
LinkedHashMap = 哈希表 + 双向链表,
既保留 HashMap 的 O(1) 增删查,又支持 插入顺序 或 访问顺序(LRU 基础),key 唯一、允许 null,非线程安全。
创建(3 种)
// 1. 默认:插入顺序(最常用)
Map<String, Integer> map = new LinkedHashMap<>();
// 2. 指定初始容量
Map<String, Integer> map = new LinkedHashMap<>(100);
// 3. 访问顺序模式(第 3 个参数 true → LRU)
Map<String, Integer> lru = new LinkedHashMap<>(16, 0.75f, true);
基本 CRUD(与 HashMap 完全一致)
map.put("apple", 3); // 增/改
map.get("apple"); // 查
map.containsKey("apple"); // 判断 key
map.remove("apple"); // 删
map.size(); // 个数
map.isEmpty(); // 空判断
map.clear(); // 清空
顺序特性演示
Map<String, Integer> map = new LinkedHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
map.forEach((k, v) -> System.out.print(k)); // 输出顺序:A B C(插入序)
访问顺序模式(LRU 雏形)
Map<String, Integer> lru = new LinkedHashMap<>(16, 0.75f, true);
lru.put("A", 1);
lru.put("B", 2);
lru.put("C", 3);
lru.get("A"); // 访问 A
lru.forEach((k, v) -> System.out.print(k)); // 输出:B C A(最近访问放最后)
遍历(顺序保证)
// 1. for-each 插入序/访问序
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
// 2. Lambda
map.forEach((k, v) -> System.out.println(k + "=" + v));
批量操作
Map<String, Integer> map2 = Map.of("k1", 1, "k2", 2); // Java 9 不可变
map.putAll(map2); // 批量放入
线程安全替代
// 1. 同步包装(保序)
Map<String, Integer> syncMap = Collections.synchronizedMap(new LinkedHashMap<>());
// 2. 高并发且需顺序
Map<String, Integer> cow = new ConcurrentHashMap<>();
// 或 ConcurrentSkipListMap(红黑树顺序)
背口诀
“LinkedHashMap 去重又保序,插入顺序默认齐;访问顺序构造传 true,一行代码实现 LRU”。
LinkedHashMap源码
LinkedHashMap = HashMap 的全部能力 + 双向链表保序;
节点带 before/after 指针,插入/访问/删除都维护链表,迭代直接遍历链表(O(n) 且缓存友好),支持 插入顺序 或 LRU 访问顺序,其余增删查仍复用父类 HashMap 的桶逻辑。
下面按 节点结构 → 链表维护 → 顺序模式 → 迭代器 → 性能与内存 5 段源码级解读
节点结构:在 HashMap.Node 基础上再加两个指针
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 双向链表指针
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next); // 保留桶里单链表/树指针
}
}
一个对象同时属于 两条链:
① next/parent 负责桶内哈希冲突链/树
② before/after 负责全局 双向链表(插入序或 LRU 序)
链表头尾指针:全局变量直接定位
transient LinkedHashMap.Entry<K,V> head; // 链表头
transient LinkedHashMap.Entry<K,V> tail; // 链表尾
final boolean accessOrder; // false=插入序(默认),true=访问序
空表时 head = tail = null;单节点时两者指向同一 Entry。
链表维护:HashMap 预留的“回调钩子”被 LinkedHashMap 重写
| 回调钩子 | 用途 | LinkedHashMap 实现 |
|---|---|---|
afterNodeAccess(e) |
访问元素后 | 若为 访问序 把节点移到尾部 |
afterNodeInsertion(evict) |
新增元素后 | 可在这里淘汰最老节点(LRU) |
afterNodeRemoval(e) |
删除元素后 | 把节点从双向链表摘掉 |
插入回调(尾插法,保插入序)
void afterNodeInsertion(boolean evict) { // evict 由 HashMap 构造参数传入
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
// 子类可重写 removeEldestEntry 做容量控制
removeNode(hash(first.key), first.key, null, false, true);
}
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
访问回调(访问序才生效)
void afterNodeAccess(Node<K,V> e) {
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) { // 非尾节点才移动
LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;
LinkedHashMap.Entry<K,V> b = p.before, a = p.after;
p.after = null; // 先摘下
if (b == null) head = a; else b.after = a;
if (a != null) a.before = b; else last = b;
// 再尾插
p.before = last;
if (last != null) last.after = p; else head = p;
tail = p;
}
}
每次 get/put 已存在 key 都会触发 afterNodeAccess,把节点移到尾部 = 最近使用。
迭代器:不再遍历桶,直接沿链表走
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount) throw new ConcurrentModificationException();
next = e.after; // 顺着 after 指针一路向后
return e;
}
迭代时间与 size 成正比,且 内存连续访问(Node 对象在堆中相邻概率高),CPU 缓存友好 → 比 HashSet/HashMap 的“桶跳跃”遍历快。
性能与内存开销
| 维度 | HashMap | LinkedHashMap |
|---|---|---|
| 增删查 | O(1) 均摊 | 同 HashMap(多几次指针赋值,可忽略) |
| 迭代 | 桶跳跃,缓存不友好 | 链表顺序,缓存友好 |
| 内存 | 1 个 Node | Node + 2 个指针(24 byte 64 位压缩) |
| 树化/退化 | 与父类完全一致 | 同一套逻辑 |
典型用法:一行实现 LRU
Map<String, Object> lru = new LinkedHashMap<>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES; // 超过容量淘汰最老
}
};
访问序 + removeEldestEntry 回调 = 标准 LRU 缓存模板。
设计思想一句话
“在 HashMap 节点上长两条链:桶链解决哈希冲突,before/after 链保全局顺序;重写父类三个回调,插入/访问/删除时维护链表;迭代直接沿链表跑,缓存友好;加 2 个指针换来有序遍历 + LRU 能力。”
ConcurrentHashMap
ConcurrentHashMap = 高并发、线程安全的哈希表,
JDK 8 起采用 "数组 + 链表 + 红黑树 + CAS + synchronized" 分段锁思想,
读操作几乎无锁,写操作 锁单个桶,并发度 ≈ 桶数;支持原子复合 API。
创建(3 种)
// 1. 默认并发级别 16
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 2. 指定初始容量
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(100);
// 3. 通过已有 Map 快速构造
ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>(existingMap);
基本 CRUD(与 HashMap 类似,但线程安全)
map.put("apple", 3); // 增/改
map.get("apple"); // 查,无锁读
map.remove("apple"); // 删
map.containsKey("apple"); // 判断 key
map.size(); // 近似值(弱一致)
map.isEmpty(); // 弱一致
map.clear(); // 清空
原子复合 API(高并发神器)
// 1. 不存在才 put
map.putIfAbsent("apple", 0);
// 2. 原子累加
map.merge("apple", 1, Integer::sum); // apple = 旧值 + 1
// 3. 原子计算并放入
map.computeIfAbsent("banana", k -> loadFromDB(k));
// 4. 存在则计算
map.computeIfPresent("apple", (k, v) -> v > 5 ? null : v + 1); // 返回 null 会删除
// 5. 原子替换
map.replace("apple", 3, 4); // 仅当旧值=3 时才替换为 4
批量操作(线程安全)
// 批量 put
Map<String, Integer> batch = Map.of("k1", 1, "k2", 2);
map.putAll(batch);
// 原子 merge 批量
batch.forEach((k, v) -> map.merge(k, v, Integer::sum));
遍历(弱一致,不会抛 ConcurrentModificationException)
// 1. for-each
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
// 2. Lambda
map.forEach((k, v) -> System.out.println(k + "=" + v));
// 3. 高级 forEach 并行(利用多 CPU)
map.forEach(4, (k, v) -> System.out.println(Thread.currentThread().getName() + " " + k));
原子读-改-写(例子:计数器)
// 线程安全计数器一行搞定
map.merge("requestCount", 1, Integer::sum);
线程安全说明
-
读操作无锁(volatile + CAS)
-
写操作只锁单个桶(synchronized 锁住首节点)
-
弱一致迭代器:遍历的是某一时刻的快照,允许并发修改,不会抛 ConcurrentModificationException
性能小贴士
初始容量适当设置,减少 rehash
并发度默认 16,一般无需修改
使用 merge、computeIfAbsent 等原子 API 避免手动 synchronized
背口诀
“ConcurrentHashMap 读无锁写锁桶,原子 merge 计算快,弱一致迭代不抛异常,高并发首选它”。
ConcurrentHashMap源码
ConcurrentHashMap(JDK 8 之后)= “数组 + 链表 + 红黑树 + CAS + synchronized 桶锁”
目标:读操作几乎无锁,写操作只锁单个桶,并发度 ≈ 桶数,线程安全且比 Hashtable 快一个量级。
下面按 内存布局 → 无锁读 → 桶锁写 → 扩容 → 计数器 → 并发
核心字段:sizeCtl 多角色控制并发
transient volatile Node<K,V>[] table; // 主桶数组
private transient volatile int sizeCtl; // 负值表示正在扩容/-1 表示初始化
static final int MOVED = -1; // ForwardingNode 的 hash
static final int TREEBIN = -2; // 红黑树根节点的 hash
static final int RESERVED = -3; // 临时保留节点的 hash
sizeCtl 在不同阶段扮演 阈值、扩容戳、初始化锁 三重身份,是并发协调的核心信号量
无锁读:get 全程不加锁,靠 volatile + CAS 保证可见性
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode()); // 再哈希
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) { // volatile 读桶头
if ((eh = e.hash) == h) { // 头节点命中
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0) // 树或 ForwardingNode
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) { // 链表遍历
if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null; // 未找到
}
tabAt 底层 = Unsafe.getObjectVolatile → CPU 级别可见性,无需 synchronized
桶锁写:put 只锁头节点,粒度 = 1 个桶
final V putVal(K key, V value, boolean onlyIfAbsent) {
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable(); // 懒初始化
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 桶为空 → CAS 插入
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break; // CAS 成功退出
}
else if ((fh = f.hash) == MOVED) // 发现扩容戳 → 帮忙迁移
tab = helpTransfer(tab, f);
else { // 桶已存在 → 锁头节点
V oldVal = null;
synchronized (f) { // ① 只锁一个桶头
if (tabAt(tab, i) == f) { // 二次校验
if (fh >= 0) { // 链表情况
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent) e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value, null);
break;
}
}
}
else if (f instanceof TreeBin) { // 红黑树情况
Node<K,V> p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value);
if (p != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; }
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); // ≥8 转树
if (oldVal != null) return oldVal;
break;
}
}
}
addCount(1L, binCount); // ② 原子计数 + 扩容检查
return null;
}
CAS 空桶 → 失败才退化到 synchronized 桶头,绝大多数并发落在 CAS 阶段,锁竞争极少。
扩容:多线程协同迁移(ForwardingNode 标记)
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length); // 扩容戳
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { // 线程+1
transfer(tab, nextTab); // 帮忙搬运
break;
}
}
return nextTab;
}
return table;
}
把桶区间 分段分配给多个线程并行迁移,CPU 核数越多扩容越快。
原子计数:LongAdder 思想,分段累加
private final CounterCell[] counterCells; // 分片计数器
final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended); // 高并发下扩容计数分片
return;
}
}
if (check <= 1) return;
if ((s = sumCount()) >= (long)(sc = sizeCtl) &&
tab != null) {
if (sc < 0) { /* 正在扩容,协助 */ }
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
transfer(tab, null); // 触发扩容
}
}
}
size() 返回 分片求和,弱一致但 无锁。
并发复合 API:全程桶锁或 CAS 保证原子
V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)
V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
boolean replace(K key, V oldValue, V newValue)
实现逻辑与 putVal 相同,同一桶锁内完成 判断+计算+更新,不会被打断。
设计思想一句话
“读用 volatile+CAS 无锁,写只锁单个桶;CAS 空桶失败才 synchronized 头节点,链表过长转红黑树;2 倍扩容多线程协同搬运,分片计数保证 size 弱一致;复合 API 同锁原子,并发度≈桶数,读远大于写性能爆表。”
使用场景
| 实现 | 线程安全 | 顺序 | 读写性能 | 典型场景一句话 |
|---|---|---|---|---|
| HashMap | ❌ | 无序 | 最快 O(1) | 单线程只追求速度 |
| LinkedHashMap | ❌ | ✅插入/访问序 | 略慢于 HashMap | 需要保序(LRU、配置回写、JSON 序列化) |
| ConcurrentHashMap | ✅高并发 | 无序 | 读≈无锁,写锁桶 | 多线程共享且读远大于写 |
HashMap —— “默认选项”
单线程、顺序无所谓、要极致速度:缓存、计数器、算法临时表。
Map<ID, User> cache = new HashMap<>(1_000_000); // 足够快
LinkedHashMap —— “保序神器”
插入顺序:导出/序列化保持用户原始顺序
Map<String, Object> jsonMap = new LinkedHashMap<>(); // 转 JSON 不乱
访问顺序(LRU 缓存基础)
Map<Key, Data> lru = new LinkedHashMap<Key, Data>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<Key, Data> eldest) {
return size() > MAX; // 超过容量自动淘汰最老
}
};
ConcurrentHashMap —— “并发基石”
多线程共享且读远大于写:计数器、本地缓存、开关映射。
private final ConcurrentHashMap<String, LongAdder> counter =
new ConcurrentHashMap<>();
// 线程安全累加一行搞定
counter.computeIfAbsent(key, k -> new LongAdder()).increment();
原子复合 API 替代 synchronized
map.merge(k, 1, Integer::sum); // 无锁自增
map.computeIfAbsent(k, this::loadFromDB); // 懒加载
决策树(30 秒拍板)
单线程?
├─ 需要保序 → LinkedHashMap
└─ 无序 → HashMap
多线程?
└─ ConcurrentHashMap(直接选,别用 Collections.synchronizedMap)
速记口诀
“单线程无序 HashMap,保序 LinkedHashMap;多线程无脑 ConcurrentHashMap”。
更多推荐

所有评论(0)