1. 概述

统计字符串中每个字符的出现频率是Java开发中常见且实用的任务。无论是处理用户输入、分析文本数据还是解决算法问题,字符频率统计都是基础操作。

本教程将深入探讨如何基于HashMap高效实现字符频率统计,提供两种主流实现方式。

2. 为什么选择HashMap?

HashMap是Java中基于键值对的数据结构,在大多数情况下提供O(1)时间复杂度的存取操作。

在字符统计场景中:

  • ✅ 将每个唯一字符作为键(Key)
  • ✅ 对应的出现次数作为值(Value)
  • ✅ 遍历字符串时动态更新计数:
    • 字符已存在 → 计数值+1
    • 字符首次出现 → 初始化为1

这种实现方式在性能和代码简洁性之间取得了良好平衡,特别适合处理中等规模文本数据。

3. Java实现方案

本节将展示两种HashMap实现方案:传统循环遍历和Java Streams API。两者核心逻辑相同,但编码风格差异明显。

3.1. 传统for循环实现

最直观的实现方式是遍历字符串中的每个字符,在HashMap中动态更新计数值。这种方法代码清晰、执行高效:

public static Map<Character, Integer> countCharactersWithLoop(String input) {
    Map<Character, Integer> characterCountMap = new HashMap<>();

    for (char ch : input.toCharArray()) {
        characterCountMap.put(ch, characterCountMap.getOrDefault(ch, 0) + 1);
    }

    return characterCountMap;
}

实现要点:

  1. 初始化空HashMap存储字符-频率映射
  2. 将字符串转为字符数组遍历
  3. 使用getOrDefault()处理字符首次出现情况
  4. 返回包含所有字符频率的完整映射

⚠️ 注意:

  • 区分大小写('A'和'a'视为不同字符)
  • 包含所有字符(空格、标点等)
  • 可轻松扩展为忽略大小写或过滤特殊字符

3.2. Java Streams实现

Java Streams API提供更函数式的实现方式,通过字符流处理和收集器操作完成统计:

public static Map<Character, Integer> countCharactersWithStreams(String input) {
    return input.chars()
      .mapToObj(c -> (char) c)
      .collect(Collectors.groupingBy(
        Function.identity(),
        Collectors.collectingAndThen(Collectors.counting(), Long::intValue)
      ));
}

核心步骤解析:

  1. input.chars() → 字符编码流(IntStream)
  2. mapToObj() → 转换为Character对象流
  3. groupingBy() → 按字符分组
  4. collectingAndThen() → 将Long计数转换为Integer

✅ 优势:

  • 代码更简洁(单行实现核心逻辑)
  • 天然支持并行处理
  • 易于集成到复杂流处理管道

❌ 限制:

  • 性能略低于循环实现(约5-10%开销)
  • 可读性对Stream不熟悉的开发者较差

4. 单元测试验证

通过JUnit测试验证两种实现的正确性,使用相同输入字符串校验输出一致性:

@Test
public void givenSimpleInput_whenCountingCharactersWithLoop_thenReturnsCorrectFrequencies() {
    String input = "test";
    Map<Character, Integer> result = CharacterFrequencyCounter.countCharactersWithLoop(input);

    assertEquals(Integer.valueOf(2), result.get('t'));
    assertEquals(Integer.valueOf(1), result.get('e'));
    assertEquals(Integer.valueOf(1), result.get('s'));
    assertEquals(3, result.size());
}

@Test
public void givenSimpleInput_whenCountingCharactersWithStreams_thenReturnsCorrectFrequencies() {
    String input = "test";
    Map<Character, Integer> result = CharacterFrequencyCounter.countCharactersWithStreams(input);

    assertEquals(Integer.valueOf(2), result.get('t'));
    assertEquals(Integer.valueOf(1), result.get('e'));
    assertEquals(Integer.valueOf(1), result.get('s'));
    assertEquals(3, result.size());
}

测试要点:

  • 验证字符't'出现2次
  • 验证字符'e'和's'各出现1次
  • 确认映射表包含3个条目(无多余字符)

两种实现通过相同测试用例,证明其功能等价性。

5. 总结

本文深入探讨了两种基于HashMap的字符频率统计方案:

  1. 传统循环实现:性能优先,代码直观
  2. Streams API实现:简洁优雅,函数式风格

两种方案均能高效处理:

  • 大小写敏感的字符统计
  • 包含空格和标点符号的完整文本
  • 任意Unicode字符

根据实际场景选择:

  • ✅ 性能敏感场景 → 优先循环实现
  • ✅ 代码简洁性要求 → 选择Streams实现
  • ✅ 复杂数据处理管道 → Streams更易扩展

完整实现代码已托管至GitHub仓库,可直接集成到项目中使用。


原始标题:HashMap Implementation to Count the Occurrences of Each Character in Java | Baeldung