第一次个人编程作业
github仓库链接:https://github.com/zzh26s/zzh26s/tree/main/3124004076
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS |
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/15693 |
| 这个作业的目标 | 让我们熟悉软件开发的流程 |
1.PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 15 | 15 |
| · Estimate | 估计这个任务需要多少时间 | 15 | 15 |
| Development | 开发 | ||
| · Analysis | 需求分析 (包括学习新技术) | 20 | 25 |
| · Design Spec | 生成设计文档 | 35 | 30 |
| · Design Review | 设计复审 | 10 | 10 |
| · Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
| · Design | 具体设计 | 40 | 35 |
| · Coding | 具体编码 | 90 | 120 |
| · Code Review | 代码复审 | 25 | 20 |
| · Test | 测试(自我测试,修改代码,提交修改) | 60 | 90 |
| Reporting | 报告 | ||
| · Test Report | 测试报告 | 20 | 30 |
| · Size Measurement | 计算工作量 | 10 | 10 |
| · Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 25 |
| 合计 | 375 | 430 |
一、计算模块接口的设计与实现过程
1. 代码整体组织结构
本次论文查重程序采用模块化分层设计,将功能分为工具类、核心算法类、程序入口类三大部分,共设计三个核心类,职责完全分离,结构清晰、便于测试与维护:
-
FileUtils 文件工具类 负责所有文件读写、文件 IO 异常处理,提供统一的文件读取、文件写入接口,完全隔离 IO 操作与算法逻辑,让核心算法无需关心文件来源。
-
PlagiarismChecker 查重核心算法类 程序核心计算模块,封装所有文本预处理、特征提取、相似度计算逻辑,提供唯一对外开放接口 calculateSimilarity(),是整个项目的核心功能模块。
-
Main 主入口类 负责接收命令行参数、参数校验、调用工具类与算法类、输出最终结果,作为程序启动入口,不处理复杂业务逻辑。
2. 类与函数之间的调用关系
程序从 Main 启动,首先校验命令行参数合法性;
Main 调用 FileUtils 读取原文、抄袭版文本;
将读取到的文本传入 PlagiarismChecker 的核心计算方法;
算法计算完成返回重复率结果;
Main 再次调用 FileUtils 将结果写入指定输出文件。
整体调用流程: Main → FileUtils(读文件)→ PlagiarismChecker(计算相似度)→ FileUtils(写结果)
3. 关键函数说明与流程图说明
| 项目结构 |
|---|
| 3124004076/ ├── pom.xml └── src/ ├── main/ │ └── java/ │ ├── Main.java │ ├── PlagiarismChecker.java │ └── FileUtils.java └── test/ └── java/ └── PlagiarismCheckerTest.java |
项目核心关键函数为:
| 函数名 | 函数功能 |
|---|---|
| calculateSimilarity() | 对外统一查重接口 |
| preprocess() | 文本清洗预处理 |
| get2GramFrequency() | 文本特征分词 |
| cosineSimilarity() | 余弦相似度计算 |
流程为:文本输入 → 文本清洗预处理 → 生成 2-gram 特征向量 → 词频统计 → 余弦相似度计算 → 保留两位小数输出重复率
4. 核心算法原理
本项目采用 2-gram 滑动分词 + 余弦相似度算法 实现论文查重:
对原文和抄袭文本进行统一清洗,过滤标点、空格、换行符,统一小写,消除无关字符干扰;
使用二元语法(2-gram)对连续文本进行滑动切分,将整段文本转化为连续字符片段特征;
统计两段文本的字符片段词频,构建文本特征向量;
通过余弦相似度公式计算两段向量的夹角相似度,最终得到文本重复率;
结果保留两位小数,满足题目输出规范。
5. 算法独到之处与优化亮点
适配中文查重场景 普通单词分词不适合中文无空格文本,本项目采用字符级 2-gram 分割,完美适配中文论文查重,识别语序微调、同义词替换、少量增删改场景。
强容错文本预处理 自动过滤所有无效符号,不受换行、空格、中英文标点干扰,鲁棒性极强。
算法精度高、稳定性好 余弦相似度不受文本长度差异影响,相比匹配字符数占比,能够更科学地反映文本相似程度。
完全模块化、低耦合设计 IO、算法、入口完全分离,支持后期替换算法、拓展多粒度分词、增加停用词库,扩展性强。
运行速度快、资源占用低 纯内存运算,无网络请求、无第三方依赖,5 秒内可完成超大文本检测,满足作业性能限制要求。
二,计算模块接口部分的性能改进
本次性能优化总共花费30 分钟。
改进思路
在未优化版本中,程序在生成 2-gram 特征时,循环内频繁创建 String 对象,产生大量临时对象,造成频繁垃圾回收;并且对文本进行两次遍历,一次生成片段列表,一次统计词频,存在冗余计算。
我使用 Profiler 性能分析工具对程序进行 CPU 采样分析,定位程序热点。分析发现get2GramFrequency()是 CPU 消耗最大的函数。
内存分配图

CPU 耗时分析

优化方案:
- 将循环内字符串拼接改为
StringBuilder,减少大量临时 String 对象创建,降低 GC 压力; - 边遍历文本边统计二元片段词频,只遍历文本一次,省去中间存储所有 2-gram 片段的 List 集合,减少内存占用;
- 增加前置判断,预处理后文本长度不足 2 时,直接返回相似度 0,跳过后续计算,避免无效运算。
三,单元测试展示
3.1 测试目的
为验证本次实现的基于2-gram字符模型+余弦相似度的文本查重算法的正确性、稳定性与边界兼容性,针对完全相同文本、完全不同文本、部分相似文本、单文本为空、双文本为空五种典型场景设计单元测试用例,覆盖常规业务场景与极端边界场景,校验查重结果是否符合预期逻辑。
3.2 测试环境
开发工具:IDEA 运行环境:JDK 1.8 测试框架:JUnit 4 核心算法:2-gram字符切分 + 余弦相似度计算
3.3 测试用例设计
本次共设计5组全覆盖测试用例,涵盖正常场景与边界异常场景:
用例1:完全相同文本 —— 校验相同文本重复率是否为1.0
用例2:完全不同文本 —— 校验无相似内容时重复率是否为0.0
用例3:部分相似文本 —— 校验局部文本改动后相似度计算合理性
用例4:原文为空、待查文本有内容 —— 单边空文本边界测试
用例5:两段文本均为空 —— 双边空文本极端边界测试
3.4部分测试代码
| 序号 | 用例名称 | 测试代码片段 |
|---|---|---|
| 1 | testIdenticalText(完全相同文本) | ```java |
| @Test | ||
| public void testIdenticalText() { |
double rate = PlagiarismChecker.calculateSimilarity("今天天气不错", "今天天气不错");
System.out.println("【完全相同文本】查重相似度 = " + rate);
assertEquals(1.00, rate, 0.01);
}
| 2 | testCompletelyDifferent(完全不同文本) | ```java
@Test
public void testCompletelyDifferent() {
double rate = PlagiarismChecker.calculateSimilarity("猫喜欢吃鱼", "飞机在天上飞");
System.out.println("【完全不同文本】查重相似度 = " + rate);
assertEquals(0.00, rate, 0.01);
}
``` |
| 3 | testExampleModify0(部分相似文本) | ```java
@Test
public void testExampleModify0() {
double rate = PlagiarismChecker.calculateSimilarity("今天天气不错适合散步", "今天天气很好适合散步");
System.out.println("【部分相似文本】查重相似度 = " + rate);
assertTrue(rate > 0.5);
}
``` |
| 4 | testEmptyOrigin(原文空,待查有内容) | ```java
@Test
public void testEmptyOrigin() {
double rate = PlagiarismChecker.calculateSimilarity("", "测试文字");
System.out.println("【原文空,待查有内容】查重相似度 = " + rate);
assertEquals(0.00, rate, 0.01);
}
``` |
| 5 | testBothEmpty(两段都为空文本) | ```java
@Test
public void testBothEmpty() {
double rate = PlagiarismChecker.calculateSimilarity("", "");
System.out.println("【两段都为空文本】查重相似度 = " + rate);
assertEquals(1.00, rate, 0.01);
}
3.5 单元测试运行结果
所有测试用例全部运行通过,无报错、无失败,控制台输出真实查重相似度结果如下:
【完全不同文本】查重相似度 = 0.0 【两段都为空文本】查重相似度 = 1.0 【完全相同文本】查重相似度 = 1.0 【部分相似文本】查重相似度 = 0.67 【原文空,待查有内容】查重相似度 = 0.0
3.5 测试结果分析
- 当两段文本完全一致时,相似度为 1.0,符合完全抄袭的判定逻辑; 2. 当两段文本内容完全无关时,相似度为 0.0,算法无误判、无冗余相似度; 3. 局部修改文本后相似度为 0.67,能够精准识别部分相似内容,算法识别效果合理; 4. 单边空文本场景下相似度为 0.0,符合查重业务逻辑; 5. 双边空文本场景返回 1.0,适配代码预设的极端边界处理规则。
综上,本次实现的查重工具类 PlagiarismChecker 运行稳定、计算结果准确,各类常规与边界场景均可正常适配,满足文本查重的功能需求。
本查重计算模块PlagiarismChecker设计了 3 类业务异常,使用自定义异常TextCheckException进行抛出,用于校验输入合法性,提前拦截非法输入,避免程序崩溃或者产生无意义的相似度计算结果。
四.计算模块部分异常处理说明
异常 1:原文为 null
触发场景:调用查重方法时,传入的原文参数为null
设计目标:防止程序直接抛出底层NullPointerException。使用自定义业务异常,提供清晰的错误提示,区分是业务输入错误,而不是未知的底层空指针崩溃,方便快速定位问题。
单元测试样例
@Test(expected = TextCheckException.class) public void testOriginTextNull(){ PlagiarismChecker.calculateSimilarity(null,"测试文本"); }
预期结果:抛出TextCheckException,提示原文不能为 null。
异常 2:待检测文本为 null
触发场景:调用查重方法时,待比对的抄袭文本参数为null
设计目标:对第二个输入参数做空值校验,和原文 null 校验逻辑保持一致,提前拦截非法输入,给出明确业务提示,防止后续字符串操作触发底层空指针。
单元测试样例
@Test(expected = TextCheckException.class) public void testCopyTextNull(){ PlagiarismChecker.calculateSimilarity("原文内容",null); }
预期结果:抛出TextCheckException,提示待检测文本不能为 null。
异常 3:预处理后文本长度不足 2,无法生成 2-gram
触发场景:输入文本经过预处理过滤标点符号后,有效字符长度小于 2,无法截取 2-gram 二元字符片段。
设计目标:2-gram 算法要求文本至少包含 2 个有效字符,文本太短无法构建二元字符片段,不能进行余弦相似度计算。抛出异常告知调用方:输入文本过短,不满足查重算法运行前提,不继续执行无效计算。
单元测试样例
@Test(expected = TextCheckException.class) public void testTextTooShort(){ PlagiarismChecker.calculateSimilarity("啊","啊"); }
预期结果:抛出TextCheckException,提示文本处理后长度不足 2,无法生成 2-gram。
异常处理小结
所有异常校验放在查重计算模块的最前端,在执行 2-gram 切分、余弦相似度计算之前拦截非法输入。使用自定义业务异常,将输入合法性问题和底层运行异常区分,便于定位错误场景,提升程序健壮性。
五.总结
基于 PSP 流程完成论文查重程序开发,使用 Java 实现 2-gram 和余弦相似度算法,代码托管于 Github。作业过程完成需求分析、编码、单元测试、代码覆盖率检测以及性能分析。通过单元测试覆盖正常、边界与异常场景,验证程序正确性;借助 IDEA Profiler 分析程序内存与 CPU 开销,找到内存占用瓶颈并思考优化方案。本次实践让我熟悉了完整软件开发流程,认识到测试、性能分析在开发中的重要性,同时也发现了自己在项目工时预估、工程工具使用上的不足,积累了软件开发实战经验。
浙公网安备 33010602011771号