从《哈利·波特》到代码:用Java词频统计,轻松分析你最爱的小说角色
2026/6/5 6:39:43 网站建设 项目流程

从《哈利·波特》到代码:用Java词频统计揭秘小说角色热度

当你第三次重读《哈利·波特》系列时,是否好奇过罗琳笔下哪位角色出场次数最多?传统的手工统计显然不现实,但借助Java编程,我们完全可以像魔法一样自动解开这个谜题。本文将带你用HashMap字符串处理技术,构建一个能够分析任意小说文本的词频统计系统,最终输出角色人气排行榜。

1. 项目准备与环境搭建

在开始编码冒险之前,我们需要准备好"魔法道具"。这个项目只需要最基本的Java开发环境:

  • JDK 8+:任何现代版本均可
  • 文本编辑器:IntelliJ IDEA、Eclipse或VS Code
  • 小说文本文件:建议从Project Gutenberg等合法渠道获取

创建新Java项目后,在项目根目录下新建resources文件夹,放入准备好的《哈利·波特》小说文本文件(如harry_potter.txt)。文本文件应当满足以下格式要求:

Harry looked at Ron and Hermione. "Let's go," Harry said. The Dursleys had never mentioned anything about Hogwarts...

提示:处理中文小说时需注意编码问题,建议统一使用UTF-8格式

2. 文本预处理与单词分割

原始文本包含各种标点和格式噪音,我们需要先进行数据清洗:

public class TextPreprocessor { public static String cleanText(String rawText) { // 转换为小写统一处理 String cleaned = rawText.toLowerCase(); // 移除所有标点符号(保留单词间空格) cleaned = cleaned.replaceAll("[^a-zA-Z\\s]", ""); // 合并连续空格 cleaned = cleaned.replaceAll("\\s+", " "); return cleaned.trim(); } }

接下来实现两种经典的分词方案对比:

分词方法优点缺点适用场景
String.split()原生支持、简单直接性能较差简单分隔
StringTokenizer高效、灵活API较旧复杂文本处理
// 使用StringTokenizer的示例 public List<String> tokenizeWithStringTokenizer(String text) { List<String> tokens = new ArrayList<>(); StringTokenizer tokenizer = new StringTokenizer(text); while (tokenizer.hasMoreTokens()) { tokens.add(tokenizer.nextToken()); } return tokens; }

3. 核心词频统计实现

我们采用HashMap作为核心数据结构,其O(1)的查询复杂度非常适合词频统计:

public Map<String, Integer> countWordFrequencies(List<String> words) { Map<String, Integer> frequencyMap = new HashMap<>(); for (String word : words) { // 如果单词长度过短可能是无意义词 if (word.length() < 3) continue; frequencyMap.put(word, frequencyMap.getOrDefault(word, 0) + 1); } return frequencyMap; }

针对小说分析的特殊需求,我们可以添加角色名字典过滤:

public static final Set<String> HP_CHARACTERS = Set.of( "harry", "ron", "hermione", "dumbledore", "voldemort", "snape", "hagrid" ); public Map<String, Integer> filterCharacterFrequencies( Map<String, Integer> fullMap) { return fullMap.entrySet().stream() .filter(entry -> HP_CHARACTERS.contains(entry.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); }

4. 结果排序与可视化输出

统计完成后,我们需要按词频降序排列结果:

public List<Map.Entry<String, Integer>> sortByFrequency( Map<String, Integer> frequencyMap) { List<Map.Entry<String, Integer>> entries = new ArrayList<>(frequencyMap.entrySet()); entries.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue())); return entries; }

添加可视化输出增强可读性:

public void printCharacterRanking(List<Map.Entry<String, Integer>> ranked) { System.out.println("《哈利·波特》角色出场次数排行榜"); System.out.println("============================"); for (int i = 0; i < ranked.size(); i++) { Map.Entry<String, Integer> entry = ranked.get(i); System.out.printf("%d. %-10s: %5d次 | %s%n", i + 1, capitalize(entry.getKey()), entry.getValue(), generateBar(entry.getValue())); } } private String generateBar(int count) { return IntStream.range(0, Math.min(count / 100, 50)) .mapToObj(i -> "█") .collect(Collectors.joining()); }

5. 完整流程整合与优化

将所有模块串联成完整流程:

public class NovelAnalyzer { public void analyze(String filePath) throws IOException { // 1. 读取文本文件 String rawText = Files.readString(Paths.get(filePath)); // 2. 文本清洗 String cleanedText = TextPreprocessor.cleanText(rawText); // 3. 单词分割 List<String> words = tokenizeWithStringTokenizer(cleanedText); // 4. 词频统计 Map<String, Integer> frequencies = countWordFrequencies(words); // 5. 角色过滤 Map<String, Integer> characterFreq = filterCharacterFrequencies(frequencies); // 6. 结果排序 List<Map.Entry<String, Integer>> ranked = sortByFrequency(characterFreq); // 7. 可视化输出 printCharacterRanking(ranked); } }

执行效果示例:

《哈利·波特》角色出场次数排行榜 ============================ 1. Harry : 4235次 | █████████████████████████████████████████████ 2. Ron : 1987次 | ████████████████████████ 3. Hermione : 1564次 | ███████████████████ 4. Dumbledore: 987次 | ███████████ 5. Snape : 845次 | █████████ 6. Voldemort : 672次 | ███████ 7. Hagrid : 543次 | ██████

6. 进阶优化与扩展思路

基础版本完成后,可以考虑以下增强功能:

  • 词干提取:处理不同词形变化(如"running"→"run")
  • 停用词过滤:忽略"the"、"and"等无意义词
  • 上下文分析:统计角色同时出现的场景
  • 情感分析:结合情感词典分析角色相关文本的情绪倾向

添加NLP扩展的示例:

// 使用OpenNLP进行命名实体识别 public Set<String> detectCharacters(String text) throws IOException { InputStream modelIn = getClass().getResourceAsStream("/en-ner-person.bin"); TokenNameFinderModel model = new TokenNameFinderModel(modelIn); NameFinderME nameFinder = new NameFinderME(model); String[] sentences = text.split("[.!?]"); Set<String> characters = new HashSet<>(); for (String sentence : sentences) { String[] tokens = sentence.split("\\s+"); Span[] nameSpans = nameFinder.find(tokens); for (Span span : nameSpans) { characters.add(String.join(" ", Arrays.copyOfRange(tokens, span.getStart(), span.getEnd()))); } } return characters; }

这个项目最有趣的部分在于,你可以自由替换文本文件来分析任何文学作品。试过《魔戒》中的角色热度吗?或者比较不同翻译版本的《三国演义》?代码不变,只需更换文本文件就能开启新的探索。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询