第2次软工作业
一、作业概述
| 这个作业属于哪个课程 | 计科4班软件工程 |
|---|---|
| 这个作业要求在哪里 | 个人项目 |
| 这个作业的目标 | 设计一个论文查重算法并对各模块进行改进与测试 |
二、github链接
三、PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 10 | 15 |
| · Estimate | · 估计这个任务需要多少时间 | 10 | 15 |
| Development | 开发 | 830 | 785 |
| · Analysis | · 需求分析 (包括学习新技术) | 240 | 270 |
| · Design Spec | · 生成设计文档 | 30 | 30 |
| · Design Review | · 设计复审 | 30 | 45 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 25 |
| · Design | · 具体设计 | 150 | 180 |
| · Coding | · 具体编码 | 240 | 300 |
| · Code Review | · 代码复审 | 60 | 75 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
| Reporting | 报告 | 90 | 105 |
| · Test Report | · 测试报告 | 30 | 45 |
| · Size Measurement | · 计算工作量 | 30 | 30 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
| 合计 | 945 | 1060 |
四、模块设计
4.1 算法流程图
- 4.1.1 总体流程图

- 4.1.2 文本SimHash值计算

4.2 项目结构

4.3 关键代码
- main函数
public static void main(String[] args) {
// 从命令行输入的路径名读取对应的文件,将文件的内容转化为对应的字符串
String str0 = TxtIOUtils.readTxt(args[0]);
String str1 = TxtIOUtils.readTxt(args[1]);
String resultFileName = args[2];
// 由字符串得出对应的 simHash值
String simHash0 = SimHashUtils.getSimHash(str0);
String simHash1 = SimHashUtils.getSimHash(str1);
// 由 simHash值求出相似度
double similarity = HammingUtils.getSimilarity(simHash0, simHash1);
// 把相似度写入最后的结果文件中
TxtIOUtils.writeTxt(similarity, resultFileName);
// 退出程序
System.exit(0);
}
- 文本SimHash值计算
int[] v = new int[128];
List<String> keywordList = HanLP.extractKeyword(str, str.length());
//分词步骤使用了外部依赖hankcs包提供的接口
//List指的是集合,<>是泛型
//List<String>类型的变量keywordList用于存储提取到的关键词
//HanLp库中的extractKeyword方法用于提取关键词,该方法接收两个参数,字符串str表示要提取关键词的文本,整数str.length()表示文本的长度
int size = keywordList.size();
//size()返回keywordList集合元素的数量
int i = 0;
for(String keyword : keywordList)
//for-each循环是一种简化版的循环语句,它的主要作用是遍历集合中的每一个元素
{
String keywordHash = getHash(keyword);
if (keywordHash.length() < 128) {
int dif = 128 - keywordHash.length();
for (int j = 0; j < dif; j++) {
keywordHash += "0";
//操作符“+=”把指定的字符添加到字符串末尾,这里把“0”添加到了字符串keywordHash末尾,使得该字符串的长度始终为128
}
}
for (int j = 0; j <128; j++) {
if (keywordHash.charAt(j) == '1')
//charAt()方法返回索引处的char值
//整型数组默认初始值为0,当hash值对应位上的值为1时加权,否则减权
{
v[j] += (10 - (i / (size / 10)));
//较早出现的关键词可能更能代表文本的主题,于是通过i的自增,赋予较早出现的关键词更大的权重
}
else {
v[j] -= (10 - (i / (size / 10)));
}
}
i++;
}
String simHash = "";
for (int j = 0; j <128; j++)
{
//此步骤进行降维操作,方便后续不同文本之间进行比较
if (v[j] <= 0) {
simHash += "0";
} else {
simHash += "1";
}
}
return simHash;
//最终返回能够代表该文本的simHash值
}
}
- 海明距离计算
public static int getHammingDistance(String simHash1, String simHash2) {
int distance = 0;
if (simHash1.length() != simHash2.length()) {
// 出错,返回-1
distance = -1;
} else {
for (int i = 0; i < simHash1.length(); i++) {
// 每一位进行比较
if (simHash1.charAt(i) != simHash2.charAt(i)) {
distance++;
}
}
}
return distance;
}
public static double getSimilarity(String simHash1, String simHash2) {
// 通过 simHash1 和 simHash2 获得它们的海明距离
int distance = getHammingDistance(simHash1, simHash2);
// 通过海明距离计算出相似度,并返回
return 0.01 * (100 - distance * 100 / 128);
}
- 异常处理,当文本长度过短时,可能会导致文本信息不足,且 HanLP 使用的算法和模型对文本长度有一定的要求,故当文本过短时需要抛出异常
try{
if(str.length() < 200) throw new ShortStringException("文本过短!");
//当文本长度太短时,HanLp可能无法取得关键字
}catch (ShortStringException e){
e.printStackTrace();
return null;
}
五、性能分析(JProfiler 14.0)
5.1 概览

5.2 实时内存

由上图可以看出,占用内存最多的是java.lang.Float,这可能与代码相似度部分的运算有关;其次是int[],这部分内存主要用于文本hash值的加权、合并等步骤。
六、测试情况
6.1 海明距离计算模块测试

6.2 SimHash 模块测试

6.3 main函数模块测试

- 测试结果
![]()
- 代码覆盖率为100%,测试用例均通过
项目改进
- 内存占用方面,可通过寻找更优的数据结构或算法减少对float类型的调用,从而减小内存调用及提高效率。
- 算法方面,我认为传统的海明距离算法有一定的局限性,比如前文提到的异常处理中关于文本长度过短的问题,当文本长度过短时海明距离算法对论文相似度的计算结果可能不太理想,针对这个问题,可以从根据文本长度动态调整计算方法方面来进行改进;从hash算法模块,我认为降维这一步在一定程度上压缩了文本的特征,使得最后计算出来的相似度还不够精准,针对这个问题,可以尝试将海明距离与其他相似度计算方法(如编辑距离、Jaccard 相似系数等)结合,以综合评估字符串之间的相似性。

浙公网安备 33010602011771号