Java词频统计实现与实例解析
本文深入探讨了Java中进行词频统计的有效方法,并提供了详细的实例代码。文章首先强调了文本预处理的重要性,包括如何使用`toLowerCase()`统一大小写,以及利用正则表达式`replaceAll()`移除标点符号,从而提高统计的准确性。核心内容是利用Java集合框架中的HashMap构建词语到频率的映射,并展示了传统方法和Java 8 Stream API两种实现方式,后者在代码简洁性和可读性上更胜一筹。此外,还讨论了处理大量文本时,如何利用NIO和Stream API提高效率,以及除了HashMap之外,TreeMap和Trie等数据结构在词频统计中的应用场景和优缺点,为读者提供了全面的Java词频统计解决方案。
要处理文本预处理中的标点符号和大小写问题,首先应统一大小写,通常使用toLowerCase()方法将所有字符转为小写;其次使用正则表达式replaceAll("1", " ")移除标点符号,将其替换为空格;最后根据需求调整正则表达式以适应数字或特定符号的统计。a-z\s ↩

在Java中进行词频统计,核心思路无非就是把文本拆分成单个词语,然后用一个映射表(Map)来记录每个词出现的次数。这听起来简单,但实际操作起来,文本预处理、效率考量,甚至最终结果的呈现方式,都有不少可以琢磨的地方。本质上,我们是在构建一个词语到其出现频率的映射,而Java的集合框架,特别是HashMap,简直是为这个任务量身定做的。

解决方案
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class WordFrequencyCounter {
/**
* 统计给定文本内容的词频。
* 进行了简单的预处理:转换为小写,移除标点符号。
*
* @param text 要统计词频的文本内容
* @return 包含词语及其出现次数的Map
*/
public Map countWords(String text) {
if (text == null || text.trim().isEmpty()) {
return new HashMap<>();
}
// 1. 文本预处理:转换为小写,移除除了字母和空格之外的所有字符
// 这一步很关键,它决定了我们“看”到的词语是什么样子
String cleanedText = text.toLowerCase().replaceAll("[^a-z\\s]", " ");
// 2. 分割文本为词语数组
// 使用空格作为分隔符,并过滤掉空字符串(例如,多个空格连在一起会产生空字符串)
String[] words = cleanedText.split("\\s+");
// 3. 使用HashMap统计词频
Map wordCounts = new HashMap<>();
for (String word : words) {
if (!word.trim().isEmpty()) { // 再次确保不是空词
wordCounts.put(word, wordCounts.getOrDefault(word, 0) + 1);
}
}
return wordCounts;
}
/**
* 演示如何使用Java 8 Stream API更简洁地实现词频统计。
* 这种方式在代码可读性和表达力上,个人觉得更胜一筹。
*
* @param text 要统计词频的文本内容
* @return 包含词语及其出现次数的Map
*/
public Map countWordsWithStreams(String text) {
if (text == null || text.trim().isEmpty()) {
return new HashMap<>();
}
return Arrays.stream(text.toLowerCase().replaceAll("[^a-z\\s]", " ").split("\\s+"))
.filter(word -> !word.trim().isEmpty())
.collect(Collectors.groupingBy(word -> word, Collectors.counting()));
}
public static void main(String[] args) {
WordFrequencyCounter counter = new WordFrequencyCounter();
String sampleText = "Java is a programming language. Java is widely used. Learn Java, enjoy Java!";
System.out.println("--- 传统方法统计 ---");
Map traditionalCounts = counter.countWords(sampleText);
traditionalCounts.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
System.out.println("\n--- Stream API方法统计 ---");
Map streamCounts = counter.countWordsWithStreams(sampleText);
streamCounts.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
// 尝试从文件读取并统计
System.out.println("\n--- 从文件读取并统计 (Stream API) ---");
try {
String fileContent = Files.readString(Paths.get("sample.txt")); // 假设有sample.txt文件
Map fileWordCounts = counter.countWordsWithStreams(fileContent);
fileWordCounts.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10) // 只显示前10个高频词
.forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
} catch (IOException e) {
System.err.println("读取文件失败,请确保 'sample.txt' 存在并有内容。错误信息: " + e.getMessage());
// 随便写点内容到sample.txt方便测试
try {
Files.writeString(Paths.get("sample.txt"), "This is a test file for word frequency counting. This file contains some words. Test, test, test.");
System.out.println("已创建 'sample.txt' 文件,请重新运行程序以查看结果。");
} catch (IOException ex) {
System.err.println("创建 'sample.txt' 也失败了: " + ex.getMessage());
}
}
}
} 如何处理文本预处理中的标点符号和大小写问题?
这在词频统计里是个绕不开的话题,而且处理得好不好,直接影响最终结果的“干净度”和“准确性”。就拿“Java.”和“java”来说,我们通常希望它们被认为是同一个词。标点符号更是烦人,一个逗号、句号,就能把一个完整的词给“粘”住。
我的习惯做法是,先统一大小写,通常是全部转为小写。String.toLowerCase()方法就是干这个的。然后,处理标点符号。最直接的方式是使用正则表达式replaceAll()。比如,replaceAll("[^a-z\\s]", " ")这个表达式,它的意思是把所有不是小写字母(a-z)也不是空白符(\s)的字符,都替换成空格。这样,像“Hello, world!”就会变成“hello world”,句号和逗号都被“抹平”了,只剩下我们关心的词语。

但这里有个小陷阱,如果文本里有数字或者我们希望统计的特定符号(比如编程语言中的_),这个正则就需要调整了。比如,如果想统计包含数字的词(像“Java8”),可能就需要replaceAll("[^a-zA-Z0-9\\s]", " ")。这其实是个权衡,没有一劳永逸的方案,得看你对“词”的定义有多宽泛。有时,我甚至会考虑更复杂的词法分析器,比如Apache OpenNLP或者Stanford CoreNLP,它们能更智能地识别词语边界,处理连字符、缩写等等,但对于简单的词频统计,手写正则通常就够用了。
如何高效地统计大量文本的词频?
处理大量文本时,效率确实是个大问题。如果文件很大,比如几个GB的文本,一次性Files.readString()读到内存里,内存可能就爆了。

这时候,逐行读取是个更稳妥的选择。Java NIO的Files.lines(Path path)方法就非常适合,它返回一个Stream,可以一行一行地处理,避免了内存溢出的风险。你可以把每一行看作一个小的文本块,对它进行预处理和词语分割,然后把这些词语汇总到同一个Map里。
另外,Java 8的Stream API在这里也能发挥巨大作用。它不仅让代码更简洁,而且在处理集合时,如果数据量足够大,通过parallelStream()可以很方便地利用多核CPU进行并行处理。比如,你可以把整个文本文件切分成多个小块,每个小块在一个单独的线程中进行词频统计,最后再把各个线程的结果合并起来。Collectors.groupingBy结合Collectors.counting,在内部实现上已经做了很多优化,对于一般的词频统计,它的性能通常都很不错。
当然,如果文本量达到TB级别,或者需要实时处理,那可能就要考虑更专业的工具和技术了,比如分布式计算框架(Hadoop MapReduce, Spark)或者搜索引擎(Elasticsearch, Solr),它们为这种规模的数据分析提供了更强大的能力。但对于大部分单机应用场景,Java的NIO和Stream API组合,已经能很好地应对了。
除了HashMap,还有哪些数据结构可以用于词频统计?它们各有什么优缺点?
HashMap无疑是词频统计的首选,因为它提供了O(1)的平均时间复杂度来插入和查找,这对于海量词语的计数来说非常高效。但它不保证顺序,如果你需要按字母顺序或者按词频高低排序,还需要额外的步骤。
TreeMap:- 优点:
TreeMap是基于红黑树实现的,它能自动根据键(这里是词语)的自然顺序进行排序。这意味着当你遍历TreeMap时,词语会是按字母顺序排列的。如果你在统计完词频后,经常需要按字母顺序展示结果,那么直接使用TreeMap可以省去额外的排序步骤。 - 缺点: 相比
HashMap,TreeMap的插入、删除和查找操作的平均时间复杂度是O(logN),效率略低。对于非常大的数据集,这个对数级别的开销可能会变得比较明显。
- 优点:
Trie(前缀树/字典树):- 优点:
Trie是一种专门用于处理字符串集合的树形数据结构。它的主要优势在于可以高效地进行前缀匹配。在词频统计中,如果你不仅想统计完整词语的频率,还需要进行诸如“以'java'开头的词有哪些,它们的总频率是多少”之类的查询,Trie会非常高效。每个节点可以存储一个计数器,表示到该节点为止的字符串出现的次数。 - 缺点:
Trie的实现相对复杂,而且通常会占用更多的内存空间,因为每个节点都需要存储指向子节点的引用。对于仅仅是统计词频,然后按频率排序的简单需求,Trie的复杂性可能有点过度。
- 优点:
总的来说,对于大多数词频统计任务,HashMap依然是兼顾效率和简洁性的最佳选择。如果你对结果的排序有强烈的需求,可以考虑TreeMap。而Trie则更适用于需要进行复杂字符串匹配和前缀查询的场景。在选择数据结构时,始终要考虑你的具体需求和数据集的规模,没有银弹,只有最适合的方案。
到这里,我们也就讲完了《Java词频统计实现与实例解析》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于java,HashMap,文本预处理,StreamAPI,词频统计的知识点!
Golang分布式追踪实现与OpenTelemetry集成教程
- 上一篇
- Golang分布式追踪实现与OpenTelemetry集成教程
- 下一篇
- Python查找国家ISO代码方法教程
-
- 文章 · java教程 | 17小时前 | map · 并发安全 · 缓存设计 · Java教程 · java optional concurrenthashmap computeIfAbsent Map缓存
- Java computeIfAbsent 缓存初始化实战:少写判断、避开空值和并发坑
- 236浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · 异步编程 · 后端开发 · CompletableFuture · 接口聚合 · java 结果合并 completablefuture 并行调用 超时兜底
- Java CompletableFuture 多接口聚合完整流程:并行调用、超时兜底和结果合并
- 428浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · 线程安全 · DateTimeFormatter · 日期处理 · 并发问题 · java 线程安全 日期格式化 threadlocal SimpleDateFormat DateTimeFormatter
- Java SimpleDateFormat 日期偶发错乱怎么办:从共享实例到线程安全一步步排查
- 481浏览 收藏
-
- 文章 · java教程 | 3天前 | http接口 · httpclient · Java教程 · 接口调试 · 超时处理 · java 接口调用 httpclient 超时控制 状态码 响应体
- Java HttpClient 调接口实战:超时、状态码和响应体这样处理
- 224浏览 收藏
-
- 文章 · java教程 | 3天前 | 时间处理 · instant · Java教程 · 时区转换 · DateTimeFormatter · java DateTimeFormatter java.time 时区处理 ZoneId INSTANT
- Java 时间与时区处理实战:Instant、ZoneId 和 DateTimeFormatter 怎么配
- 461浏览 收藏
-
- 文章 · java教程 | 3天前 | Java · Stream · 集合统计 · 分组聚合 · Collectors · java Stream Collectors groupingBy counting summarizingInt
- Java Stream 分组统计实战:groupingBy、counting 和 summarizingInt 怎么用
- 478浏览 收藏
-
- 文章 · java教程 | 3天前 | Java · 文件读取 · 异常处理 · 资源管理 · try-with-resources · java 异常处理 try-with-resources 资源关闭 AutoCloseable 文件流
- Java try-with-resources 资源关闭实战:文件流和目录扫描这样写更稳
- 268浏览 收藏
-
- 文章 · java教程 | 4天前 | Java教程 · 后端开发 · BigDecimal · 金额计算 · java 舍入 bigdecimal 浮点误差 金额计算 RoundingMode
- Java BigDecimal 金额计算实战:避免浮点误差和舍入问题
- 324浏览 收藏
-
- 文章 · java教程 | 4天前 | 异步编程 · Java教程 · 超时治理 · CompletableFuture · java 异步任务 超时处理 completablefuture orTimeout completeOnTimeout
- Java CompletableFuture 超时处理实战:orTimeout 和兜底结果怎么选
- 421浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 210次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 232次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 202次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 368次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 365次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览

