# 问题
30. 在实际业务开发中,如何选择合适的 Map 实现?举例说明你的选择依据。
# 标准答案
选择合适的 Map
需要综合考虑查询性能、插入性能、并发安全、排序需求、内存管理等因素。不同 Map
适用于不同业务场景,例如:
HashMap
(O(1) 查询):适用于高效的 KV 存储,如用户会话缓存。ConcurrentHashMap
(高并发优化):适用于多线程环境,如请求去重。TreeMap
(O(log N) 查询 + 有序):适用于范围查询,如订单按时间排序。LinkedHashMap
(LRU 访问顺序):适用于最近访问缓存,如本地 LRU 缓存。WeakHashMap
(自动回收):适用于存储短生命周期对象,如临时缓存。IdentityHashMap
(按引用比较):适用于对象池、序列化优化,如对象去重。ConcurrentSkipListMap
(线程安全的 TreeMap):适用于有序并发访问,如时间序列数据。
# 答案解析
# 1. 缓存用户 Token(高效 KV 查询)
需求:用户登录后,服务端缓存用户 Token,每次请求验证 Token 是否有效。
选择:ConcurrentHashMap
(保证线程安全,O(1) 查询)。
private static final Map<String, String> tokenCache = new ConcurrentHashMap<>();
tokenCache.put("user123", "token_xyz");
String token = tokenCache.get("user123"); // O(1) 查询
2
3
为什么不用 HashMap?
HashMap
线程不安全,可能导致数据竞争或扩容时的竞态条件。
# 2. 记录最近访问的 100 个页面(LRU 缓存)
需求:维护最近访问的页面,超过 100 个时删除最旧的数据。
选择:LinkedHashMap
(按照访问顺序维护元素,自动移除旧数据)。
LinkedHashMap<String, String> pageCache = new LinkedHashMap<>(100, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
return size() > 100; // 超过 100 个元素时移除最老的数据
}
};
pageCache.put("page1", "Home");
pageCache.put("page2", "Profile");
2
3
4
5
6
7
为什么不用 HashMap?
HashMap
不能维护访问顺序,无法实现 LRU 逻辑。
# 3. 电商订单按时间排序(范围查询)
需求:按下单时间排序,快速查询某时间段的订单。
选择:TreeMap
(支持 O(log N) 查询 和子区间查询)。
TreeMap<Long, String> orderMap = new TreeMap<>();
orderMap.put(1700000000000L, "Order1");
orderMap.put(1700000002000L, "Order2");
NavigableMap<Long, String> subMap = orderMap.subMap(1700000000000L, true, 1700000002000L, true);
2
3
4
为什么不用 HashMap?
HashMap
无法保持 key 的有序性,不适用于范围查询。
# 4. 数据库索引(并发排序查询)
需求:需要在多线程环境下高效范围查询,如分布式日志查询。
选择:ConcurrentSkipListMap
(线程安全的 TreeMap
)。
ConcurrentSkipListMap<Long, String> logMap = new ConcurrentSkipListMap<>();
logMap.put(1700000000000L, "Log1");
logMap.put(1700000003000L, "Log2");
NavigableMap<Long, String> subMap = logMap.subMap(1700000000000L, true, 1700000003000L, true);
2
3
4
为什么不用 TreeMap?
TreeMap
线程不安全,在多线程环境下可能导致数据不一致或并发异常。
# 5. 短生命周期对象缓存(避免内存泄漏)
需求:存储弱引用对象,当对象没有强引用时自动回收。
选择:WeakHashMap
(key 被 GC 回收时,自动删除对应的 entry)。
WeakHashMap<Object, String> weakMap = new WeakHashMap<>();
Object key = new Object();
weakMap.put(key, "Temporary Data");
key = null; // 让 key 变成弱引用,GC 后 weakMap 自动删除该 entry
System.gc(); // 触发 GC,weakMap 可能变空
2
3
4
5
为什么不用 HashMap?
HashMap
强引用 key,即使 key 不再使用,仍然占用内存,可能导致内存泄漏。
# 深入追问
为什么
ConcurrentHashMap
比Collections.synchronizedMap(new HashMap<>())
更高效?ConcurrentHashMap
采用 CAS + 分段锁,多个线程可以同时操作不同的 bucket。synchronizedMap
锁整个 Map,并发性能低。
为什么
LinkedHashMap
适合 LRU 缓存,而TreeMap
不适合?LinkedHashMap
维护访问顺序,每次访问会调整顺序,淘汰最久未使用的数据。TreeMap
只按 key 排序,不支持访问顺序调整。
为什么
WeakHashMap
适合存储临时对象,而HashMap
可能导致内存泄漏?WeakHashMap
的 key 是弱引用,GC 之后会自动移除 entry。HashMap
强引用 key,即使不再使用,仍然占用内存。
# 相关面试题
- 如何选择适合的 Map?不同 Map 适用于哪些场景?
- ConcurrentHashMap 为什么比 synchronizedMap 更高效?
- LRU 缓存为什么用 LinkedHashMap 而不是 TreeMap?
- WeakHashMap 适用于哪些场景?它的 key 为什么是弱引用?