Map
定位说明
本篇是 Map 系列总览:Map 核心特性 + 四实现类对比 + 选型入门。深读动线:HashMap → HashMap 源码 → LinkedHashMap(LRU)→ TreeMap(排序,含 红黑树 前置篇)。
Map 核心特性
-
键值对存储
- 每个条目由 键(Key) 和 值(Value) 组成,键唯一(底层通过
equals()和hashCode()保证),值可重复。 - 典型操作:
put(K key, V value)(插入 / 更新)、get(Object key)(通过键取值)、containsKey(Object key)(判断键是否存在)。
- 每个条目由 键(Key) 和 值(Value) 组成,键唯一(底层通过
-
无序性与有序性
- 无序 Map(如
HashMap):键值对的存储顺序与插入顺序无关,遍历时顺序不可预测。 - 有序 Map(如
LinkedHashMap、TreeMap):LinkedHashMap按插入顺序或访问顺序维护条目顺序。TreeMap按键的自然排序或自定义比较器排序(如字典序、数值大小)。
- 无序 Map(如
-
线程安全性
- 核心实现类(
HashMap、LinkedHashMap、TreeMap)非线程安全,多线程场景需使用ConcurrentHashMap或手动同步(如Collections.synchronizedMap())。
- 核心实现类(
-
null 支持
HashMap、LinkedHashMap:允许 1 个 null 键和 多个 null 值(TreeMap不允许null 键,Hashtable不允许任何 null)。
核心实现类概览
| 实现类 | 数据结构 | 顺序性 | 线程安全 | null 键支持 | 典型场景 |
|---|---|---|---|---|---|
| HashMap | 哈希表(数组 + 链表 + 红黑树) | 无序 | 非安全 | 允许 | 快速键值对查询(如缓存、字典) |
| LinkedHashMap | 哈希表 + 双向链表 | 插入 / 访问顺序 | 非安全 | 允许 | 有序存储、LRU 缓存 |
| TreeMap | 红黑树(自平衡二叉树) | 自然 / 定制排序 | 非安全 | 不允许 | 排序、范围查询(如字典序统计) |
| Hashtable(见总览) | 哈希表(数组 + 链表) | 无序 | 安全(同步) | 不允许 | 旧系统线程安全场景(极少使用) |
与 Set、Queue 的对比
| 接口 / 类 | 存储形式 | 核心特性 | 典型场景 |
|---|---|---|---|
| Set | 单一元素 | 元素唯一、无序 / 有序(取决于实现类) | 去重、唯一性校验 |
| Queue | 线性元素(FIFO) | 先进先出、支持阻塞操作(如 BlockingQueue) |
任务队列、消息传递 |
| Map | 键值对 | 快速映射、键唯一、值可重复 | 数据关联、配置管理、统计计数 |
快速入门建议
-
优先使用 HashMap:
若无特殊顺序或线程安全需求,
HashMap是性能最优的选择,适用于高频的put/get操作(如用户信息存储、缓存系统)。 -
需要有序性时:
- 按插入顺序:选
LinkedHashMap(如用户操作日志记录)。 - 按键排序:选
TreeMap(如学生成绩排名、按时间戳排序的事件数据)。
- 按插入顺序:选
-
线程安全场景:
- 高并发:使用
ConcurrentHashMap(JDK 8 优化后性能显著提升)。 - 低并发且需兼容旧代码:使用
Hashtable(不推荐,仅用于遗留系统)。
- 高并发:使用
-
避免误区:
- 不要用
TreeMap替代HashMap做普通映射(排序开销会降低性能)。 LinkedHashMap的访问顺序模式需配合removeEldestEntry实现 LRU,否则无法自动淘汰旧元素。
- 不要用
通过理解 Map 的核心特性与实现类差异,可根据业务需求快速选择合适的数据结构,提升代码效率和可维护性。
上手示例:遍历三式与计数套路
Map<String, Integer> score = new HashMap<>();
score.put("Alice", 92);
score.put("Bob", 85);
// 1. entrySet——键值都要,首选(一次遍历拿全)
for (Map.Entry<String, Integer> e : score.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
// 2. keySet——只要键,值用 get 补(两次查找,略慢)
for (String name : score.keySet()) {
System.out.println(name);
}
// 3. values——只要值
for (Integer s : score.values()) {
System.out.println(s);
}
// 4. forEach(JDK 8+,写起来最短)
score.forEach((name, s) -> System.out.println(name + ":" + s));
两个日常最高频的套路
词频统计——别写 if (map.containsKey(k)) map.put(k, map.get(k)+1) 三行了:
`map.merge(word, 1, Integer::sum);` // 存在则加 1,不存在放 1
map.putIfAbsent(word, 0); // 只在缺键时补默认值
int def = map.getOrDefault("nobody", 0); // 取值给默认值,避免 null
这三个方法(merge / putIfAbsent / getOrDefault)是 JDK 8 给 Map 的"日常三件套",刷题和业务代码里到处都是。
💬 评论