Map 家族对比
问题
Java 中有哪些 Map 实现?它们各自适用于什么场景?
答案
Map 家族一览
| 实现类 | 底层结构 | 有序性 | null key | 线程安全 | 特点 |
|---|---|---|---|---|---|
| HashMap | 数组+链表+红黑树 | 无序 | 允许 1 个 | 否 | 最常用 |
| LinkedHashMap | HashMap + 双向链表 | 插入/访问顺序 | 允许 1 个 | 否 | 有序 Map |
| TreeMap | 红黑树 | Key 排序 | 不允许 | 否 | 有序 + 范围查询 |
| Hashtable | 数组+链表 | 无序 | 不允许 | 是(全表锁) | 已废弃 |
| ConcurrentHashMap | 数组+链表+红黑树 | 无序 | 不允许 | 是(桶锁) | 并发首选 |
| WeakHashMap | 数组+链表 | 无序 | 允许 1 个 | 否 | 弱引用 key |
| IdentityHashMap | 数组 | 无序 | 允许 | 否 | == 比较 key |
| EnumMap | 数组 | 枚举顺序 | 不允许 | 否 | 枚举 key 专用 |
LinkedHashMap
继承 HashMap,通过双向链表维护顺序:
LinkedHashMapDemo.java
// 保持插入顺序(默认)
Map<String, Integer> insertOrder = new LinkedHashMap<>();
insertOrder.put("c", 3);
insertOrder.put("a", 1);
insertOrder.put("b", 2);
System.out.println(insertOrder.keySet()); // [c, a, b]
// 保持访问顺序(LRU 缓存的基础)
Map<String, Integer> accessOrder = new LinkedHashMap<>(16, 0.75f, true);
accessOrder.put("a", 1);
accessOrder.put("b", 2);
accessOrder.put("c", 3);
accessOrder.get("a"); // 访问 a,移到链表尾部
System.out.println(accessOrder.keySet()); // [b, c, a]
// 基于 LinkedHashMap 实现 LRU 缓存
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LRUCache(int maxSize) {
super(maxSize, 0.75f, true); // accessOrder = true
this.maxSize = maxSize;
}
// 超出容量时移除最久未访问的元素(链表头部)
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}
TreeMap
基于红黑树,key 有序,支持范围查询:
TreeMapDemo.java
TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "c");
map.put(1, "a");
map.put(5, "e");
map.put(2, "b");
map.put(4, "d");
map.firstKey(); // 1(最小 key)
map.lastKey(); // 5(最大 key)
map.floorKey(3); // 3(≤ 3 的最大 key)
map.ceilingKey(3); // 3(≥ 3 的最小 key)
map.subMap(2, 5); // {2=b, 3=c, 4=d}([2, 5) 范围)
map.headMap(3); // {1=a, 2=b}(< 3)
map.tailMap(3); // {3=c, 4=d, 5=e}(≥ 3)
WeakHashMap
key 使用弱引用(WeakReference),当 key 没有其他强引用时,GC 会回收该键值对:
WeakHashMapDemo.java
WeakHashMap<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "value");
System.out.println(map.size()); // 1
key = null; // 移除强引用
System.gc(); // 触发 GC
Thread.sleep(100);
System.out.println(map.size()); // 0(key 被回收,键值对自动清理)
适用于缓存场景,避免缓存阻止对象被 GC。ThreadLocal 内部的 ThreadLocalMap 就使用了类似弱引用的 Entry。
常见面试问题
Q1: HashMap 和 TreeMap 怎么选?
答案:
- HashMap:不需要排序,追求最高性能( 增删查)
- TreeMap:需要按 key 排序、范围查询( 增删查)
Q2: LinkedHashMap 怎么实现 LRU 缓存?
答案:
构造时设置 accessOrder = true,每次 get/put 后将节点移到链表尾部。重写 removeEldestEntry() 方法,在 size > maxSize 时返回 true,自动移除链表头部(最久未访问)的节点。
Q3: IdentityHashMap 和 HashMap 的区别?
答案:
IdentityHashMap 使用 ==(引用相等)而非 equals() 比较 key。两个内容相同但不是同一个对象的 key 会被当作不同的 key。适用于需要严格区分对象引用的场景(如序列化框架检测循环引用)。
Q4: 如何选择合适的 Map 实现?
答案:
是否需要线程安全?
├── 是 → ConcurrentHashMap
└── 否 → 是否需要排序?
├── 是 → TreeMap
└── 否 → 是否需要有序?
├── 插入顺序 → LinkedHashMap
├── 访问顺序(LRU)→ LinkedHashMap(accessOrder=true)
└── 不需要 → HashMap