第一次个人编程作业
论文查重项目
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/15693 |
| 这个作业的目标 | 让我们熟悉软件开发的流程 |
GitHub 仓库:https://github.com/csxy-boy/csxy
一、PSP 表格(开发前预估)
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
|---|---|---|
| Planning | 计划 | 20 |
| · Estimate | · 估计这个任务需要多少时间 | 20 |
| Development | 开发 | 300 |
| · Analysis | · 需求分析(包括学习新技术) | 50 |
| · Design Spec | · 生成设计文档 | 30 |
| · Design Review | · 设计复审 | 20 |
| · Coding Standard | · 代码规范 | 15 |
| · Design | · 具体设计 | 45 |
| · Coding | · 具体编码 | 90 |
| · Code Review | · 代码复审 | 20 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 40 |
| Reporting | 报告 | 70 |
| · Test Report | · 测试报告 | 30 |
| · Size Measurement | · 计算工作量 | 15 |
| · Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 25 |
| 合计 | 390 |
二、计算模块接口的设计与实现过程
1. 代码组织
项目采用 Maven 标准目录结构,按职责拆分为三个包:
com.papercheck.core:核心算法。包含PaperSimilarity、LcsSimilarity、NgramSimilarity、TextNormalizer。com.papercheck.io:输入输出。包含CommandLineParser、FileTextReader、AnswerWriter。com.papercheck.exception:自定义异常。包含PaperCheckException及四个子类。
Main 是唯一入口,负责把参数解析、文件读取、查重计算、答案写入串起来。这样的分层使核心算法不依赖命令行,便于单元测试。
2. 关键类与关系
Main
├─ CommandLineParser 校验 3 个命令行参数
├─ FileTextReader 读取原文和抄袭版文件
├─ PaperSimilarity 调度核心算法
│ ├─ TextNormalizer 去除空白、标点并统一英文大小写
│ ├─ LcsSimilarity 小文本:字符级 LCS + Dice 系数
│ └─ NgramSimilarity 大文本:字符 n-gram 的余弦相似度
└─ AnswerWriter 写入两位小数的答案文件
3. 算法关键
程序先把中文标点和空白去掉,只保留字母和数字,使格式差异不影响结果。
对于普通长度文本,使用字符级最长公共子序列。设原文长度为 n、抄袭文本长度为 m,LCS 长度为 k,则:
相似度 = 2k / (n + m)
使用 Dice 系数而不是 k / max(n, m),能同时惩罚插入和删除带来的长度变化。动态规划用滚动数组实现,空间复杂度由 O(n*m) 降为 O(min(n,m))。
当 n*m 超过阈值时,LCS 的双重循环会变慢,此时自动切换到字符二元组、三元组的余弦相似度,保证大文件也能在 5 秒内完成。
4. 独到之处
- 核心算法与文件 IO 分离,
PaperSimilarity可直接被测试。 - 使用 Unicode 码点而不是
char,对非 BMP 字符也能正确处理。 - 设置大文本兜底策略,兼顾准确度和运行时间。
- 自定义异常统一继承
PaperCheckException,入口只做一次捕获,错误信息清晰。
三、计算模块接口部分的性能改进
改进前
最初直接使用二维数组 dp[n+1][m+1] 实现 LCS,空间占用大,且大文件容易超时。
改进思路
- 用两个一维滚动数组替代二维数组,减少内存分配。
- 把较短的文本作为列,进一步降低空间占用。
- 设置
MAX_EXACT_PRODUCT = 500_000_000,超过后切换到线性时间的 n-gram 算法。
性能分析图

消耗最大的函数
从 Call Tree 可以看出,小文本场景下最耗时的是:
com.papercheck.core.LcsSimilarity.longestCommonSubsequenceLength
大文本场景下最耗时的是:
com.papercheck.core.NgramSimilarity.ngrams
实测结果
以 orig.txt 为原文,对提供的 5 个抄袭版文件运行 main.jar,答案如下:
| 抄袭版文件 | 重复率 |
|---|---|
| orig_0.8_add.txt | 0.91 |
| orig_0.8_del.txt | 0.89 |
| orig_0.8_dis_1.txt | 0.97 |
| orig_0.8_dis_10.txt | 0.84 |
| orig_0.8_dis_15.txt | 0.67 |
四、计算模块部分单元测试展示
项目共 26 个单元测试,覆盖核心算法、参数校验、文件读取、答案写入和异常处理,满足“至少 10 个测试用例”的要求。
核心算法测试节选:
class PaperSimilarityTest {
@Test
void modifiedSampleShouldHaveHighSimilarity() {
double value = similarity.compute(
"今天是星期天,天气晴,今天晚上我要去看电影。",
"今天是周天,天气晴朗,我晚上要去看电影。");
assertTrue(value > 0.7 && value <= 1.0, "期望得到较高相似度,实际为 " + value);
}
@Test
void oneEmptyTextShouldThrow() {
assertThrows(EmptyTextException.class, () -> similarity.compute("今天天气好", ""));
}
}
测试数据构造思路:
- 相同文本、完全无关文本、大小写差异、标点差异,验证边界和归一化。
- 固定 LCS 长度的字符串
ABCBDAB/BDCABA,验证算法正确性。 - 超长文本触发 n-gram 兜底分支。
- 缺失文件、目录作为答案路径、空参数等,验证异常路径。
覆盖率截图
五、计算模块部分异常处理说明
1. CommandLineArgumentException
设计目标:当参数数量不是 3 个,或存在空路径时,阻止程序继续执行。
测试样例:
@Test
void tooFewArgumentsShouldThrow() {
assertThrows(CommandLineArgumentException.class,
() -> CommandLineParser.validate(new String[]{"C:/tests/orig.txt"}));
}
对应场景:用户漏传抄袭版文件路径或答案文件路径。
2. FileAccessException
设计目标:原文或抄袭版文件不存在、无权限、路径是目录时,给出明确错误,而不是抛出难以理解的底层异常。
测试样例:
@Test
void missingFileShouldThrowFileAccessException() {
assertThrows(FileAccessException.class,
() -> FileTextReader.read(tempDir.resolve("not-exist.txt")));
}
对应场景:输入文件路径拼写错误或文件已被删除。
3. AnswerWriteException
设计目标:答案路径无法创建或不可写时,明确报告写入失败。
测试样例:
@Test
void directoryAsTargetShouldThrowAnswerWriteException() {
assertThrows(AnswerWriteException.class, () -> AnswerWriter.write(tempDir, 0.5));
}
对应场景:把目录误当作答案文件,或答案目录没有写权限。
4. EmptyTextException
设计目标:其中一个输入文件规范化后没有任何字母或数字时,提示无法比较。
测试样例:
@Test
void oneEmptyTextShouldThrow() {
assertThrows(EmptyTextException.class, () -> similarity.compute("今天天气好", ""));
}
对应场景:文件中只有标点、空格,或者文件为空。
六、附录:PSP 表格(实际耗时)
| PSP2.1 | Personal Software Process Stages | 实际耗时(分钟) |
|---|---|---|
| Planning | 计划 | 15 |
| · Estimate | · 估计这个任务需要多少时间 | 20 |
| Development | 开发 | 320 |
| · Analysis | · 需求分析(包括学习新技术) | 60 |
| · Design Spec | · 生成设计文档 | 25 |
| · Design Review | · 设计复审 | 25 |
| · Coding Standard | · 代码规范 | 20 |
| · Design | · 具体设计 | 40 |
| · Coding | · 具体编码 | 80 |
| · Code Review | · 代码复审 | 30 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 40 |
| Reporting | 报告 | 75 |
| · Test Report | · 测试报告 | 25 |
| · Size Measurement | · 计算工作量 | 20 |
| · Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 30 |
| 合计 | 410 |
七、项目总结
本项目把论文查重拆分为参数校验、文件 IO、文本归一化、相似度计算、答案写入五个可独立测试的部分。算法上先用字符级 LCS 保证准确性,再通过滚动数组和大文本 n-gram 兜底保证性能,单元测试覆盖了正常路径和四类异常路径。


浙公网安备 33010602011771号