Simhash实现论文查重

项目

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Networkengineering1834
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11146
这个作业的目标 <实现论文查重算法,学会使用PSP表格估计,学会单元测试>

github地址

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 15
Estimate 估计这个任务需要多少时间 5 5
Development 开发 360 410
Analysis 需求分析 (包括学习新技术) 20 20
Design Spec 生成设计文档 0 0
Design Review 设计复审 0 0
Coding Standard 代码规范 (为目前的开发制定合适的规范) 10 10
Design 具体设计 20 20
Coding 具体编码 240 280
Code Review 代码复审 30 20
Test 测试(自我测试,修改代码,提交修改) 40 60
Reporting 报告 60 120
Test Repor 测试报告 20 20
Size Measurement 计算工作量 5 5
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 40 30
合计 510 605

模块接口的设计

项目类图


Simhash类中自定义了hash方法,实现simhash算法,并且得到了相应论文的数字指纹。ReadTxt类中实现了读取文件和写入文件功能。

关键算法流程

  • 代码原理

程序首先输入抄袭论文和原版论文的绝对路径,并且通过SimHash算法获取两个论文相应的Simhash对象hash1和hash2。

在Simhash的方法中,首先通过Java自带的StringTokenizer实现对论文的分词,并且定义一个与数字指纹等长的数组用于记录文档所有特征的向量和,然后重写了hash方法对于每段分词进行hash处理,并将其进行判断处理得出文档特征的向量和v[i],最后对数组进行判断,大于0的记为1,小于等于0的记为0,得到一个 92bit 的数字指纹/签名.

得到hash1和hash2之后,取出两者的数字指纹传入getDistance方法中,通过逐位比较两个指纹的二进制数据,统计其中相似之处并返回一个整数dis以表示两个数字指纹中总共相似的地方。最后通过dis/hashbit(hashbit是自定义的数字指纹位数,通过多次修改测试得出92位的数字指纹算出的结果比64位的数字指纹精确,且并不是越大越好)求出论文的相似率。

  • 项目代码流程

  • 数字指纹的获取

  • 论文查重的原理

模块接口部分的性能分析

  • JProfiler性能分析
  • 内容分析

模块代码测试及部分代码展示

public class SimHash {

	private String tokens;

	private BigInteger intSimHash;

	private String strSimHash;

	private int hashbits;

	public String getTokens() {
		return tokens;
	}

	public void setTokens(String tokens) {
		this.tokens = tokens;
	}

	public BigInteger getIntSimHash() {
		return intSimHash;
	}

	public void setIntSimHash(BigInteger intSimHash) {
		this.intSimHash = intSimHash;
	}

	public String getStrSimHash() {
		return strSimHash;
	}

	public void setStrSimHash(String strSimHash) {
		this.strSimHash = strSimHash;
	}

	public SimHash(String tokens) {
		this.tokens = tokens;
		this.intSimHash = this.simHash();
	}

	public SimHash(String tokens, int hashbits) {
		this.tokens = tokens;
		this.hashbits = hashbits;
		this.intSimHash = this.simHash();
	}

	public BigInteger simHash() {
		// 定义特征向量/数组
		int[] v = new int[this.hashbits];
		// 1、将文本去掉格式后, 分词.
		StringTokenizer stringTokens = new StringTokenizer(this.tokens, ",。!、:“”");
		while (stringTokens.hasMoreTokens()) {
			String temp = stringTokens.nextToken();
//	             System.out.println(temp);//查看拆分后的结果
			// 2、将每一个分词hash为一组固定长度的数列.比如 92bit 的一个整数.
			BigInteger t = this.hash(temp);
			for (int i = 0; i < this.hashbits; i++) {
				BigInteger bitmask = new BigInteger("1").shiftLeft(i);
				// 3、建立一个长度为92的整数数组(假设要生成92位的数字指纹,也可以是其它数字),
				// 对每一个分词hash后的数列进行判断,如果是1000...1,那么数组的第一位和末尾一位加1,
				// 中间的90位减一,也就是说,逢1加1,逢0减1.一直到把所有的分词hash数列全部判断完毕.
				if (t.and(bitmask).signum() != 0) {
					// 这里是计算整个文档的所有特征的向量和
					// 这里实际使用中需要 +- 权重,而不是简单的 +1/-1,
					v[i] += 1;
				} else {
					v[i] -= 1;
				}
			}
		}
		BigInteger fingerprint = new BigInteger("0");
		StringBuffer simHashBuffer = new StringBuffer();
		for (int i = 0; i < this.hashbits; i++) {
			// 4、最后对数组进行判断,大于0的记为1,小于等于0的记为0,得到一个 92bit 的数字指纹/签名.
			if (v[i] >= 0) {
				fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i));
				simHashBuffer.append("1");
			} else {
				simHashBuffer.append("0");
			}
		}
		this.strSimHash = simHashBuffer.toString();
		// 测试数字指纹
//	    System.out.println( this .strSimHash + " length "  +  this .strSimHash.length());
		return fingerprint;
	}

	private BigInteger hash(String source) {
		if (source == null || source.length() == 0) {
			return new BigInteger("0");
		} else {
			char[] sourceArray = source.toCharArray();
			BigInteger x = BigInteger.valueOf(((long) sourceArray[0]) << 7);
			BigInteger m = new BigInteger("1000003");
			BigInteger mask = new BigInteger("2").pow(this.hashbits).subtract(new BigInteger("1"));
			for (char item : sourceArray) {
				BigInteger temp = BigInteger.valueOf((long) item);
				x = x.multiply(m).xor(temp).and(mask);
			}
			x = x.xor(new BigInteger(String.valueOf(source.length())));
			if (x.equals(new BigInteger("-1"))) {
				x = new BigInteger("-2");
			}
			return x;
		}
	}

	public int hammingDistance(SimHash other) {

		BigInteger x = this.intSimHash.xor(other.intSimHash);
		int tot = 0;

		// 统计x中二进制位数为1的个数
		// 一个二进制数减去1,那么,从最后那个1(包括那个1)后面的数字全都反了,对吧,然后,n&(n-1)就相当于把后面的数字清0,
		// 我们看n能做多少次这样的操作就OK了。

		while (x.signum() != 0) {
			tot += 1;
			x = x.and(x.subtract(new BigInteger("1")));
		}
		return tot;
	}

	public int getDistance(String str1, String str2) {
		int distance;
		if (str1.length() != str2.length()) {
			distance = -1;
		} else {
			distance = 0;
			for (int i = 0; i < str1.length(); i++) {
				if (str1.charAt(i) == str2.charAt(i)) {
					distance++;
				}
			}
		}
		return distance;
	}

//	     public List subByDistance(SimHashDemo simHash,  int distance) {
//	         // 分成几组来检查
//	         int numEach =  this .hashbits / (distance + 1 );
//	         List characters = new  ArrayList();
//	 
//	         StringBuffer buffer = new  StringBuffer();
//	 
//	         int k =  0 ;
//	         for ( int  i =  0 ; i <  this .intSimHash.bitLength(); i++) {
//	             // 当且仅当设置了指定的位时,返回 true
//	             boolean sr = simHash.intSimHash.testBit(i);
//	 
//	             if (sr) {
//	                 buffer.append( "1" );
//	             } else  {
//	                 buffer.append( "0" );
//	             }
//	 
//	             if ((i +  1 ) % numEach == 0 ) {
//	                 // 将二进制转为BigInteger
//	                 BigInteger eachValue = new  BigInteger(buffer.toString(), 2 );
//	                 System.out.println( "----" + eachValue);
//	                 buffer.delete( 0 , buffer.length());
//	                 characters.add(eachValue);
//	             }
//	         }
//	 
//	         return characters;
//	     }

测试

请输入抄袭版论文的文件的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig_0.8_add.txt
请输入论文原文的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig.txt
论文相似率:0.88

请输入抄袭版论文的文件的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig_0.8_del.txt
请输入论文原文的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig.txt
论文相似率:0.97

请输入抄袭版论文的文件的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig_0.8_dis_1.txt
请输入论文原文的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig.txt
论文相似率:0.95

请输入抄袭版论文的文件的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig_0.8_dis_10.txt
请输入论文原文的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig.txt
论文相似率:0.86

请输入抄袭版论文的文件的绝对路径C:\\Users\\10973\\Desktop\\test\\orig_0.8_dis_15.txt
请输入论文原文的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig.txt
论文相似率:0.64

请输入抄袭版论文的文件的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig.txt
请输入论文原文的绝对路径:C:\\Users\\10973\\Desktop\\test\\orig.txt
论文相似率:1.00

代码指纹样板

模块异常处理说明

  • 路径出错(java.io.FileNotFoundException)
posted @ 2020-09-24 21:04  流逝时光Z  阅读(914)  评论(0编辑  收藏  举报