Java 哈希表(HashMap)全解析:基础用法 + 实战算法 + 场景选型
一、HashMap 核心原理与基础用法
HashMap 是 Java 中最常用的哈希表实现,基于数组 + 链表 / 红黑树的结构存储数据,核心是通过哈希函数将键映射到数组下标,实现 O(1) 级别的增删改查效率,是日常开发和算法解题中必备的数据结构。
1. 常用方法
java
运行
import java.util.HashMap;
public class HashMapBasic {
public static void main(String[] args) {
// 创建 HashMap 对象
HashMap<Integer, String> map =<>();
// 1. 添加元素(put)
map.put(1, "Apple");
map.put(2, "Banana");
map.put(3, "Cherry");
// 2. 获取元素(get)
String fruit = map.get(2); // 返回 "Banana"
// 3. 判断键是否存在(containsKey)
boolean hasKey = map.containsKey(3); // 返回 true
// 4. 删除元素(remove)
map.remove(1);
// 5. 遍历元素
for (Integer key : map.keySet()) {
System.out.println("Key: " + key + ", Value: " + map.get(key));
}
}
}
2. 核心特性
- 键唯一:默认情况下重复键会覆盖旧值,可通过特殊设计避免覆盖
- 无序性:元素顺序不保证与插入顺序一致(需有序可使用
LinkedHashMap) - 允许 null 键和 null 值:但仅能有一个 null 键
二、扩展:让 HashMap 重复键不覆盖旧值
默认的 HashMap 遵循键唯一原则,遇到重复键会直接覆盖旧值。但实际开发中,我们经常需要同一个键对应多个值,此时只需将 HashMap 的值设计为集合类型(如 List),配合 computeIfAbsent 方法即可实现不覆盖、追加存储的效果。
实现思路
- 将 HashMap 泛型声明为<V>>`,用 List 存储同一键的多个值
- 插入数据时,先判断键是否存在:不存在则创建新 List,存在则直接追加新值
代码示例
java
运行
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class NonOverwriteHashMap {
public static void main(String[] args) {
<Integer>><>();
// 添加第一个键值对:"key1" -> 1
map.computeIfAbsent("key1", k -> new<>()).add(1);
// 添加第二个键值对:"key1" -> 2(不覆盖,追加至List)
map.computeIfAbsent("key1",<>()).add(2);
// 添加第三个键值对:"key2" -> 3
map.computeIfAbsent("key2<>()).add(3);
// 输出结果
System.out.println(map.get("key1")); // 输出 [1, 2]
System.out.println(map.get("key2")); // 输出 [3]
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; // 别忘了导入 Map 接口
public class NonOverwriteHashMap {
public static void main(String[] args) {
// 声明:键是 String,值是 List<Integer>
Map<String, List<Integer>> map = new HashMap<>();
// 如果 "key1" 不存在,创建一个 ArrayList,然后 add(1)
map.computeIfAbsent("key1", k -> new ArrayList<>()).add(1);
// 如果 "key1" 已存在,直接返回现有的 List,然后 add(2)
map.computeIfAbsent("key1", k -> new ArrayList<>()).add(2);
// 修正 "key2" 的引号,并补全 ArrayList
map.computeIfAbsent("key2", k -> new ArrayList<>()).add(3);
// 输出结果
System.out.println(map.get("key1")); // 输出: [1, 2]
System.out.println(map.get("key2")); // 输出: [3]
}
}
核心方法说明
computeIfAbsent(K key,<? super K, ? extends V> mappingFunction):
- 键不存在时,执行函数创建新值并返回
- 键存在时,直接返回已有值
- 一行代码实现判空、创建、追加操作,代码更简洁高效
三、算法实战:HashMap 三道经典例题
掌握基础用法后,我们通过三道高频算法题,感受 HashMap 空间换时间的优化魅力。
1. 两数之和
题目:给定整数数组 nums 和目标值 target,找出数组中两个数的索引,使它们的和等于 target。
思路:用 HashMap 存储已遍历数字及其索引,遍历过程中计算目标差值,差值存在则直接返回结果,时间复杂度优化至 O(n)。
java
运行
import java.util.HashMap;
public class TwoSum {
public int[] twoSum(int[] nums, int target)<Integer, Integer> map = new HashMap<>();
for (int i = 0;< nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i};
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
public static void main(String[] args) {
TwoSum solution = new TwoSum();
int[] nums = {2, 7, 11, 15};
int target = 9;
int[] result = solution.twoSum(nums, target);
System.out.println("[" + result[0] + ", " + result[1] + "]"); // 输出 [0, 1]
}
}
2. 字母异位词分组
题目:将字符串数组中的字母异位词(字母相同但排列不同的字符串)分组。
思路:以字符串排序后的结果为特征键,用 HashMap 存储分组结果,同一异位词自动归入同一组。
java
运行
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map; // 需要导入 Map 接口
public class GroupAnagrams {
// 修正1: 返回类型应为 List<List<String>>
public List<List<String>> groupAnagrams(String[] strs) {
// 修正2: 补全 Map 的类型声明,并初始化
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
// 将字符串转换为字符数组并排序,生成唯一键
char[] chars = s.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
// 修正3: 逻辑补全
// 如果键不存在,放入一个新的 ArrayList;如果存在,获取该列表
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
// 向列表中添加字符串
map.get(key).add(s);
}
// 修正4: 正确实例化返回对象
return new ArrayList<>(map.values());
}
public static void main(String[] args) {
GroupAnagrams solution = new GroupAnagrams();
String[] strs = {"eat", "tea", "tan", "ate", "nat", "bat"};
// 修正5: 修正变量类型的泛型
List<List<String>> result = solution.groupAnagrams(strs);
System.out.println(result);
// 预期输出: [[eat, tea, ate], [tan, nat], [bat]] (顺序可能不同)
}
}
3. 最长连续序列
题目:给定未排序的整数数组,找出最长连续序列的长度。
思路:借助 HashSet(哈希表集合)去重,快速判断元素是否存在,仅从序列起点开始遍历,避免重复计算,保证 O(n) 时间复杂度。
java
运行
import java.util.HashSet;
import java.util.Set;
public class LongestConsecutive {
public int longestConsecutive(int[] nums) {
// 1. 声明并初始化集合
Set<Integer> set = new HashSet<>();
// 2. 数组元素存入集合去重
for (int num : nums) {
set.add(num);
}
int maxLength = 0;
// 3. 遍历集合
for (int num : set) {
// 判断当前数字是否为序列起点(即 num-1 不存在)
if (!set.contains(num - 1)) {
int currentNum = num;
int currentLength = 1;
// 4. 向后查找连续数字
while (set.contains(currentNum + 1)) {
currentNum++;
currentLength++;
}
// 5. 更新最大长度
maxLength = Math.max(maxLength, currentLength);
}
}
return maxLength;
}
}
四、核心总结:什么时候使用哈希表?
很多初学者纠结何时选用哈希表,其实牢记一句话:要快查、用键值、需去重、做分组,就用哈希表。
一、必用哈希表的场景
- 需要 O (1) 快速查找 / 判断:替代数组、List 的线性查找,大幅降低时间复杂度
- 键值映射关系:存储 ID - 信息、配置项、索引映射等一对一 / 一对多数据
- 数据去重:快速判断元素是否重复,保留唯一值
- 频率统计:统计元素出现次数、数量统计
- 特征分组:根据固定特征对数据归类(如字母异位词分组)
- 空间换时间优化:暴力解法时间复杂度过高,用哈希表优化
二、不建议用哈希表的场景
- 需要有序遍历数据:优先选用 TreeMap、LinkedHashMap
- 自定义对象作为键,且未重写
hashCode()和equals():会导致哈希冲突、查找失败 - 数据量极小、操作简单:List、数组更轻量,无需引入哈希表
- 需要按索引顺序访问:数组、ArrayList 更合适
五、全文总结
HashMap 作为 Java 最核心的哈希表实现,凭借高效的增删改查性能,成为开发和算法中的利器。默认键唯一会覆盖旧值,<K, List<V>>` 的形式可实现一对多存储、不覆盖旧值。
在算法解题中,哈希表能轻松将暴力解法 O(n2) 的时间复杂度优化至 O(n),两数之和、字母异位词分组、最长连续序列都是其经典应用。掌握哈希表的场景选型和用法,能大幅提升编码效率和代码性能,是 Java 开发者必须掌握的核心技能。
更多推荐



所有评论(0)