这个作业属于哪个课程 | |
这个作业要求在哪里 | |
这个作业的目标 | 1.PSP表格 2.编码 3.单元测试 4.性能测试 |
其他参考文献 | CSDN |
一、作业地址
二、整体设计
2.1 需求分析
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
2.2 开发环境
开发语言:Java 17
开发工具:Intellij IDEA 2021
2.3项目依赖
项目构建工具:maven
单元测试:JUnit-4.12
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
性能分析工具:JProfiler 11
依赖的外部 jar 包:汉语言处理包
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.5.4</version>
</dependency>
2.4 实现思路
![](https://i-blog.csdnimg.cn/blog_migrate/be2db1cc01c79109ec40236cced60940.png)
三、接口实现
3.1 读写txt文件模块
类:TxtIOUtils
方法:
readTxt:读取txt文件内容,返回String;
writeTxt:将数字写入到txt文件中。
3.2 计算SimHash模块
类:SimHashUtils
方法:
getHash:传入String,利用MD5计算出它的hash值,并以字符串形式输出;
getSimHash:传入String,计算出它的simHash值,并以字符串形式输出。
主要流程:
分词:使用了外部依赖汉语言处理hankcs包提供的接口,把需要判断的文本分词形成这个文章的特征单词;
List<String> keywordList = HanLP.extractKeyword(str, str.length());//取出所有关键词
获取hash值:通过hash算法把每个词变成hash值;
String keywordHash = getHash(keyword);
if (keywordHash.length() < 128) {
// hash值可能少于128位,在低位以0补齐
int dif = 128 - keywordHash.length();
for (int j = 0; j < dif; j++) {
keywordHash += "0";
}
}
加权、合并:通过 2步骤的hash生成结果,按照单词的权重形成加权数字串,把上面各个单词算出来的序列值累加,变成一个序列串;
for (int j = 0; j < v.length; j++) {
// 对keywordHash的每一位与'1'进行比较
if (keywordHash.charAt(j) == '1') {
//权重分10级,由词频从高到低,取权重10~0
v[j] += (10 - (i / (size/10)));
} else {
v[j] -= (10 - (i / (size/10)));
}
}
i++;
}
降维:把4步算出来的序列串变成 0 1 串,形成最终的simhash。
String simHash = "";// 储存返回的simHash值
for (int j = 0; j < v.length; j++) {
// 从高位遍历到低位
if (v[j] <= 0) {
simHash += "0";
} else {
simHash += "1";
}
}
3.3 计算海明距离模块
类:HammingUtils
方法:
getHammingDistance:输入两个simHash值,计算它们的海明距离;
for (int i = 0; i < simHash1.length(); i++) {
// 每一位进行比较
if (simHash1.charAt(i) != simHash2.charAt(i)) {
distance++;
}
}
getSimilarity:输入两个simHash值,输出它们的相似度;
int distance = getHammingDistance(simHash1, simHash2);
// 通过海明距离计算出相似度,并返回
return 0.01 * (100 - distance * 100 / 128);
通过比较差异的位数就可以得到两串文本的差异,差异的位数,称之为“海明距离”,通常认为海明距离<3的是高度相似的文本。
3.4 Main主模块
类:MainPaperChecker
主要流程:
调用TxtIOUtils,从命令行输入的绝对路径名读取对应的文件,将文件的内容转化为对应的字符串;
调用SimHashUtils,由字符串得出对应的 simHash值;
调用HammingUtils,由 simHash值求出相似度;
调用TxtIOUtils,把相似度写入最后的结果文件中。
四、性能分析
4.1 overview
![](https://i-blog.csdnimg.cn/blog_migrate/77744861978512ee5440173a38fa1ca1.png)
4.2 方法调用情况
![](https://i-blog.csdnimg.cn/blog_migrate/8ac5efdc4a248a121e0c1cfbcf565f41.png)
4.3 性能改进
由上图所展示的方法调用情况可知,程序主要消耗函数是外部依赖汉语言处理hankcs提供的接口,即分词模块,所以整体没有改进必要。
五、单元测试
5.1 读写txt文件模块测试
public class TxtIOUtilsTest {
@Test
public void readTxtTest() {
// 路径存在,正常读取
String str = TxtIOUtils.readTxt("D:/file/tests/org.txt");
String[] strings = str.split(" ");
for (String string : strings) {
System.out.println(string);
}
}
@Test
public void readTxtFailTest() {
// 路径不存在,读取失败
String str = TxtIOUtils.readTxt("D:/file/tests/o.txt");
}
@Test
public void writeTxtTest() {
// 路径存在,正常写入
double[] elem = {0.81, 0.45, 0.90, 0.22};
for (int i = 0; i < elem.length; i++) {
TxtIOUtils.writeTxt(elem[i], "D:/file/tests/ans.txt");
}
}
@Test
public void writeTxtFailTest() {
// 路径错误,写入失败
double[] elem = {0.81, 0.45, 0.90, 0.22};
for (int i = 0; i < elem.length; i++) {
TxtIOUtils.writeTxt(elem[i], "D:/file/t/a.txt");
}
}
}
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/90f01631d70a4b42f5820c5dc3ebe6e3.png)
代码覆盖率:
![](https://i-blog.csdnimg.cn/blog_migrate/ef1aeb96fa05ac4003ff1219b945a19a.png)
5.2 计算SimHash模块测试
public class SimHashUtilsTest {
@Test
public void getHashTest(){
String[] strings = {"美国", "51区", "雇员", "称", "内部", "有","9架","飞碟"};
for (String string : strings) {
String stringHash = SimHashUtils.getHash(string);
System.out.println(stringHash.length());
System.out.println(stringHash);
}
}
@Test
public void getSimHashTest(){
String str0 = TxtIOUtils.readTxt("D:/file/tests/org.txt");
String str1 = TxtIOUtils.readTxt("D:/file/tests/org_add.txt");
System.out.println(SimHashUtils.getSimHash(str0));
System.out.println(SimHashUtils.getSimHash(str1));
}
}
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/e9b3d110f58c4d293c1da275cc9d2153.png)
代码覆盖率:
![](https://i-blog.csdnimg.cn/blog_migrate/3664b0d4163186aea69aef8e4ebfabf0.png)
5.3 计算海明距离模块测试
public class getHammingDistanceTest {
@Test
public void getHammingDistanceTest() {
String str0 = TxtIOUtils.readTxt("D:/file/tests/org.txt");
String str1 = TxtIOUtils.readTxt("D:/file/tests/org_add.txt");
int distance = HammingUtils.getHammingDistance(SimHashUtils.getSimHash(str0), SimHashUtils.getSimHash(str1));
System.out.println("海明距离:" + distance);
}
@Test
public void getSimilarityTest() {
String str0 = TxtIOUtils.readTxt("D:/file/tests/org.txt");
String str1 = TxtIOUtils.readTxt("D:/file/tests/org_add.txt");
double similarity = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str0), SimHashUtils.getSimHash(str1));
System.out.println("str0和str1的相似度:" + similarity);
}
}
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/dd60a8809e570b9ed8f711f3ccfede86.png)
代码覆盖率:
![](https://i-blog.csdnimg.cn/blog_migrate/1e05614302ab93b9a8eef1e1896134c1.png)
5.4 Main主模块测试
public class MainTest {
@Test
public void origAndAllTest(){
String[] str = new String[6];
str[0] = TxtIOUtils.readTxt("D:/file/tests/org.txt");
str[1] = TxtIOUtils.readTxt("D:/file/tests/org_add.txt");
str[2] = TxtIOUtils.readTxt("D:/file/tests/org_add2.txt");
str[3] = TxtIOUtils.readTxt("D:/file/tests/org_del.txt");
str[4] = TxtIOUtils.readTxt("D:/file/tests/org_del2.txt");
str[5] = TxtIOUtils.readTxt("D:/file/tests/org_del3.txt");
String ansFileName = "D:/file/tests/ans.txt";
for(int i = 0; i <= 5; i++){
double ans = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str[0]), SimHashUtils.getSimHash(str[i]));
TxtIOUtils.writeTxt(ans, ansFileName);
}
}
}
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/9dc9fef42557d4029ad5daf42c69847e.png)
结果文件:
![](https://i-blog.csdnimg.cn/blog_migrate/078ab3d438ce8513471983cd474e634f.png)
六、异常处理
6.1 异常处理模块
类:ShortStringException
继承Exception类。
6.2 异常处理模块测试
public class ShortStringExceptionTest {
@Test
public void shortStringExceptionTest(){
//测试str.length()<200的情况
System.out.println(SimHashUtils.getSimHash("从前有座山"));
}}
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/167eb3271a46c1fefedc7bdfcacd8207.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0411796e615c9e040fc3969f7b562011.png)
代码覆盖率:
![](https://i-blog.csdnimg.cn/blog_migrate/8c63bdbfe6305b882e7975508a1cdaa4.png)
七、PSP 表格
PSP2.1**** | Personal Software Process Stages**** | 预估耗时(分钟)**** | 实际耗时(分钟)**** |
Planning | 计划 | 40 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 60 |
Development | 开发 | 300 | 250 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 30 |
· Design Spec | · 生成设计文档 | 40 | 30 |
· Design Review | · 设计复审 | 30 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 90 | 60 |
· Coding | · 具体编码 | 300 | 330 |
· Code Review | · 代码复审 | 70 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 150 |
Reporting | 报告 | 150 | 120 |
· Test Repor | · 测试报告 | 40 | 30 |
· Size Measurement | · 计算工作量 | 40 | 40 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 30 |
· 合计 | 1430 | 1160 |