1. 引言
我们已经知道,ArrayList 是一个会自动增长的“可扩展数组”。但它有一个限制:访问元素必须通过编号(索引)。如果我们有一个包含上千人的列表,想找到爱丽丝的电话,要么知道她的索引,要么就得把所有人都遍历一遍。
在实际中,我们经常是按唯一特征来查找数据:
- 根据姓名找电话号码,
- 根据护照号码找持有人,
- 根据登录名找到密码,
- 根据 id 在数据库中查找对象。
为此我们需要一种结构,能够做到:“拿到键 → 立刻得到值”。在 Java 中,这正是 HashMap<K,V> 的职责。
类比:
想象一本纸质词典。你要找单词“house”的翻译,不必从头翻到尾,而是直接翻到字母“H”,很快就能找到需要的词。HashMap 也是这样:根据键(单词)我们可以瞬时得到值(翻译)。
创建字典
要创建字典,需要指定哪些类型作为键,哪些类型作为值。
import java.util.HashMap;
HashMap<String, String> phonebook = new HashMap<String, String>();
这里:
- String(第一个类型)是键的类型(人的姓名)。
- String(第二个类型)是值的类型(电话号码)。
现在 phonebook 就像一本真正的电话簿。
其他变体示例:
HashMap<String, Integer> grades = new HashMap<String, Integer>(); // 姓名 → 分数
HashMap<Integer, String> users = new HashMap<Integer, String>(); // id → 姓名
HashMap<String, Boolean> flags = new HashMap<String, Boolean>(); // 键 → 布尔值
与 ArrayList 不同,映射没有 add() 方法,但有自己的一套同样有趣的方法。
2. 方法 put(key, value) — 添加一对键值
HashMap 与数组和列表的根本区别在于,它存储的不是单个值,而是键–值对。这让它更像一本真正的词典:每个单词有翻译,每个名字有电话,每个登录名有密码。
示例:
phonebook.put("爱丽丝", "+380501112233");
phonebook.put("鲍勃", "+380671234567");
现在在我们的电话簿中:
- 键 "爱丽丝" 关联的值是 "+10501112233",
- 键 "鲍勃" 关联的值是 "+10671234567"。
这意味着要获得一个值(电话号码),我们只需知道对应的键(姓名)。
务必记住:键是唯一的。如果我们尝试向已存在的键添加新值,旧值会被覆盖。
phonebook.put("爱丽丝", "+10999999999");
现在爱丽丝只保留了新号码:旧号码丢失了。
结论:put 用于添加或更新条目。键是新的——就创建记录;键已存在——就更新记录。
3. 方法 get(key) — 获取值
要查找某个值,需要知道键。这正是 HashMap 的强项:按键查找非常快,几乎是瞬时的,不管我们有一千条记录还是一百万条。
示例:
System.out.println(phonebook.get("爱丽丝"));
输出:
+10501112233
如果没有这个键,get 会返回 null:
System.out.println(phonebook.get("查理")); // null
这表示:“字典中没有键 "查理"”。
4. 方法 containsKey(key) — 检查是否存在
为避免遇到 null,先检查字典中是否有该键会很有用。
示例:
System.out.println(phonebook.containsKey("查理"));
输出:
false
因此我们可以事先判断:
- 如果没有该键——创建新条目,
- 如果已有该键——更新它。
常见写法:
if (phonebook.containsKey("爱丽丝"))
{
System.out.println("爱丽丝已经有号码了!");
}
else
{
phonebook.put("爱丽丝", "+10111111111");
}
5. 方法 remove(key) — 删除条目
删除同样简单:只需知道键。
phonebook.remove("鲍勃");
System.out.println(phonebook.get("鲍勃"));
输出:
null
现在字典中已没有键为 "鲍勃" 的条目。
6. 遍历所有键值对
很多时候不仅要按特定键取值,还需要把整本“字典”打印出来。为此可以使用 entrySet() 方法。
for (var entry : phonebook.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
示例输出:
爱丽丝 -> +10501112233
这样我们就能同时访问键(entry.getKey())和值(entry.getValue())。
7. 实践:词频统计
来看一个经典问题——统计文本中单词的出现次数。这是 HashMap 用途最典型的示例之一。
String text = "java java core java";
HashMap<String, Integer> freq = new HashMap<String, Integer>();
for (String w : text.split(" "))
{
Integer old = freq.get(w);
freq.put(w, (old == null) ? 1 : old + 1);
}
System.out.println(freq);
输出:
{core=1, java=3}
我们来详细看看这里发生了什么:
- 我们把字符串 "java java core java" 按空格切分成单词。
- 对每个单词查看它是否已经在字典中(freq.get(w))。
- 如果没有(null),说明是首次出现 → 放入 1。
- 如果已经存在,说明该单词出现过 → 将值加一。
实际应用:
- 统计 API 被调用的次数,
- 统计文本中的词频,
- 保存仓库中商品的库存数量。
8. 实践:电话簿
我们来写一个稍大一点的应用。
import java.util.HashMap;
import java.util.Scanner;
public class PhonebookApp {
public static void main(String[] args)
{
HashMap<String, String> phonebook = new HashMap<String, String>();
Scanner console = new Scanner(System.in);
while (true)
{
System.out.print("请输入姓名(留空回车退出): ");
String name = console.nextLine();
if (name.isEmpty()) break;
System.out.print("请输入号码: ");
String phone = console.nextLine();
phonebook.put(name, phone);
}
System.out.println("电话簿:");
for (var entry : phonebook.entrySet())
{
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}
该程序就像一个小型通讯录。你输入姓名和电话,它们会保存在 HashMap 中。最后可以输出完整列表。字典在真实项目中非常常用。
GO TO FULL VERSION