| 这个作业属于哪个课程 | 首页 - 计科23级34班 |
|---|---|
| 这个作业要求在哪里 | 软工作业 |
| 这个作业的目标 | 设计一个论文查重程序,并进行性能分析与测试 |
| Github仓库 | github仓库 |
一、PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 40 | 50 |
| Estimate | 估计这个任务需要多少时间 | 30 | 50 |
| Development | 开发 | 200 | 320 |
| Analysis | 需求分析(包括学习新技术) | 50 | 90 |
| Design Spec | 生成设计文档 | 30 | 40 |
| Design Review | 设计复审 | 30 | 40 |
| Coding Standard | 代码规范(为目前的开发制定合适的规范) | 20 | 20 |
| Design | 具体设计 | 40 | 60 |
| Coding | 具体编码 | 60 | 90 |
| Code Review | 代码复审 | 30 | 20 |
| Test | 测试(自我测试,修改测试,提交修改) | 40 | 40 |
| Reporting | 报告 | 50 | 70 |
| Test Report | 测试报告 | 30 | 50 |
| Size Measurement | 计算工作量 | 20 | 25 |
| Postmortem & Process improvement Plan | 事后总结,并提出修改过程改进计划 | 20 | 20 |
| 合计 | 740 | 985 |
二、模块设计与实现过程
2.1 代码组织架构
homework/
├── README.md # 项目说明文档
├── pom.xml # Maven项目配置文件
└── src/
├── main/
│ ├── java/ # Java源代码目录
│ └── resources/ # 资源文件目录
├── test/
│ └── java/ # 测试代码目录
│ └── resources/ # 测试资源目录
└── out/
└── paperChecker.jar # 打包后的jar包
2.2 核心类和函数设计
函数关系图
main() // 主函数
├── writeFile(String filePath, String content) 写入结果文件
├── readFile(String filePath) 读取文件内容
├── preprocessText(String text) // 文本预处理
│ └── IKAnalyzer 分词
├── cosineSimilarity(List<String> text1, List<String> text2) 计算余弦相似度
└── getWordFrequency(List<String> words, Set<String> allWords) 生成词频向量
2.3 重复度算法
- 文本预处理
- 对原文和抄袭版论文进行分词
- 过滤标点符号、空白字符,统一转为小写(英文),得到两篇文本的词语列表。
- 构建词频向量
- 合并两篇文本的所有词语,形成一个全局词集合。
- 分别统计原文和抄袭版中每个词语的出现次数,生成两个词频向量。
- 计算余弦相似度
- 公式:
相似度 = 两向量的点积 / (两向量的模长乘积)。 - 点积:对应位置词频相乘后求和;模长:各词频平方和的平方根。
- 结果范围为 0~1,值越接近 1 表示重复度越高。
- 公式:
- 结果处理
- 将相似度乘以 100(可选,根据需求),保留两位小数,写入输出文件。
2.4 核心代码实现
/**
* 使用余弦相似度计算文本相似度
*/
private static double cosineSimilarity(List<String> text1, List<String> text2) {
// 创建所有单词的集合
Set<String> allWords = new HashSet<>(text1);
allWords.addAll(text2);
// 计算词频向量
Map<String, Integer> vector1 = getWordFrequency(text1, allWords);
Map<String, Integer> vector2 = getWordFrequency(text2, allWords);
// 计算向量点积
int dotProduct = 0;
for (String word : allWords) {
dotProduct += vector1.get(word) * vector2.get(word);
}
// 计算向量模长
int magnitude1 = 0;
int magnitude2 = 0;
for (String word : allWords) {
magnitude1 += Math.pow(vector1.get(word), 2);
magnitude2 += Math.pow(vector2.get(word), 2);
}
// 计算余弦相似度
if (magnitude1 == 0 || magnitude2 == 0) {
return 0.0;
}
return (double) dotProduct / (Math.sqrt(magnitude1) * Math.sqrt(magnitude2));
}
三、性能分析
消耗最大的过程:处理文本的分词器分词功能

四、异常分析
空文本路径输入异常

五、单元测试
部分测试代码
@Test
public void testCalculateSimilarity() {
try {
// 测试1:完全相同的文本
String text1 = "今天是星期天,天气晴,今天晚上我要去看电影。";
String text2 = "今天是星期天,天气晴,今天晚上我要去看电影。";
List<String> words1 = PaperChecker.preprocessText(text1);
List<String> words2 = PaperChecker.preprocessText(text2);
long startTime = System.currentTimeMillis();
double similarity1 = PaperChecker.calculateSimilarity(words1, words2);
long elapsedTime = System.currentTimeMillis() - startTime;
System.out.println("完全相同文本相似度: " + similarity1);
System.out.println("完全相同文本相似度计算耗时: " + elapsedTime + " 毫秒");
// 修正:使用更宽松的误差范围,避免浮点计算精度问题
assertEquals(1.0, similarity1, 0.001);
// 测试2:相似文本
String original = "今天是星期天,天气晴,今天晚上我要去看电影。";
String plagiarized = "今天是周天,天气晴朗,我晚上要去看电影。";
List<String> originalWords = PaperChecker.preprocessText(original);
List<String> plagiarizedWords = PaperChecker.preprocessText(plagiarized);
startTime = System.currentTimeMillis();
double similarity2 = PaperChecker.calculateSimilarity(originalWords, plagiarizedWords);
elapsedTime = System.currentTimeMillis() - startTime;
System.out.println("相似文本相似度: " + similarity2);
System.out.println("相似文本相似度计算耗时: " + elapsedTime + " 毫秒");
// 修正:根据实际计算结果调整预期范围
assertTrue("相似文本相似度低于预期", similarity2 > 0.6);
} catch (Exception e) {
fail("测试失败: " + e.getMessage());
}
}

浙公网安备 33010602011771号