这个作业属于哪个课程 | <计科22级34班> |
---|---|
这个作业要求在哪里 | [<作业要求>](个人项目 - 作业 - 计科22级34班 - 班级博客 - 博客园 (cnblogs.com)) |
这个作业的目标 | 通过实际编程任务,全面提升学生在编程、算法、项目管理、性能优化、代码测试和版本控制等方面的能力,为学生未来处理复杂开发项目打下扎实的基础。 |
项目 GitHub 链接:https://github.com/Tracywu1/3222004892
🧩一、PSP表格
PSP各个阶段 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 50 | 50 |
· Estimate | · 明确需求和其他因素,估计以下的各个任务需要多少时间 | 50 | 50 |
Development | 开发 | 390 | 435 |
· Analysis | · 需求分析 (包括学习新技术、新工具的时间) | 60 | 50 |
· Design Spec | · 生成设计文档(整体框架的设计,各模块的接口,用时序图,快速原型等方法) | 60 | 60 |
· Design Review | · 设计复审 | 10 | 5 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 10 |
· Design | · 具体设计 | 30 | 20 |
· Coding | · 具体编码 | 120 | 200 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 160 | 180 |
· Test Report | · 测试报告(发现了多少bug,修复了多少) | 30 | 50 |
· Size Measurement | · 计算工作量(多少行代码,多少次签入,多少测试用例,其他工作量) | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划(包括写文档、博客的时间) | 120 | 120 |
· 合计 | 570 | 665 |
🧩二、计算模块接口的设计与实现过程
1.类
- Main:程序的入口点,处理命令行参数并协调整个查重过程
- PlagiarismChecker:核心类,负责检查抄袭
- SimHashCalculator:计算文本的SimHash值
- FileUtil:处理文件读写
- HashUtil:计算字符串哈希值
- Constants:存储系统常量和配置
2.函数
- Main.main():程序入口,处理命令行参数并执行查重流程
- PlagiarismChecker.checkPlagiarism():检查两段文本的抄袭程度
- SimHashCalculator.getSimHash():计算文本的SimHash值
- FileUtil.readFile():读取文件内容
- FileUtil.writeResult():写入检测结果
- HashUtil.hash():计算字符串哈希值
3.类之间的关系
- Main 类是整个程序的入口,它使用 FileUtil 读取输入文件,调用 PlagiarismChecker 进行抄袭检测,然后再次使用 FileUtil 写入结果。
- PlagiarismChecker 依赖于 SimHashCalculator 来计算SimHash值。
- SimHashCalculator 又依赖于 HashUtil 来计算单词的哈希值,并使用 Constants 中定义的常量和停用词列表。
4.关键函数流程图
5.算法关键点和独到之处
(1)关键点:
- 命令行接口:
- 程序通过命令行参数接收输入和输出文件路径,提高了灵活性和可集成性。
- SimHash算法:
- 使用SimHash算法来快速估计文本相似度,特别适用于大规模文本比较。
- SimHash将长文本转换为固定长度的指纹(在这里是64位),通过比较指纹来判断相似度。
- 分词和停用词过滤:
- 使用结巴分词(JiebaSegmenter)进行中文分词,这对于处理中文文本很重要。
- 使用预定义的停用词列表(在Constants类中定义)进行过滤,提高了相似度计算的准确性。
- 词频加权:
- 在计算SimHash时考虑了词频,这使得重要词语对最终的指纹产生更大影响。
- 汉明距离:
- 使用汉明距离来衡量两个SimHash的差异,这是一种简单但有效的相似度度量方法。
- 可配置性:
- 通过Constants类集中管理配置参数(如哈希位数和停用词列表),便于未来调整和优化。
- 错误处理:
- Main类中包含了基本的错误处理机制,可以捕获并报告在文件读写或相似度计算过程中可能发生的异常。
(2)独到之处:
- 使用Java 8 Stream API进行词频统计和SimHash计算,代码简洁高效。
- 采用BigInteger来处理哈希值,提供了更大的数值范围和更精确的计算。
- 将相似度结果规范化到0-1范围,便于理解和比较。
- 系统设计考虑了中文文本的特点,包括使用专门的中文分词工具和中文停用词列表。
🧩三、计算模块接口部分的性能改进
由 IDEA 自带的 profiler 功能生成了以上图表,它展示了程序运行时的方法调用树和每个方法所的执行时间。
对图表的分析:
- 性能瓶颈:
- SimHashCalculator.getSimHash 和 JiebaSegmenter.sentenceProcess 是查重过程中的主要耗时点。
改进思路:
- 使用延迟初始化和双重检查锁定模式来初始化 JiebaSegmenter,这样可以避免在不需要时进行昂贵的初始化操作。
- 在 SimHashCalculator 中使用传统的 for 循环替代流操作,并使用 long[] 数组代替 int[],可能会略微提高性能。
SimHashCalculator 类原代码:
public class SimHashCalculator {private static final JiebaSegmenter SEGMENTER = new JiebaSegmenter();public static String getSimHash(String text) throws NoSuchAlgorithmException {if (text == null || text.trim().isEmpty()) {return String.join("", Collections.nCopies(Constants.HASH_BITS, "0"));}// 使用jieba分词对文本进行分词处理List<String> words = SEGMENTER.sentenceProcess(text);// 过滤掉停止词,并统计剩余词汇的词频Map<String, Long> wordFrequency = words.stream().filter(word -> !Constants.STOP_WORDS.contains(word)).collect(Collectors.groupingBy(w -> w, Collectors.counting()));// 如果过滤后没有剩余的词,返回全0的SimHashif (wordFrequency.isEmpty()) {return String.join("", Collections.nCopies(Constants.HASH_BITS, "0"));}// 初始化用于存储每个哈希位的权重和的数组int[] v = new int[Constants.HASH_BITS];// 遍历词频映射,根据每个词的哈希值更新数组vfor (Map.Entry<String, Long> entry : wordFrequency.entrySet()) {BigInteger hash = HashUtil.hash(entry.getKey());long weight = entry.getValue();// 遍历哈希值的每一位for (int i = 0; i < Constants.HASH_BITS; i++) {if (hash.testBit(Constants.HASH_BITS - 1 - i)) {v[i] += weight;} else {v[i] -= weight;}}}// 将数组v中的每个元素转换为二进制字符串,并拼接成最终的SimHash值StringBuilder simHashBuilder = new StringBuilder(Constants.HASH_BITS);for (int i = 0; i < Constants.HASH_BITS; i++) {if (v[i] >= 0) {simHashBuilder.append("1");} else {simHashBuilder.append("0");}}return simHashBuilder.toString();}
}
改进后代码:
public class SimHashCalculator {private static volatile JiebaSegmenter SEGMENTER;private static JiebaSegmenter getSegmenter() {if (SEGMENTER == null) {synchronized (SimHashCalculator.class) {if (SEGMENTER == null) {SEGMENTER = new JiebaSegmenter();}}}return SEGMENTER;}/*** 计算给定文本的 SimHash 值。* @param text 输入的文本字符串* @return 文本的 SimHash 值,以二进制字符串形式表示*/public static String getSimHash(String text){// 同之前// 使用数组来存储词频,避免使用流操作,并使用 long[] 数组代替 int[]long[] v = new long[Constants.HASH_BITS];for (String word : words) {if (!Constants.STOP_WORDS.contains(word)) {BigInteger hash = HashUtil.hash(word);for (int i = 0; i < Constants.HASH_BITS; i++) {if (hash.testBit(Constants.HASH_BITS - 1 - i)) {v[i]++;} else {v[i]--;}}}}// 使用StringBuilder构建最终的SimHashStringBuilder simHashBuilder = new StringBuilder(Constants.HASH_BITS);for (long bit : v) {simHashBuilder.append(bit >= 0 ? "1" : "0");}return simHashBuilder.toString();}
}
使用延迟初始化和双重检查锁定模式来初始化 JiebaSegmenter:
private static volatile JiebaSegmenter SEGMENTER;private static final int SHORT_TEXT_THRESHOLD = 50; // 定义短文本的阈值// 使用延迟初始化和双重检查锁定模式来初始化JiebaSegmenter,这样可以避免在不需要时进行昂贵的初始化操作。private static JiebaSegmenter getSegmenter() {if (SEGMENTER == null) {synchronized (PlagiarismChecker.class) {if (SEGMENTER == null) {SEGMENTER = new JiebaSegmenter();}}}return SEGMENTER;}
改进后的程序运行时的方法调用树:
🧩四、计算模块部分单元测试展示
展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。(12')
PlagiarismCheckerTest:
-
testCheckPlagiarismWithSimilarTexts: 测试相似但不完全相同的文本,验证相似度在合理范围内。
public void testCheckPlagiarismWithSimilarTexts() throws PlagiarismException.EmptyTextException {// 定义原始文本和涉嫌抄袭的文本String originalText = "今天是星期天,天气晴,今天晚上我要去看电影。";String plagiarizedText = "今天是周天,天气晴朗,我晚上要去看电影。";// 调用PlagiarismChecker类的checkPlagiarism方法检查两段文本的相似度double similarity = PlagiarismChecker.checkPlagiarism(originalText, plagiarizedText);System.out.println(similarity);// 验证返回的相似度是否在0到1之间assertTrue(similarity >= 0 && similarity <= 1);// 预期这两段文本的相似度应该很高,因此相似度应大于0.5assertTrue(similarity >= 0.5); }
-
testCheckPlagiarismWithIdenticalTexts: 测试完全相同的文本,验证相似度为1。
-
testCheckPlagiarismWithDifferentTexts: 测试完全不同的文本,验证相似度接近0。
-
testCheckPlagiarismWithLongTexts: 测试长文本,验证系统能处理大量数据。
-
testTextWithManyStopWords: 测试包含大量停用词的文本,验证相似度在合理范围内。
SimHashCalculatorTest:
-
testGetSimHashWithNormalText: 测试普通文本的SimHash计算。
public void testGetSimHashWithNormalText() {// 测试普通文本的SimHash计算String text = "This is a test text for SimHash calculation.";String simHash = SimHashCalculator.getSimHash(text);assertNotNull("SimHash不应为空", simHash);assertEquals("SimHash长度应为64位", 64, simHash.length());assertTrue("SimHash应只包含0和1", simHash.matches("[01]+")); }
-
testGetSimHashWithChineseText: 测试中文文本的SimHash计算,验证多语言支持。
-
testGetSimHashConsistency: 测试SimHash计算的一致性,确保相同输入产生相同结果。
-
testGetSimHashWithSpecialCharacters: 测试包含特殊字符的文本,验证系统的鲁棒性。
FileUtilTest:
-
testReadFileWithNormalContent: 测试读取普通内容的文件。
public void testReadFileWithNormalContent() throws Exception {// 测试读取普通内容的文件File file = tempFolder.newFile("normal.txt");String content = "This is a test file content.";try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {writer.write(content);}String readContent = FileUtil.readFile(file.getPath());assertEquals("读取的内容应与写入的内容相同", content, readContent); }
-
testReadFileWithEmptyContent: 测试读取空文件,验证边界情况处理。
-
testReadNonexistentFile: 测试读取不存在的文件,验证异常处理。
-
testWriteResultWithNormalValue: 测试写入正常的相似度值。
-
testWriteResultWithZeroValue: 测试写入零相似度值,验证边界情况处理。
HashUtilTest:
-
testHashWithNormalString: 测试普通字符串的哈希计算。
public void testHashWithNormalString() {// 测试普通字符串的哈希计算String input = "test string";BigInteger hash = HashUtil.hash(input);assertNotNull("哈希值不应为空", hash);assertTrue("哈希值应大于0", hash.compareTo(BigInteger.ZERO) > 0); }
-
testHashConsistency: 测试哈希计算的一致性,确保相同输入产生相同哈希值。
-
testHashWithEmptyString: 测试空字符串的哈希计算,验证边界情况处理。
-
testHashWithLongString: 测试长字符串的哈希计算,验证系统对大量数据的处理能力。
🧩五、计算模块部分异常处理说明
-
空输入异常 (NullInputException)
设计目标: 防止空输入导致的潜在空指针异常,提高系统的稳定性。
// NullInputException.java
public class NullInputException extends RuntimeException {public NullInputException(String message) {super(message);}
}// PlagiarismCheckerTest.java
import org.junit.Test;
import static org.junit.Assert.*;public class PlagiarismCheckerTest {@Test(expected = NullInputException.class)public void testNullInput() {PlagiarismChecker.checkPlagiarism(null, "Some text");}
}
错误场景: 当用户提供空文本作为输入时,例如忘记上传文件或文件内容为空。