第 14 章:Map 接口与 HashMap
学习目标
- 掌握 HashMap 的常用 API
- 理解 key 的 hashCode + equals 契约
- 学会 4 种遍历方式
一、Map:键值对
java
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1); // ① 添加/覆盖
map.get("apple"); // ② 1(不存在返回 null)
map.getOrDefault("orange", 0); // ③ 0(不存在返回默认值)
map.containsKey("apple"); // ④ true
map.containsValue(1); // ⑤ true
map.remove("apple"); // ⑥ 删除(返回被删的 value)
map.size();
map.isEmpty();
map.clear();二、4 种遍历方式
java
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
// ① 遍历键(需要 keys 时)
for (String key : map.keySet()) {
System.out.println(key + " = " + map.get(key));
}
// ② 遍历值
for (Integer value : map.values()) {
System.out.println(value);
}
// ③ 遍历 entry(键值对,推荐)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// ④ forEach + Lambda(Java 8+)
map.forEach((k, v) -> System.out.println(k + " = " + v));三、key 的 hashCode + equals 契约
HashMap 用 key 的 hashCode 定位桶,用 equals 比较键是否相同。
⚠️ 坑 1:自定义对象做 key,必须重写 hashCode 和 equals,否则
get()找不到值。javaMap<User, String> map = new HashMap<>(); User u = new User("张三", 18); map.put(u, "value"); map.get(new User("张三", 18)); // ❌ null(因为 hashCode 不同)
⚠️ 坑 2:可变对象不能做 key。如果 key 的字段改了,hashCode 也变了,就找不到了。
java// String 不可变,所以能做 key // 自己写个 MutableUser 改了 name,就 get 不到了
四、Map 实现类对比
| 实现 | 底层 | 有序 | 线程安全 | 适用 |
|---|---|---|---|---|
HashMap | 数组+链表+红黑树 | ❌ | ❌ | 99% 场景 |
LinkedHashMap | 链表+HashMap | ✅ 插入序 | ❌ | 要保留插入顺序 |
TreeMap | 红黑树 | ✅ 键排序 | ❌ | 要按 key 排序 |
ConcurrentHashMap | 分段锁/CAS | ❌ | ✅ | 多线程场景 |
java
// 默认
Map<String, Integer> map = new HashMap<>();
// 多线程并发(后续章节详解)
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();五、实战:用 Map 统计频次
java
String text = "hello world hello java hello";
Map<String, Integer> wordCount = new HashMap<>();
for (String word : text.split(" ")) {
wordCount.merge(word, 1, Integer::sum); // 不存在则 put 1,存在则 +1
}
// 输出:{hello=3, world=1, java=1}
System.out.println(wordCount);⚠️ 坑 3:
merge是最优雅的累加写法,不要写if (containsKey) { +1 } else { put 1 }这种啰嗦代码。
六、HashMap 性能小技巧
预分配容量(知道大概会放多少元素):
java
// 默认容量 16,放多了会扩容(重新哈希,慢)
Map<String, Integer> map = new HashMap<>(1000); // 预分配 1000 容量loadFactor 一般不动,默认 0.75 是平衡点。
七、本章小结
| 要点 | 关键 |
|---|---|
| Map 特性 | 键值对,key 不可重复 |
| key 契约 | 必须重写 hashCode + equals,不可变 |
| 遍历 | entrySet() 最常用 |
| 选型 | HashMap(默认)/ LinkedHashMap(有序)/ ConcurrentHashMap(线程安全) |
| merge | 累加的最优雅写法 |
动手练习
- 字符频次统计:统计字符串中每个字符出现的次数
- 两数之和:给定数组和 target,找两个数的下标使和等于 target(用 HashMap)
- 按 value 排序:把
Map<String, Integer>按 value 降序排序后输出