Skip to content

Map 选型对比:从问题到方案

先问自己三个问题

拿到需求,不要急着写代码。先回答这三个问题:

问题 1:需要排序吗?
  └─ 需要 → TreeMap / ConcurrentSkipListMap
  └─ 不需要 → 问题 2

问题 2:需要保持顺序吗?
  └─ 需要 → LinkedHashMap
  └─ 不需要 → HashMap / ConcurrentHashMap

问题 3:需要并发安全吗?
  └─ 需要 → ConcurrentHashMap / ConcurrentSkipListMap
  └─ 不需要 → HashMap / TreeMap / LinkedHashMap

六种 Map 一张表看懂

实现类底层有序性key/value null线程安全适用场景
HashMap数组+链表/红黑树无序✅ / ✅通用场景
LinkedHashMapHashMap+链表插入顺序✅ / ✅保持顺序
TreeMap红黑树排序❌ / ✅需要排序
Hashtable数组+链表无序❌ / ❌✅(全局锁)遗留代码
ConcurrentHashMapCAS+分段锁无序❌ / ❌✅(分段)高并发
WeakHashMapHashMap+弱引用无序✅ / ✅缓存

HashMap:通用场景的首选

适用场景:不需要顺序、不需要排序,纯粹 key-value 映射。

java
// 场景 1:用户信息缓存
Map<String, User> userCache = new HashMap<>();
userCache.put(userId, fetchUser(userId));

// 场景 2:单词计数
Map<String, Integer> wordCount = new HashMap<>();
for (String word : text.split("\\s+")) {
    wordCount.merge(word, 1, Integer::sum);
}

// 场景 3:对象映射
Map<Long, Product> productMap = new HashMap<>();
Product p = productMap.get(productId);

LinkedHashMap:去重且保持顺序

适用场景:需要在 HashMap 的 O(1) 性能基础上,保持插入顺序。

java
// 场景 1:去除重复但保持首次出现的顺序
List<String> urls = Arrays.asList("a.com", "b.com", "a.com", "c.com");
Map<String, String> unique = new LinkedHashMap<>();
for (String url : urls) {
    unique.putIfAbsent(url, url);
}
System.out.println(unique.keySet()); // [a.com, b.com, c.com]

// 场景 2:FIFO 缓存(配合 removeEldestEntry)
LinkedHashMap<String, Data> cache = new LinkedHashMap<>(16, .75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_CACHE_SIZE;
    }
};

TreeMap:需要排序的能力

适用场景:需要范围查询、最值查找、按 key 排序遍历。

java
// 场景 1:排行榜(按分数排序)
TreeMap<Integer, String> leaderboard = new TreeMap<>(Comparator.reverseOrder());
leaderboard.put(1500, "张三");
leaderboard.put(2000, "李四");
System.out.println(leaderboard.firstEntry()); // 最高分

// 场景 2:时间区间查找
TreeMap<LocalDate, Event> schedule = new TreeMap<>();
schedule.put(LocalDate.of(2024, 3, 15), new Event("发布"));
schedule.put(LocalDate.of(2024, 3, 20), new Event("测试"));
// 查询 3 月 16 日之后的事件
Map<LocalDate, Event> after = schedule.tailMap(LocalDate.of(2024, 3, 16));

ConcurrentHashMap:高性能并发

适用场景:多线程共享 Map,且有较高并发度。

java
// 场景 1:高并发计数器
ConcurrentHashMap<String, LongAdder> counters = new ConcurrentHashMap<>();
counters.computeIfAbsent(endpoint, k -> new LongAdder()).increment();

// 场景 2:线程安全的缓存
ConcurrentHashMap<String, Future<String>> cache = new ConcurrentHashMap<>();
String result = cache.computeIfAbsent(key, k -> {
    return CompletableFuture.supplyAsync(() -> heavyComputation(k));
}).get();

WeakHashMap:自动清理的缓存

适用场景:key 不再被外部引用时,希望自动从 Map 中移除。

java
// 场景:缓存外部对象,不需要手动清理
WeakHashMap<ImageKey, SoftReference<Bitmap>> imageCache = new WeakHashMap<>();

// 当 ImageKey 不再被程序持有时,下一次 GC 会自动回收 entry
public Bitmap getImage(ImageKey key) {
    SoftReference<Bitmap> ref = imageCache.get(key);
    if (ref != null) {
        Bitmap cached = ref.get();
        if (cached != null) return cached;
    }
    Bitmap loaded = loadBitmap(key);
    imageCache.put(key, new SoftReference<>(loaded));
    return loaded;
}

并发 Map 对比

类型锁粒度适用场景
Collections.synchronizedMap(hashMap)全局锁偶尔并发
ConcurrentHashMap段锁(Java 7)/ CAS(Java 8+)高并发写
ConcurrentSkipListMap无锁高并发写 + 需要排序
java
// ❌ 全局锁:所有线程竞争同一把锁
Map<String, Integer> sync = Collections.synchronizedMap(new HashMap<>());

// ✅ ConcurrentHashMap:分段锁,线程间竞争少
Map<String, Integer> concurrent = new ConcurrentHashMap<>();

// ✅ ConcurrentSkipListMap:跳表实现,支持排序 + 无锁
Map<String, Integer> sorted = new ConcurrentSkipListMap<>();

不要只盯着 Map

有些场景,用其他数据结构更合适:

java
// ❌ 用 Map 统计唯一值数量
Map<String, Integer> uniqueCount = new HashMap<>();
for (String item : data) {
    uniqueCount.put(item, 1); // 浪费空间
}
// 结果:只需要一个数字

// ✅ 用 Set
Set<String> unique = new HashSet<>(data);
System.out.println(unique.size());

// ❌ 用 Map 做简单的标志位
Map<String, Boolean> flags = new HashMap<>();
flags.put("enabled", true);
if (flags.getOrDefault("enabled", false)) { ... }

// ✅ 直接用常量或 Set
Set<String> enabledFeatures = new HashSet<>();
if (enabledFeatures.contains("feature")) { ... }

选型决策树

需要 Map 吗?

├── 需要排序(按 key 比大小)
│   ├── 单线程 → TreeMap
│   └── 多线程 → ConcurrentSkipListMap

├── 需要保持插入顺序
│   └── LinkedHashMap

├── 高并发
│   └── ConcurrentHashMap

├── key 需要被 GC 自动清理
│   └── WeakHashMap

└── 其他所有场景
    └── HashMap

常见误区

误区 1:所有场景都用 HashMap

HashMap 不是万能的。如果需要排序,HashMap 的遍历顺序会让你困惑。

误区 2:TreeMap 比 HashMap 更「高级」

TreeMap 的 O(log n) 比 HashMap 的 O(1) 慢 5-10 倍。只有需要 TreeMap 的独有功能时才用它。

误区 3:Hashtable 是线程安全的所以更好

Hashtable 用全局锁,高并发下性能很差。ConcurrentHashMap 的分段锁/CAS 性能好得多。

误区 4:null key 和 null value 都可以随便用

HashMap 允许 null key 和 null value,但 TreeMap/ConcurrentHashMap 不允许。应该尽量避免 null,改用 getOrDefault


总结

需求推荐
通用场景,不需要顺序HashMap
需要保持插入顺序LinkedHashMap
需要排序/范围查询TreeMap
高并发ConcurrentHashMap
高并发 + 需要排序ConcurrentSkipListMap
key 需要自动清理WeakHashMap
遗留代码迁移到 ConcurrentHashMap

选型口诀:无排序无顺序用 HashMap,需要顺序用 Linked,需要排序用 Tree,并发场景用 Concurrent。


相关链接

基于 VitePress 构建