第一次个人编程作业
软件工程 | https://edu.cnblogs.com/campus/gdgy/CSGrade21-12?filter=all |
---|---|
作业要求 | https://edu.cnblogs.com/campus/gdgy/CSGrade21-12/homework/13014 |
作业目标 | 学习了解项目开发全流程 |
Java实现论文查重程序
代码
Git链接 :https://gitee.com/heart-knot/3121004688
需求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
PSP
PSP2.1 | Personal Software Process Stages | 预估耗费时间 (分钟) | 实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | 30 | 50 |
Estimate | 估计这个任务需要多少时间 | 360 | 450 |
Development | 开发 | 210 | 180 |
Analysis | 需求分析 (包括学习新技术) | 60 | 80 |
Design Spec | 生成设计文档 | 40 | 50 |
Design Review | 设计复审 | 20 | 20 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
Design | 具体设计 | 30 | 50 |
Coding | 具体编码 | 30 | 50 |
Code Review | 代码复审 | 20 | 20 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 80 |
Reporting | 报告 | 30 | 30 |
Test Report | 测试报告 | 20 | 30 |
Size Measurement | 计算工作量 | 5 | 5 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 5 | 5 |
Total | 合计 | 720 | 900 |
开发环境
编译环境:JDK8
开发工具:IDEA
代码分析工具:阿里巴巴开发规范插件
单元测试:JUnit4.12
性能分析工具:Jprofiler11.1.4
功能实现
ps:代码都根据阿里巴巴开发规范规范化代码
Constans类
作用:存储常量值
代码实现:
/**
* @author Xinjie
* @date 2023/9/12 9:23
*/
public class Constants {
/**
* 文本最小长度
*/
public static final int MIN_LENGTH = 200;
}
FileIOUtils类
设计:通过Java提供的文件IO类实现对指定路径下文件的读写
作用:文件读写工具类
代码实现:
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* @author Xinjie
* @date 2023/9/11 20:24
* <p>
* 文件读写工具类
*/
public class FileUtils {
/**
* 将文件内容转化为String字符串输出
*
* @param path 文件路径
* @return 文件内容
*/
public static String readFile(String path) {
StringBuilder str = new StringBuilder();
String strLine;
// 将文件按行读入 str中
File file = new File(path);
FileInputStream fileInputStream;
try {
fileInputStream = new FileInputStream(file);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
// 字符串拼接
while ((strLine = bufferedReader.readLine()) != null) {
str.append(strLine);
}
// 关闭IO流
inputStreamReader.close();
bufferedReader.close();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return str.toString();
}
/**
* 传入内容、文件全路径名
*
* @param elem 传入的内容
* @param path 写入的文件路径
*/
public static void writeFile(double elem, String path) {
String str = Double.toString(elem);
File file = new File(path);
FileWriter fileWriter;
try {
fileWriter = new FileWriter(file, true);
fileWriter.write(str, 0, (str.length() > 3 ? 4 : str.length()));
fileWriter.write("\r\n");
// 关闭IO流
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
SimHashUtils类
设计:基于MD5算法计算hash值,导入汉语言处理包hancs,利用hancs进行关键词分词
作用:计算simHash值工具类
代码实现:
import com.hankcs.hanlp.HanLP;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.List;
/**
* @author Xinjie
* @date 2023/9/11 17:19
* <p>
* 计算simHash值工具类
*/
public class SimHashUtils {
/**
* 使用MD5获取hash值
*
* @param s 字符串
* @return 二进制
*/
public static String calculateHash(String s) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
return new BigInteger(1, messageDigest.digest(s.getBytes(StandardCharsets.UTF_8))).toString(2);
} catch (Exception e) {
e.printStackTrace();
return s;
}
}
public static String calculateSimHash(String s) {
// 文本长度太短时HanLp无法取得关键字
try {
if (s.length() < Constants.MIN_LENGTH) {
throw new Exception("文本过短!");
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
//用数组表示特征向量
int[] v = new int[128];
//分词取出所有关键词
List<String> keywordList = HanLP.extractKeyword(s, s.length());
// hash
int size = keywordList.size();
//以i做外层循环
int i = 0;
for (String keyword : keywordList) {
//获取hash值
StringBuilder keywordHash = new StringBuilder(calculateHash(keyword));
if (keywordHash.length() < 128) {
// hash值可能少于128位,在低位以0补齐
int dif = 128 - keywordHash.length();
for (int j = 0; j < dif; j++) {
keywordHash.append("0");
}
}
//加权、合并
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++;
}
//simHash值
StringBuilder simHash = new StringBuilder();
//降维
for (int k : v) {
// 从高位遍历到低位
if (k <= 0) {
simHash.append("0");
} else {
simHash.append("1");
}
}
return simHash.toString();
}
}
HammingUtils类
设计:比较字符串的个数计算海明距离,并通过海明距离计算相似度
作用:计算海明距离工具类
代码实现:
/**
* @author Xinjie
* @date 2023/9/11 17:09
* <p>
* 计算海明距离工具类
*/
public class HammingUtils {
/**
* 计算海明距离
*
* @param simHash1 值1
* @param simHash2 值2
* @return 距离
*/
public static int calculateHammingDistance(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;
}
/**
* 计算相似度
*
* @param simHash1 值1
* @param simHash2 值2
* @return 相似度
*/
public static double calculateSimilarity(String simHash1, String simHash2) {
int distance = calculateHammingDistance(simHash1, simHash2);
return 0.01 * (100 - 1.0 * distance * 100 / 128);
}
}
PaperReviewMain类
设计:命令行运行并输入文件路径参数
作用:程序运行主类
代码实现:
/**
* @author Xinjie
* @date 2023/9/11 17:08
* <p>
* 程序运行主类
*/
public class PaperReviewMain {
public static void main(String[] args) {
// 读取文件
String str0 = FileIOUtils.readFile(args[0]);
String str1 = FileIOUtils.readFile(args[1]);
String resultFileName = args[2];
// 计算simHash值
String simHash0 = SimHashUtils.calculateSimHash(str0);
String simHash1 = SimHashUtils.calculateSimHash(str1);
// 求相似度
double similarity = HammingUtils.calculateSimilarity(simHash0, simHash1);
// 把相似度写入结果文件中
FileIOUtils.writeFile(similarity, resultFileName);
// 退出程序
System.exit(0);
}
}
性能分析
overview
memory
单元测试
FileUtils文件读写工具类测试
import org.junit.Test;
import static org.junit.Assert.*;
public class FileUtilsTest {
/**
* 正确输入路径测试文件读取
*/
@Test
public void readFile() {
String s = FileUtils.readFile("D:/code/Software/test/orig.txt");
String[] s1 = s.split(" ");
for (String s2 : s1) {
System.out.println(s2);
}
}
/**
* 正确输入路径测试文件写出
*/
@Test
public void writeFile() {
double[] elem = {0.11, 0.22, 0.33, 0.44, 0.55};
for (double v : elem) {
FileUtils.writeFile(v,"D:/code/Software/test/result.txt");
}
}
/**
* 错误输入路径测试文件读取
*/
@Test
public void readFileFail(){
String s = FileUtils.readFile("D:/code/Software/test/orig0.txt");
String[] s1 = s.split(" ");
for (String s2 : s1) {
System.out.println(s2);
}
}
/**
* 错误输入路径测试文件读取
*/
@Test
public void writeFileFail(){
double[] elem = {0.11, 0.22, 0.33, 0.44, 0.55};
for (double v : elem) {
FileUtils.writeFile(v,"A:/code/Software/test/result.txt");
}
}
}
执行结果:
覆盖率:
SimHashUtils计算simHash工具类测试
import org.junit.Test;
import static org.junit.Assert.*;
public class SimHashUtilsTest {
@Test
public void calculateHash() {
String[] strings = {"我", "是", "广东工业大学", "的", "学生"};
for (String string : strings) {
String stringHash = SimHashUtils.calculateHash(string);
System.out.println(stringHash.length());
System.out.println(stringHash);
}
}
@Test
public void calculateSimHash() {
String str0 = FileUtils.readFile("D:/code/Software/test/orig.txt");
String str1 = FileUtils.readFile("D:/code/Software/test/orig_0.8_add.txt");
System.out.println(SimHashUtils.calculateSimHash(str0));
System.out.println(SimHashUtils.calculateSimHash(str1));
}
}
执行结果:
覆盖率:
HammingUtils计算海明距离及相似度工具类测试
import org.junit.Test;
import java.util.Objects;
import static org.junit.Assert.*;
public class HammingUtilsTest {
@Test
public void calculateSimilarity() {
String str0 = FileUtils.readFile("D:/code/Software/test/orig.txt");
String str1 = FileUtils.readFile("D:/code/Software/test/orig_0.8_add.txt");
int distance = HammingUtils.calculateHammingDistance(Objects.requireNonNull(SimHashUtils.calculateSimHash(str0)), Objects.requireNonNull(SimHashUtils.calculateSimHash(str1)));
System.out.println("海明距离:" + distance);
System.out.println("相似度: " + (100 - distance * 100 / 128) + "%");
}
}
执行结果:
覆盖率: