第一次个人编程作业

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 消耗最大的函数。
内存分配图
image
CPU 耗时分析
image

优化方案:

  1. 将循环内字符串拼接改为StringBuilder,减少大量临时 String 对象创建,降低 GC 压力;
  2. 边遍历文本边统计二元片段词频,只遍历文本一次,省去中间存储所有 2-gram 片段的 List 集合,减少内存占用;
  3. 增加前置判断,预处理后文本长度不足 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. 当两段文本完全一致时,相似度为 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 开销,找到内存占用瓶颈并思考优化方案。本次实践让我熟悉了完整软件开发流程,认识到测试、性能分析在开发中的重要性,同时也发现了自己在项目工时预估、工程工具使用上的不足,积累了软件开发实战经验。

posted @ 2026-09-14 21:22  zzhihao  阅读(11)  评论(0)    收藏  举报