跳到主要内容

Map 家族对比

问题

Java 中有哪些 Map 实现?它们各自适用于什么场景?

答案

Map 家族一览

实现类底层结构有序性null key线程安全特点
HashMap数组+链表+红黑树无序允许 1 个最常用
LinkedHashMapHashMap + 双向链表插入/访问顺序允许 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:不需要排序,追求最高性能(O(1)O(1) 增删查)
  • TreeMap:需要按 key 排序、范围查询(O(logn)O(\log n) 增删查)

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

相关链接