1. 引言
在现实生活中,我们常常需要把某个唯一的“键”对应到某个“值”。电话簿中存的是姓名与电话号码,词典把单词与其翻译关联,在成绩表中每个学生都有自己的名字和对应的分数。
在 Java 中,这类任务可以用 Map 接口完成。它是一种存储“键—值”对(key-value pair)的集合。
Map 的关键性质:
- 每个键都是唯一的(不允许重复)。
- 一个键只能对应一个值。
- 值可以重复。
打个比方:列表(List)就像食堂排队(每个人都有自己的位置,可以按下标访问);而映射(Map)则像带编号的储物柜:每个柜子都有一个编号(键),里面放着某样东西(值)。
Map 接口:基础操作
接口 Map 声明了处理键值对的基本方法:
| 方法 | 说明 |
|---|---|
|
按键添加/替换值 |
|
按键获取值 |
|
按键删除键值对 |
|
检查是否存在该键 |
|
检查是否存在该值 |
|
映射中的键值对数量 |
|
检查 Map 是否为空 |
|
删除所有键值对 |
类型 K 和 V 是泛型参数:K(Key)为键的类型,V(Value)为值的类型。
3. HashMap 类:按键快速访问
什么是 HashMap?
HashMap 是 Map 接口最常用的实现,它能根据键快速访问对应的值。
重要:HashMap 不保证元素的存储顺序!即使你按某个顺序添加键,遍历时顺序也可能不同。
如何创建 HashMap?
import java.util.HashMap;
import java.util.Map;
public class Example {
public static void main(String[] args) {
// 创建映射:键为 String,值为 Integer
Map<String, Integer> ages = new HashMap<>();
// 添加元素
ages.put("Basil", 25);
ages.put("Peter", 30);
ages.put("Mary", 22);
// 根据键获取值
int vasyaAge = ages.get("Basil");
System.out.println("Basil 的年龄: " + vasyaAge); // 25
// 检查是否存在某个键
if (ages.containsKey("Mary")) {
System.out.println("Mary 在列表中!");
}
// 删除元素
ages.remove("Peter");
// 遍历所有键值对
for (String name : ages.keySet()) {
System.out.println(name + ": " + ages.get(name));
}
}
}
输出:
Basil 的年龄: 25
Mary 在列表中!
Basil: 25
Mary: 22
HashMap 的特点
最重要的一点:HashMap 中的键始终是唯一的。如果你放入一个已存在键的新元素,旧值会被新值替换。
值可以重复:不同的键可以指向相同的值。
还有一个重要点——元素顺序。HashMap 不关心添加顺序。输出时条目可能是乱序的——这是正常行为。
4. TreeMap 类:按键排序
与 HashMap 不同,TreeMap 会按键的顺序存储元素。
何时使用 TreeMap?
当你需要元素按键递增(或递减)排列时。例如,希望按字母顺序输出电话簿。
示例:
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
Map<String, String> phoneBook = new TreeMap<>();
phoneBook.put("Basil", "+1-900-123-45-67");
phoneBook.put("Mary", "+1-900-555-55-55");
phoneBook.put("Peter", "+1-900-222-33-44");
for (String name : phoneBook.keySet()) {
System.out.println(name + ": " + phoneBook.get(name));
}
}
}
输出:
Mary: +1-900-555-55-55
Peter: +1-900-222-33-44
Basil: +1-900-123-45-67
请注意:键是按字典序排序的。
5. Map 的基本操作
添加与替换元素
Map<String, Integer> scores = new HashMap<>();
scores.put("Anna", 90);
scores.put("John", 85);
scores.put("Anna", 95); // 会覆盖 "Anna" 的旧值
获取值
Integer annaScore = scores.get("Anna"); // 95
Integer unknown = scores.get("Basil"); // 如果没有该键,则为 null
检查是否存在某个键或值
scores.containsKey("John"); // true
scores.containsValue(85); // true
按键删除键值对
scores.remove("John");
Map 的大小与清空
int size = scores.size();
scores.clear(); // 删除所有元素
5. 遍历 Map 的元素
Map 不是列表,没有索引。但可以这样遍历:
按键遍历:
for (String key : scores.keySet()) {
System.out.println("键: " + key + ", 值: " + scores.get(key));
}
按值遍历:
for (Integer value : scores.values()) {
System.out.println("值: " + value);
}
按键值对遍历(最佳方式):
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " => " + value);
}
何时使用 HashMap,何时使用 TreeMap?
HashMap 是“默认”的通用选择。如果不关心键的顺序、而更在意操作速度,几乎总是使用它。
TreeMap 适用于需要顺序的场景。它会自动按键排序,并能快速找到最小/最大键或处理区间。
总结:在 90% 的情况下使用 HashMap。当数据需要“天然有序”时,使用 TreeMap。
6. Map 的使用示例
示例 1:电话簿
Map<String, String> phoneBook = new HashMap<>();
phoneBook.put("Kate", "+1-999-111-22-33");
phoneBook.put("Oliver", "+1-999-222-33-44");
phoneBook.put("Kate", "+1-999-555-66-77"); // Kate 的旧号码会被新号码覆盖
for (Map.Entry<String, String> entry : phoneBook.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
输出:
Oliver: +1-999-222-33-44
Kate: +1-999-555-66-77
示例 2:统计单词次数
假设我们有一个单词列表,希望知道每个单词出现了多少次:
import java.util.*;
public class WordCount {
public static void main(String[] args) {
List<String> words = Arrays.asList("苹果", "香蕉", "苹果", "梨", "香蕉", "苹果");
Map<String, Integer> counts = new HashMap<>();
for (String word : words) {
int oldCount = counts.getOrDefault(word, 0); // 如果没有该键 — 0
counts.put(word, oldCount + 1);
}
System.out.println(counts); // {梨=1, 苹果=3, 香蕉=2}
}
}
7. 使用 Map 时的常见错误
错误 1:混淆键和值。 新手常常尝试像在列表中那样按索引取值,或者忘了键必须唯一。在 Map 中没有索引——只有键。
错误 2:使用 null 键或值。 在 HashMap 中允许键为 null,但在 TreeMap 中不允许(会抛出 NullPointerException)。两种实现中值都可以为 null,但这通常并不实用。
错误 3:期待 HashMap 中有固定顺序。 HashMap 不保证任何顺序。如果需要顺序——使用 LinkedHashMap(保留插入顺序)或 TreeMap(按键排序)。
错误 4:遍历过程中修改 Map。 如果在循环遍历 Map 的同时添加/删除元素,可能会出现 ConcurrentModificationException。此类任务请使用带有 remove() 方法的迭代器,或使用专用并发集合。
错误 5:用 == 比较键和值,而不是 equals。 Map 使用 equals 来比较键(和值)。如果你创建自定义键类,一定要重写 equals 和 hashCode。
GO TO FULL VERSION