第一次个人编程作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS?page=2
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/homework/15702
这个作业的目标 完成个人编程作业编码部分
作业所在地址 https://github.com/Parker-HJD/Parker-HJD/releases/tag/4207

一、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 30
· Estimate · 估计这个任务需要多少时间 20 30
Development 开发 300 360
· Analysis · 需求分析(包括学习新技术) 30 40
· Design Spec · 生成设计文档 20 20
· Design Review · 设计复审 10 10
· Coding Standard · 代码规范(为目前的开发制定合适的规范) 10 10
· Design · 具体设计 30 40
· Coding · 具体编码 150 180
· Code Review · 代码复审 20 20
· Test · 测试(自我测试,修改代码,提交修改) 30 40
Reporting 报告 60 70
· Test Report · 测试报告 30 30
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结,并提出过程改进计划 20 30
合计 380 460

二、计算模块接口的设计与实现

1. 整体结构

本项目只使用单个 Main 类,包含以下核心静态方法:

  1. readFile(String path):读取文本文件内容,返回字符串;使用 try-with-resources 自动关闭文件流。
  2. getWords(String text):文本预处理,过滤非中英文字符,分词得到词语列表。
  3. calcHash(String word):对单个词语计算 64 位 hash 值。
  4. simHash(List<String> words):对词语列表生成 64 位 SimHash 指纹。
  5. hammingDistance(long hash1, long hash2):计算两个指纹之间的海明距离。
  6. calcSimilarity(long hash1, long hash2):由海明距离换算文本相似度百分比。
  7. main(String[] args):程序入口,读取命令行参数,调用各个模块,输出结果写入结果文件。

模块调用流程:
main入口 → 读取两个文本文件 → 文本分词预处理 → 生成两份 SimHash 指纹 → 计算海明距离 → 换算相似度 → 写入输出文件。

2. SimHash 算法关键

  1. 分词:把文档切分成词语,过滤符号、空白字符;
  2. 哈希:每个词语映射成 64 位无符号 hash;
  3. 加权累加:每一位,hash 为 1 则 + 1,为 0 则 - 1;
  4. 降维:累加后,大于 0 置 1,否则置 0,得到文档 64 位 SimHash 指纹;
  5. 相似度:比对两个指纹的海明距离,距离越小代表文本越相似。

3. 独到之处

  • 使用try-with-resources管理 IO,自动释放资源,避免文件句柄泄漏;
  • 正则预编译,只在类加载时编译一次,减少重复开销;
  • 完善异常捕获,对文件缺失、空文件、参数数量错误等场景做容错;
  • 模块化拆分,每个函数职责单一,方便单元测试。

三、计算模块性能改进

1. 性能改进耗时记录

性能分析与优化,实际耗时 40 分钟

2. 改进思路

原始版本:getWords()中使用String.replaceAll(),每次调用都会重复编译正则表达式。长文本场景下,分词函数占用 CPU 最高,是性能瓶颈。
优化方案:把正则提取为static final Pattern静态常量,类加载阶段仅编译一次,后续直接复用 Matcher 对象,省去反复编译正则的开销。
image

四、计算模块部分单元测试展示

image
image

五、模块部分异常处理说明

问题 1:NoSuchFileException 文件找不到

现象:运行程序抛出异常,提示找不到 txt 文件。
原因:代码里写的文件名和实际文件名不一致;文件放错目录,放在 src 里面而不是项目根目录;Windows 隐藏文件扩展名,实际后缀不对。
处理:修改代码里的文件名,统一为 test.txt;把 orig.txt、test.txt 放到和 pom.xml 同级的项目根目录;增加异常捕获,区分文件不存在和普通 IO 错误,给出中文提示。

问题 2:空指针 NullPointerException

现象:传入空文本,调用列表方法时报空指针崩溃。
原因:分词方法在文本为空时直接返回 null,后续遍历集合时报错。
处理:修改 splitWords,空输入时返回空 ArrayList,不返回 null;calcSimilarity 方法开头增加判空,list 为 null 或者空集合直接返回相似度 0。

问题 3:除零异常 ArithmeticException

现象:两段文本全为空,计算余弦相似度除法时报错。
原因:向量模长 magnitudeA 或 magnitudeB 等于 0,分母为 0。
处理:计算前判断模长,如果任意一方为 0,直接返回 0,跳过除法计算。

问题 4:词频平方计算整数溢出

现象:长文本词频很高时,相似度计算结果异常。
原因:int 相乘超出 int 范围,数值溢出变成负数。
处理:强转为 long 进行乘法计算 (long)countA * countA,避免溢出。

问题 5:单元测试用例失败

现象:JUnit 测试报错,断言不通过。
原因:分词正则只提取汉字,标点、数字字母全部丢弃,预期列表写错;文件读取用例依赖 orig.txt 存在。
处理:修正测试预期结果;新增专门的异常测试用例,校验找不到文件时是否抛出指定异常。

问题 6:最初余弦相似度逻辑错误

现象:两段相同文本算出来相似度不等于 1。
原因:循环遍历词频时,重复累加向量模长;点积计算逻辑写错。
处理:重构 calcSimilarity,分开计算点积、向量 A 模、向量 B 模,修正循环逻辑。

问题 7:命令行参数数组越界

现象:直接运行 main 方法,读取 args [] 时报数组越界。
原因:直接在 IDE 运行时没有传入命令行参数,args 数组为空。
处理:取消命令行参数读取,直接指定 orig.txt 和 test.txt 文件名。

六、总结以及改进计划

本项目基于余弦相似度实现文本查重功能,流程分为读取文本、中文单字分词、统计词频、向量计算、输出相似度。开发中先后解决文件读取、空指针、除零、整数溢出、单元测试校验等问题,搭配异常捕获与防御式编程提升程序健壮性。
异常处理采用先捕获具体异常、再捕获通用 IO 异常的策略;单元测试覆盖正常文本、相同文本、完全无关文本、空文本、文件不存在场景,验证核心函数逻辑正确性。程序最终可以读取 orig.txt 与 test.txt,输出保留四位小数的相似度结果。

现有不足

  1. 分词仅按单个汉字切割,不支持词语分词,语义能力弱,长句改写后查重结果误差大。
  2. 仅支持 txt 文件,不支持 docx、pdf 等常见文档格式。
  3. 没有停用词过滤,“的、是、在” 这类高频虚词会干扰相似度计算。
  4. 没有结果持久化,相似度只能控制台打印,不能自动保存到文件。
  5. 缺少命令行参数,文件路径写死在代码内,更换文件需要修改源码。
  6. 缺少日志,错误信息简单,不利于复杂场景调试。

改进方向

  1. 分词优化:引入中文分词库(如 HanLP),由单字切分改为词语切分,提升相似度计算贴合语义。
  2. 增加停用词表,过滤无意义虚词,减少噪声对向量的干扰。
  3. 扩展文件读取模块,支持更多文档类型;增加将相似度结果写入 result.txt 的功能。
  4. 支持命令行传入两个文件路径,不用修改代码即可更换待比对文本。
  5. 优化日志输出,区分普通信息与错误信息;增加进度提示。
  6. 可拓展功能:支持多文件批量查重、设置相似度阈值,当相似度超过阈值给出高重复提示。
posted @ 2026-09-15 18:12  Zmjqka  阅读(11)  评论(0)    收藏  举报