最直接方式是遍历字符串用HashMap统计字符频次,键用Character,避免null异常需用getOrDefault;单词统计应split("\s+")并转小写;流式写法注意chars()对中文不准确,应用codePoints();预处理推荐toLowerCase().replaceAll("1", "")。\p{L}\p{Nd}\s ↩

如何用 HashMap 统计字符串中每个字符出现次数
最直接、可控的方式是遍历字符串,用 HashMap 记录频次。注意 char 是基本类型,Character 才能作为泛型键;遇到空格、换行、标点也要纳入统计(除非业务明确排除)。
常见错误:用 get() 获取旧值后直接 ++,但 get() 返回 null 时会抛 NullPointerException。应改用 getOrDefault(ch, 0)。
示例关键片段:
Mapfreq = new HashMap<>(); for (char ch : str.toCharArray()) { freq.put(ch, freq.getOrDefault(ch, 0) + 1); }
统计单词频次时为什么 split("\\s+") 比 split(" ") 更可靠
split(" ") 只按单个空格切分,遇到制表符、连续空格或首尾空格会产生空字符串,导致 "" 被误计为单词。而 split("\\s+") 匹配一个或多个空白字符(包括 \t、\n、\r),天然过滤掉冗余分隔。
立即学习“Java免费学习笔记(深入)”;
还需注意:英文单词通常需转小写再统计(word.toLowerCase()),否则 "Hello" 和 "hello" 算两个词;中文文本则无需此步,但要考虑是否要剔除标点——可用 replaceAll("[\\p{Punct}&&[^']]", "") 保留英文撇号(如 don't)。
用 Collectors.groupingBy 实现一行统计的陷阱
Java 8+ 可用流式写法:str.chars().mapToObj(c -> (char) c).collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))。但要注意:
-
str.chars()返回的是IntStream,直接mapToObj(c -> c)会装箱成Integer,必须显式转(char) c否则键类型错 - 中文字符(Unicode 辅助平面)可能被拆成两个
char(代理对),此时chars()无法正确识别单个汉字,应改用codePoints()并映射为String或int - 性能上,流式写法比传统循环慢 2–3 倍,大数据量时不建议无脑用
忽略大小写 + 排除标点的完整预处理怎么做
真实场景中,统计前往往要标准化输入。推荐组合使用:
str.toLowerCase().replaceAll("[^\\p{L}\\p{Nd}\\s]", "") —— 先转小写,再删除所有非字母、非数字、非空白字符。其中 \\p{L} 匹配任意语言字母(含中文、日文平假名等),\\p{Nd} 匹配任意数字,比硬写 [a-z0-9] 更健壮。
若还要保留内部撇号(如英文缩写),可改为:replaceAll("[^\\p{L}\\p{Nd}'\\s]", ""),但需额外 .replaceAll("'+", "'") 合并连续单引号。
这一步漏掉 Unicode 字母支持,会导致中文、阿拉伯文等完全丢失;只删 ASCII 标点(如 [^a-zA-Z0-9\\s])也会让中文句号、顿号残留进单词里。









