第一次个人编程作业
| 这个作业属于哪个课程 | 计科23级12班 |
|---|---|
| 这个作业要求在哪里 | 作业要求 |
| 这个作业的目标 | 掌握GitHub使用方法,锻炼个人开发软件能力 |
一、PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 60 | 45 |
| Estimate | 估计这个任务需要多少时间 | 60 | 45 |
| Development | 开发 | 480 | 520 |
| Analysis | 需求分析 (包括学习新技术) | 120 | 90 |
| Design Spec | 生成技术文档 | 60 | 75 |
| Design Review | 设计复审 | 30 | 25 |
| Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
| Design | 具体设计 | 90 | 110 |
| Coding | 具体编码 | 150 | 180 |
| Code Review | 代码复审 | 30 | 40 |
| Test | 测试(自我测试,修改代码,提交修改) | 90 | 120 |
| Reporting | 报告 | 180 | 165 |
| Test Repor | 测试报告 | 60 | 70 |
| Size Measurement | 计算工作量 | 30 | 25 |
| Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 90 | 70 |
| 合计 | 720 | 730 |
二、模块接口的设计与实现
1、模块划分
本系统采用模块化设计,主要包含以下几个模块:
Main.java (主程序类)
PaperComparator (论文比较核心类)
FileUtils (文件工具类)
ExceptionHandler (异常处理类)
PaperComparatorTest (测试类)
2.类之间的关系图

3.相似度计算的关键流程

4.算法的关键
1.基于余弦相似度的向量空间模型
核心原理:将文本转换为数学向量,通过计算向量夹角来衡量相似度
详细说明:
- 将每篇论文视为高维空间中的一个向量,每个维度对应一个词汇
- 使用词频作为向量坐标值,保留词汇分布特征
- 余弦相似度计算向量夹角,有效解决文本长度差异问题
2.智能自适应的文本预处理流水线
核心原理:通过多层过滤和标准化,提取文本核心比较单元
详细说明:
- 字符级清理:去除标点、特殊字符,保留字母、数字、汉字等有效内容
- 格式标准化:统一转为小写,规范化空格处理,消除格式差异
- 自适应机制:无需预定义规则,自动适应不同格式的文本输入
5.独到之处
- 采用直观的数学模型,纯Java标准库实现,零外部依赖,部署极其简单
- 支持大文件处理,避免内存溢出
- 处理时间与文本大小成正比,可预测性强
三、性能改进
使用性能分析工具初步性能分析后得出程序中各函数的消耗差不多,都不大程序时间主要消耗在:
1.文件读取操作 - 从磁盘读取orig.txt和plagiarized.txt
2.文件写入操作 - 将结果写入result.txt
3.I/O等待 - 程序在等待磁盘操作完成
因此初步推断程序性能瓶颈不在CPU计算,而在I/O操作。
下图为部分内存抽样结果

性能瓶颈:
集合操作开销:HashSet操作(add、retainAll、addAll)性能较差
内存使用:为每个字符创建Character对象,内存效率低
算法精度:基于字符集合的相似度计算准确度有限
该程序为I/O密集型应用,主要时间消耗在文件读写操作,可通过算法准确性优化和I/O优化进行程序改进。
四、单元测试
测试函数说明:
testIdenticalTexts()- 测试完全相同的文本
testCompletelyDifferent()- 测试完全不同的文本
testEmptyFiles()- 测试两个空文件
testOneEmptyFile()- 测试一个空文件与一个非空文件
testSimilarTexts()- 测试相似但不完全相同的文本
测试数据构造思路:
1.边界条件测试:包括空文件、完全相同的文件等极端情况
2.典型场景测试:包括相似但不完全相同的文本
3.差异性测试:完全不同的文本用于验证低相似度情况
4.文件操作测试:通过临时文件来测试文件读取功能
测试覆盖率:

由图可知核心算法得到充分测试,但整体覆盖存在明显短板。
1. 核心模块测试充分
PaperComparator类:作为查重程序的核心算法,其行覆盖率 (100%) 和方法覆盖率 (100%) 均达到满分。表明单元测试(如完全相同、完全不同文本的测试)成功执行了所有核心逻辑代码。
2. 测试代码自身覆盖完全
PaperComparatorTest类:行覆盖率达100%,这证明所有测试用例都被执行,测试代码本身没有冗余。
3. 工具类与入口类缺乏测试
FileUtils类:方法覆盖率仅为50%,说明这个工具类中可能用于写文件或处理异常的方法未被测试到。
Main和 ExceptionHandler类:覆盖率为0%,这表明应用程序的启动入口和全局异常处理逻辑都尚未被测试覆盖。
五、异常处理说明
- IOException (文件操作错误)
设计目标:处理所有与文件读写相关的异常。例如,用户提供的论文文件路径错误、文件不存在、文件权限问题、磁盘空间不足等。捕获此异常可以防止程序因文件问题而突然崩溃,并给出明确的错误提示。
@Test
public void testFileNotExist() {
String invalidPath = "nonexistent_orig.txt";
String validPath = "test2_plagiarized.txt";
double result = PaperComparator.calculateSimilarity(invalidPath, validPath);
assertEquals("处理文件不存在异常时,应返回错误码-1", -1, result, DELTA);
} - General Exception (程序执行错误)
设计目标:作为一个全局的异常捕获机制。用于处理业务逻辑中未预料的运行时错误(如空指针异常、数组越界、算术异常等)。确保任何未曾预料到的错误都不会导致程序直接崩溃,而是被捕获并记录。
@Test
public void testAlgorithmRuntimeError() throws IOException {
Files.write(Paths.get("test_special1.txt"), "特殊内容A".getBytes());
Files.write(Paths.get("test_special2.txt"), "触发除零异常的内容B".getBytes());
doubleresult = PaperComparator.calculateSimilarity
("test_special1.txt","test_special2.txt");
assertEquals("处理算法运行时异常时,应返回错误码-1", -1, result, DELTA);
}
浙公网安备 33010602011771号