Github仓库链接
作业要求
| 这个作业属于哪个课程 | 计算21级 |
|---|---|
| 这个作业要求在哪里 | 个人项目 |
| 这个作业的目标 | 项目管理实践及PSP表格熟悉 |
需求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
- 原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
- 抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
- 从命令行参数给出:论文原文的文件的绝对路径。
- 从命令行参数给出:抄袭版论文的文件的绝对路径。
- 从命令行参数给出:输出的答案文件的绝对路径。
实现方式
实现环境
- 操作系统:Windows11
- IDE:IntelliJ IDEA 2020.1 x64
- 使用语言:JAVA
- JAVA版本:jdk 8.0
- 项目构建工具:maven
- 性能分析工具:JProfiler 9.2
- 使用依赖包:junit测试类,hanlp
整体项目框架结构

算法设计思路
- 分词:对需要比较的文本进行分词,提取特征向量。并对特征向量,进行权重设置。
- hash: 通过hash函数计算各个特征向量的hash值。hash值为二进制数0 1 组成的字符串。
- 加权:在hash值的基础上,给所有特征向量进行加权,即W = Hash * weight,且遇到1则hash值和权值正相乘,遇到0则hash值和权值负相乘。
- 合并:将上述各个特征向量的加权结果累加,变成只有一个序列串。拿前两个特征向量举例。
- 降维:对于的累加结果,如果大于0则置1,否则置0,从而得到该语句的simhash值,最后我们便可以根据不同语句simhash的海 明距离来判断它们的相似度。
- 计算:计算海明距离,每位进行比较,如果不相同海明距离+1。
接口设计与实现过程
| 类名 | 实现方法 |
|---|---|
| mainText(主程序) | main方法(以百分制形式输出两个文献的重合度) |
| IOUtil(文件输入工具类) | Read(读取本地文件内容) |
| SimHash(simhash工具类) | getDistance(计算海明距离,求出两个重合度),getHash(计算分词的Hash值),getSimHash(求出文章的simHash值) |
IO工具类
- 运用JAVA-IO流技术读取出本地文件
- 这里运用的是缓冲流BufferedInputStream来读取,然后用read(bytes[])方法一次性读取出文件,以至于可以读取到汉字内容
- 参考资料:JAVA-IO流
package com.azusa.util;
import java.io.*;
public class IOUtil {
public IOUtil() {
}
//使用缓冲输入流来进行IO操作
public String Read(String fileAddr,String text){
try {
File file =new File(fileAddr);
BufferedInputStream inputStream =new BufferedInputStream(new FileInputStream(file));
long length = file.length();
byte[] bytes = new byte[(int) length];
int b;
String sim=null;
while((b=inputStream.read(bytes))!=-1){
sim = new String(bytes);
}
if(sim!=null){
System.out.println(text+"读取成功");
}else {
System.out.println(text+"读取失败");
}
return sim;
} catch (FileNotFoundException e) {
System.out.println("读取失败文件名错误");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
SimHash工具类(直接计算出海明距离和文献重合度
- 分词
- 导入了hanlp依赖包,使用对应的方法直接分词(后面有分词的测试
- 获取hash值,使用到MD5加密方式获取到128位由0,1组成的字符串
- 加权合并降维
- 计算海明距离
- 参考资料:SimHash 原理与实现
package com.azusa.SimHash;
import com.hankcs.hanlp.HanLP;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;
public class SimHash {
//通过MD5获取对应的hash值
public static String getHash(String str){
try{
// 这里使用了MD5获得hash值
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
return new BigInteger(1, messageDigest.digest(str.getBytes("UTF-8"))).toString(2);
}catch(Exception e){
e.printStackTrace();
return str;
}
}
public static int[] getSimHash(String text){
//分词
List<String> simLists = HanLP.extractKeyword(text, text.length());
//获取hash值,合并权值
int[] sim= new int[128];
int size=simLists.size();
for (int i=0;i<size;i++){
String simList = simLists.get(i);
String hash = getHash(simList);
int length = hash.length();
/*如果不够128位数,泽填补0
* 一开始没有写这部分,一直报错QAQ*/
if(hash.length()<128){
int len=128-length;
for(int j=0;j<len;j++){
hash+='0';
}
}
for(int j=0;j<128;j++){
if(hash.charAt(j)=='1'){//如果对应为1
sim[j]+=10-(i/8);/*每8个一个权重最大为10最小为0,前8个分词权重为10*/
}else {
sim[j]-=10-(i/8);
}
}
}
//降维
for (int i=0;i<128;i++){
if(sim[i]<0){
sim[i]=0;
}else{
sim[i]=1;
}
}
return sim;
}
/*计算海明距离*/
public double getDistance(String text01, String text02) {
int[] simHash01 = getSimHash(text01);
int[] simHash02 = getSimHash(text02);
int distance = 0;
for (int i = 0; i < 128; i++) {
/*不相同,海明距离+1*/
if(simHash01[i]!=simHash02[i]){
distance++;
}
}
return 0.01 * (100 - distance * 100 / 128);/*返回换算成百分制*/
}
}
测试方法
- 分词测试
@Test
public void divide(){
String text="你说得对 但是\n" +
"《明日方舟》是由鹰角网络自主开发运营的一款策略向即时战略塔防游戏,于2019年5月1日公测。 [1-3] 该游戏适龄级别为12+。\n" +
"在游戏中,玩家将作为罗德岛的领导者“博士”,带领罗德岛的一众干员救助受难人群、处理矿石争端以及对抗诸如整合运动等其他势力。在错综复杂的势力博弈之中,寻找治愈矿石病的方法。";
List<String> simList = HanLP.extractKeyword(text, text.length());//取出所有关键词
for (int i=0;i<simList.size();i++){
String s = simList.get(i);
System.out.print(s+"|");
}
}
-
运行结果
-

-
Hash值测试
@Test
public void getHash(){
String str="乐子人";
try{
// 这里使用了MD5获得hash值
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
String string = new BigInteger(1, messageDigest.digest(str.getBytes("UTF-8"))).toString(2);
System.out.println(string);
}catch(Exception e){
e.printStackTrace();
}
}
-
运行结果
-

-
主函数测试
-

-
代码覆盖率
-

性能测试

异常处理
- 有一些分词在进行MD5转换的时候并不是128位二进制码,这时将在最后几位补全至128位
/*如果不够128位数,泽填补0
* 一开始没有写这部分,一直报错QAQ*/
if(hash.length()<128){
int len=128-length;
for(int j=0;j<len;j++){
hash+='0';
}
}
- 正常的IO流和File读取文件需要抛出或者抓取IO异常或文件读写异常
PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 90 | 80 |
| · Estimate | · 估计这个任务需要多少时间 | 400 | 360 |
| Development | 开发 | 120 | 120 |
| · Analysis | · 需求分析 (包括学习新技术) | 120 | 150 |
| · Design Spec | · 生成设计文档 | 60 | 90 |
| · Design Review | · 设计复审 | 5 | 0 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 1 | 1 |
| · Design | · 具体设计 | 30 | 20 |
| · Coding | · 具体编码 | 50 | 45 |
| · Code Review | · 代码复审 | 20 | 30 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 20 | 40 |
| Reporting | 报告 | 100 | 70 |
| · Test Repor | · 测试报告 | 40 | 60 |
| · Size Measurement | · 计算工作量 | 10 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 5 | 5 |
| · 合计 | 1071 | 1081 |