一、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;
    }
}

四、核心总结:什么时候使用哈希表?

很多初学者纠结何时选用哈希表,其实牢记一句话:要快查、用键值、需去重、做分组,就用哈希表

一、必用哈希表的场景

  1. 需要 O (1) 快速查找 / 判断:替代数组、List 的线性查找,大幅降低时间复杂度
  2. 键值映射关系:存储 ID - 信息、配置项、索引映射等一对一 / 一对多数据
  3. 数据去重:快速判断元素是否重复,保留唯一值
  4. 频率统计:统计元素出现次数、数量统计
  5. 特征分组:根据固定特征对数据归类(如字母异位词分组)
  6. 空间换时间优化:暴力解法时间复杂度过高,用哈希表优化

二、不建议用哈希表的场景

  1. 需要有序遍历数据:优先选用 TreeMap、LinkedHashMap
  2. 自定义对象作为键,且未重写hashCode()equals():会导致哈希冲突、查找失败
  3. 数据量极小、操作简单:List、数组更轻量,无需引入哈希表
  4. 需要按索引顺序访问:数组、ArrayList 更合适

五、全文总结

HashMap 作为 Java 最核心的哈希表实现,凭借高效的增删改查性能,成为开发和算法中的利器。默认键唯一会覆盖旧值,<K, List<V>>` 的形式可实现一对多存储、不覆盖旧值。

在算法解题中,哈希表能轻松将暴力解法 O(n2) 的时间复杂度优化至 O(n),两数之和、字母异位词分组、最长连续序列都是其经典应用。掌握哈希表的场景选型和用法,能大幅提升编码效率和代码性能,是 Java 开发者必须掌握的核心技能。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐