用 hashmap 统计单词频次最直接:键存单词、值存次数;需统一转小写、正确处理标点和空白字符、显式指定文件编码、过滤空串,jdk 8+ 推荐用 merge 或 getordefault 安全累加。

用 HashMap 统计单词频次最直接
Java 里做单词统计,HashMap<string integer></string> 是最自然的选择:键存单词,值存出现次数。别一上来就用 TreeMap 或 LinkedHashMap——除非你明确需要排序或插入顺序,否则纯增加开销。
常见错误是每次 put 前不检查 key 是否存在,写成 map.put(word, map.get(word) + 1),结果 map.get(word) 返回 null,拆箱时报 NullPointerException。
- 用
map.merge(word, 1, Integer::sum)一行搞定累加,JDK 8+ 推荐 - 或者先
map.getOrDefault(word, 0)再put,安全且易读 - 注意大小写:一般需求要统一转
toLowerCase(),否则"Hello"和"hello"算两个词
String.split() 切单词时正则别写错
用 split() 提取单词最常用,但很多人卡在分隔符上。写 str.split(" ") 只能切空格,遇到制表符、换行、多个连续空格就漏词;写 str.split("\s+") 才真正匹配所有空白字符(包括 Unicode 空白)。
更关键的是:英文文本里单词边界不只有空格,还有标点。直接 split("\s+") 会把 "word," 当作一个“单词”,逗号跟着进去了。
立即学习“Java免费学习笔记(深入)”;
- 稳妥做法是先用
replaceAll("[^a-zA-Z0-9\s]", " ")把标点全替成空格,再split("\s+") - 或者一步到位用
Pattern.compile("\b[a-zA-Z]+\b").matcher(str)配合循环提取,更准但稍重 - 避免用
split("")或split("(?!^)")拆单字——这不是单词统计,是字符统计
处理文件输入时别忽略编码和空行
从文件读单词,Files.readAllLines(path) 默认用 UTF-8,但如果文件是 GBK 编码(比如 Windows 记事本默认保存),中文就会变乱码,连带影响单词切分逻辑。
另外,文件里常有空行、纯空白行,split 后可能产生空字符串,不过滤就塞进 HashMap,导致 "" 被当做一个“单词”统计一次。
- 读文件务必显式指定编码:
Files.readAllLines(path, StandardCharsets.UTF_8) - 每行处理前加
line = line.trim(),再判断line.isEmpty()跳过 - split 后用
Arrays.stream(parts).filter(s -> !s.isEmpty())过滤空串,比手动循环更稳
性能敏感时慎用 String.substring() 配合 split()
大文本(比如几十 MB 日志)做单词统计,频繁调用 split() 会产生大量临时 String 对象,GC 压力明显。不是说不能用,而是得知道代价在哪。
真正瓶颈往往不在 Map 更新,而在字符串切分本身——split() 底层用 Pattern 编译正则,每次调用都走一遍匹配流程。
- 如果只是简单空格分隔,用
Scanner配合useDelimiter("\s+")流式读取,内存更友好 - 对超大文件,考虑用
BufferedReader.readLine()逐行读,每行内用indexOf+substring手动找单词边界,绕过正则引擎 -
split()返回的数组长度,和原始字符串长度无关,但和分隔符出现频次强相关——标点多的文本,数组创建开销更大
实际写的时候,先跑通逻辑,再看 profiling 数据。多数小工具根本不需要优化到这一步,但得知道哪块容易拖慢。










