Java文本处理:高效移除停用词与词频统计

云辰小哥_5157

云辰小哥_5157

2025-08-26

974人浏览

原创

java文本处理:高效移除停用词与词频统计

本教程旨在指导读者如何使用Java高效地从文本文件中移除指定的停用词,并进一步统计清洗后文本中各词汇的出现频率。通过结合Java NIO.2文件操作、字符串处理以及集合框架,我们将构建一个健壮的解决方案,实现文本数据的预处理和基本分析。

在自然语言处理(NLP)领域,文本预处理是至关重要的一步,其中移除停用词(Stop Words)是常见的操作。停用词通常指那些在文本中频繁出现但对文本语义贡献不大的词汇,例如“的”、“是”、“在”等。移除这些词汇有助于降低数据维度,提高后续文本分析(如词频统计、文本分类)的效率和准确性。本教程将详细介绍如何利用Java实现这一过程,并在此基础上进行词频统计。

核心概念与技术栈

  1. 文件I/O操作: 采用Java NIO.2 (java.nio.file.Files 和 java.nio.file.Paths),它提供了更现代、高效的文件读写API,相比传统的 FileInputStream 和 BufferedReader 更为简洁。
  2. 字符串处理: 利用 String 类的 split() 方法进行文本分割,以及 replaceAll() 方法进行停用词替换。
  3. 集合框架: 使用 java.util.Map (具体为 HashMap) 来存储词汇及其出现频率,并通过 java.util.List 和 Collections.sort() 对词频进行排序。
  4. 正则表达式: 在停用词替换和词汇分割时,合理使用正则表达式可以实现更精确的匹配和处理。

实现步骤

我们将分步实现:首先读取主文本文件和停用词文件,然后从主文本中移除停用词,最后统计剩余词汇的频率。

1. 读取文件内容

首先,我们需要将 hello.txt(主文本)和 stopwords.txt(停用词列表)的内容读取到内存中。Files.readString() 方法是读取小型到中型文件内容的理想选择。

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class TextProcessor {

    public static void main(String[] args) {
        try {
            // 读取主文本文件内容
            String mainTextContent = Files.readString(Paths.get("hello.txt"), Charset.defaultCharset());
            // 读取停用词文件内容
            String stopWordsContent = Files.readString(Paths.get("stopwords.txt"), Charset.defaultCharset());

            System.out.println("原始文本内容:
" + mainTextContent);
            System.out.println("停用词内容:
" + stopWordsContent);

            // ... 后续处理

        } catch (IOException e) {
            System.err.println("文件读取错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

示例文件内容:

  • hello.txt: remove leave remove leave remove leave re move remov e leave remove hello remove world!
  • stopwords.txt: remove world

2. 移除停用词

读取停用词后,我们需要将它们从主文本内容中移除。这里我们将停用词文件内容按空白字符分割成单独的词汇,然后遍历这些词汇,使用 String.replaceAll() 方法将它们从主文本中替换掉。为了确保只替换完整的单词,而不是单词的一部分,我们将结合正则表达式的单词边界 。

// ... (接上一步代码)

            String[] stopWordsArray = stopWordsContent.split("\s+"); // 使用 \s+ 匹配一个或多个空白字符

            String cleanedText = mainTextContent;
            for (String stopWord : stopWordsArray) {
                // 构建正则表达式,使用  确保匹配整个单词
                // 例如,如果 stopWord 是 "remove",则匹配 "remove"
                // 忽略大小写,并处理可能的标点符号
                cleanedText = cleanedText.replaceAll("(?i)\b" + stopWord + "\b", "");
            }

            // 清理多余的空白字符,将多个空格替换为单个空格,并去除首尾空格
            cleanedText = cleanedText.replaceAll("\s+", " ").trim();

            System.out.println("
清洗后的文本内容:
" + cleanedText);

// ...

注意事项:

password-manager
password-manager

本地密码管理技能,可添加、编辑、删除、搜索密码,支持分类和备注,数据本地存储,支持导入导出和备份。用户提及密码、账号、密码管理、保存密码或查找密码时使用。

下载
  • split("\s+") 用于将停用词字符串分割成数组,\s+ 匹配一个或多个空白字符。
  • replaceAll("(?i)\b" + stopWord + "\b", "") 是关键。
    • (?i) 是一个嵌入式标志表达式,表示匹配不区分大小写。
    •  是单词边界,确保我们只替换完整的单词,而不是单词的一部分(例如,避免将 "remove" 从 "remover" 中移除)。
    • 替换为空字符串 ""。
  • 最后的 replaceAll("\s+", " ").trim() 用于将清洗后可能产生的多个连续空格合并为一个,并去除文本首尾的空格,使文本更整洁。

3. 词频统计与排序

清洗后的文本现在只包含有意义的词汇。接下来,我们将统计这些词汇的出现频率,并按频率降序排列,以找出最常出现的词。

// ... (接上一步代码)

            // 将清洗后的文本分割成单词
            // 使用正则表达式匹配非字母数字字符作为分隔符,并转换为小写
            String[] words = cleanedText.toLowerCase().split("[^a-zA-Z0-9]+");

            // 使用 HashMap 存储词频
            Map<string long> wordFrequencies = Arrays.stream(words)
                                                    .filter(word -> !word.isEmpty()) // 过滤空字符串
                                                    .collect(Collectors.groupingBy(
                                                        String::valueOf, // 词汇本身作为键
                                                        Collectors.counting() // 统计出现次数
                                                    ));

            // 将词频Map转换为List,以便排序
            // 使用LinkedHashMap保持插入顺序(在排序后重新构建时有用)
            Map<string long> sortedWordFrequencies = wordFrequencies.entrySet()
                                                                     .stream()
                                                                     .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) // 按值降序排序
                                                                     .collect(Collectors.toMap(
                                                                         Map.Entry::getKey,
                                                                         Map.Entry::getValue,
                                                                         (oldValue, newValue) -> oldValue, // 合并函数,这里不应该发生键冲突
                                                                         LinkedHashMap::new // 保持排序顺序
                                                                     ));

            System.out.println("
词频统计 (降序):");
            sortedWordFrequencies.forEach((word, count) -> System.out.println(word + ": " + count));

            // 如果需要显示Top N词汇
            int topN = 3; // 例如,显示前3个
            System.out.println("
Top " + topN + " 词汇:");
            sortedWordFrequencies.entrySet()
                                 .stream()
                                 .limit(topN)
                                 .forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));

// ...</string></string>

解释:

  • cleanedText.toLowerCase().split("[^a-zA-Z0-9]+"):
    • toLowerCase():将所有词汇转换为小写,确保“Hello”和“hello”被视为同一个词。
    • split("[^a-zA-Z0-9]+"):使用一个或多个非字母数字字符作为分隔符来分割文本,这有助于移除标点符号并获取纯粹的单词。
  • Arrays.stream(words).filter(word -> !word.isEmpty()).collect(Collectors.groupingBy(String::valueOf, Collectors.counting())):这是一个高效的Java 8 Stream API用法。
    • filter(word -> !word.isEmpty()):过滤掉因分割可能产生的空字符串。
    • Collectors.groupingBy(String::valueOf, Collectors.counting()):将Stream中的元素按其值(单词本身)分组,并计算每个组(单词)的出现次数。
  • wordFrequencies.entrySet().stream().sorted(...).collect(...):
    • 将 HashMap 的 entrySet() 转换为Stream。
    • sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())):根据Map的值(即词频)进行降序排序。
    • Collectors.toMap(...):将排序后的Stream重新收集到一个新的 LinkedHashMap 中,以保持排序顺序。

4. 将结果写入文件(可选)

如果需要将清洗后的文本或词频统计结果保存到文件中,可以使用 Files.write() 方法。

// ... (接上一步代码)

            // 将清洗后的文本写入新文件
            // Files.write(Paths.get("cleaned_hello.txt"), cleanedText.getBytes(Charset.defaultCharset()));
            // System.out.println("
清洗后的文本已保存到 cleaned_hello.txt");

            // 将词频统计结果写入文件
            // StringBuilder freqOutput = new StringBuilder();
            // sortedWordFrequencies.forEach((word, count) -> freqOutput.append(word).append(": ").append(count).append("
"));
            // Files.write(Paths.get("word_frequencies.txt"), freqOutput.toString().getBytes(Charset.defaultCharset()));
            // System.out.println("词频统计结果已保存到 word_frequencies.txt");

// ...

完整代码示例

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class TextProcessor {

    public static void main(String[] args) {
        try {
            // 1. 读取文件内容
            String mainTextContent = Files.readString(Paths.get("hello.txt"), Charset.defaultCharset());
            String stopWordsContent = Files.readString(Paths.get("stopwords.txt"), Charset.defaultCharset());

            System.out.println("--- 原始数据 ---");
            System.out.println("原始文本内容:
" + mainTextContent);
            System.out.println("停用词内容:
" + stopWordsContent);

            // 2. 移除停用词
            String[] stopWordsArray = stopWordsContent.split("\s+");
            String cleanedText = mainTextContent;
            for (String stopWord : stopWordsArray) {
                // 使用  确保匹配整个单词,(?i) 忽略大小写
                cleanedText = cleanedText.replaceAll("(?i)\b" + stopWord + "\b", "");
            }
            // 清理多余的空白字符
            cleanedText = cleanedText.replaceAll("\s+", " ").trim();

            System.out.println("
--- 文本清洗结果 ---");
            System.out.println("清洗后的文本内容:
" + cleanedText);

            // 3. 词频统计与排序
            // 将清洗后的文本分割成单词,转换为小写,并过滤空字符串
            String[] words = cleanedText.toLowerCase().split("[^a-zA-Z0-9]+");

            Map<string long> wordFrequencies = Arrays.stream(words)
                                                    .filter(word -> !word.isEmpty())
                                                    .collect(Collectors.groupingBy(
                                                        String::valueOf,
                                                        Collectors.counting()
                                                    ));

            // 按词频降序排序
            Map<string long> sortedWordFrequencies = wordFrequencies.entrySet()
                                                                     .stream()
                                                                     .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                                                                     .collect(Collectors.toMap(
                                                                         Map.Entry::getKey,
                                                                         Map.Entry::getValue,
                                                                         (oldValue, newValue) -> oldValue,
                                                                         LinkedHashMap::new
                                                                     ));

            System.out.println("
--- 词频统计 (降序) ---");
            sortedWordFrequencies.forEach((word, count) -> System.out.println(word + ": " + count));

            // 4. 显示Top N词汇 (例如 Top 3)
            int topN = 3;
            System.out.println("
--- Top " + topN + " 词汇 ---");
            sortedWordFrequencies.entrySet()
                                 .stream()
                                 .limit(topN)
                                 .forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));

            // 可选:将清洗后的文本写入文件
            // Files.write(Paths.get("cleaned_hello.txt"), cleanedText.getBytes(Charset.defaultCharset()));
            // System.out.println("
清洗后的文本已保存到 cleaned_hello.txt");

        } catch (IOException e) {
            System.err.println("文件操作错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}</string></string>

运行上述代码,如果 hello.txt 和 stopwords.txt 存在于项目根目录,将得到类似以下输出:

--- 原始数据 ---
原始文本内容:
remove leave remove leave remove leave re move remov e leave remove hello remove world!
停用词内容:
remove world

--- 文本清洗结果 ---
清洗后的文本内容:
leave leave leave re move remov e leave hello !

--- 词频统计 (降序) ---
leave: 4
re: 1
move: 1
remov: 1
e: 1
hello: 1

--- Top 3 词汇 ---
leave: 4
re: 1
move: 1

注意事项与优化

  1. 文件编码: 始终明确指定文件编码,例如 Charset.defaultCharset() 或 StandardCharsets.UTF_8,以避免乱码问题。
  2. 大文件处理: Files.readString() 适用于中小型文件。对于GB级别的大文件,建议使用 Files.lines() 逐行处理,或结合 BufferedReader 避免一次性加载所有内容到内存,防止 OutOfMemoryError。
  3. 正则表达式的精确性:
    •  单词边界非常重要,它可以防止意外替换。例如,如果没有 ,移除“cat”可能会影响“category”。
    • 对于更复杂的停用词(如包含特殊字符),可能需要对停用词本身进行正则转义:Pattern.quote(stopWord)。
  4. 性能优化:
    • String.replaceAll() 会创建新的字符串对象,在循环中频繁调用可能效率不高。对于极端性能要求,可以考虑使用 StringBuilder 结合 Matcher.appendReplacement() 和 appendTail() 进行更细粒度的控制。
    • 如果停用词列表非常庞大,将其存储在 HashSet 中进行快速查找(contains() 方法)会比 replaceAll 循环更高效,但这需要改变处理逻辑,即先将主文本分割成词,然后逐个检查是否为停用词。
  5. 文本标准化:
    • 在词频统计前,将所有词汇转换为小写是标准做法。
    • 处理标点符号:本教程使用 split("[^a-zA-Z0-9]+") 来移除标点,这在大多数情况下是足够的。对于更复杂的场景,可能需要更精细的标点符号处理逻辑。
  6. 错误处理: 始终包含健壮的异常处理,特别是文件I/O操作,以应对文件不存在、权限不足等问题。

总结

通过本教程,我们学习了如何利用Java NIO.2、字符串处理和Stream API来高效地实现文本文件的停用词移除和词频统计。这个过程是许多文本分析任务的基础,掌握这些技术将有助于更好地处理和理解文本数据。在实际应用中,根据具体需求,可以进一步优化和扩展这些功能,例如支持不同语言的停用词、更复杂的文本清洗规则等。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

2023.06.20

4696

8

正则表达式不包含
正则表达式不包含

正则表达式,又称规则表达式,,是一种文本模式,包括普通字符和特殊字符,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式的文本。php中文网给大家带来了有关正则表达式的相关教程以及文章,希望对大家能有所帮助。

2023.07.05

3181

4

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

8842

9

java正则表达式匹配字符串
java正则表达式匹配字符串

在Java中,我们可以使用正则表达式来匹配字符串。本专题为大家带来java正则表达式匹配字符串的相关内容,帮助大家解决问题。

2023.08.11

812

5

正则表达式空格
正则表达式空格

正则表达式空格可以用“\s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。本专题为大家提供正则表达式相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.31

520

5

Python爬虫获取数据的方法
Python爬虫获取数据的方法

Python爬虫可以通过请求库发送HTTP请求、解析库解析HTML、正则表达式提取数据,或使用数据抓取框架来获取数据。更多关于Python爬虫相关知识。详情阅读本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.13

693

12

正则表达式空格如何表示
正则表达式空格如何表示

正则表达式空格可以用“\s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。想了解更多正则表达式空格怎么表示的内容,可以访问下面的文章。

2023.11.17

406

3

正则表达式中如何匹配数字
正则表达式中如何匹配数字

正则表达式中可以通过匹配单个数字、匹配多个数字、匹配固定长度的数字、匹配整数和小数、匹配负数和匹配科学计数法表示的数字的方法匹配数字。更多关于正则表达式的相关知识详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.12.06

739

7

php正则表达式有哪些
php正则表达式有哪些

php正则表达式有"/pattern/"、"^"、"$"、"."、"[]"、"[^]"、"[a-z]"、"[A-Z]"、"[0-9]"、"\d"、"\D"、"\w"、"\W"、&a

2024.02.23

312

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习