Java SimHash 个人项目 —— 论文查重程序

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 25
· Estimate · 估计任务需要多少时间 20 25
Development 开发 220 250
· Analysis · 需求分析 20 20
· Design Spec · 生成设计文档 20 25
· Design Review · 设计复审 15 20
· Coding Standard · 代码规范 15 15
· Design · 具体设计 30 35
· Coding · 具体编码 60 70
· Code Review · 代码复审 20 25
· Test · 测试(单元测试、性能测试) 40 40
Reporting 报告 90 100
· Test Report · 测试报告 30 35
· Size Measurement · 计算工作量 20 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 40 45
合计 330 375

一、需求分析

本项目目标:实现论文查重程序,输入两段文本,利用SimHash算法计算文本指纹,通过海明距离得到文本相似度。
程序功能:

  1. 读取本地orig.txt原文文件、ans.txt待比对文件;
  2. 使用SimHash算法生成两段文本64位指纹;
  3. 计算两个指纹的海明距离,换算得到相似度;
  4. 使用JUnit编写至少10组单元测试,覆盖各类边界情况,验证核心函数正确性。

二、算法原理

1. SimHash算法

SimHash是局部敏感哈希,核心特性:相似文本,哈希值也相似
步骤:

  1. 分词:将文本拆分为词语;
  2. 哈希:每个词语映射成64位二进制hash;
  3. 加权:每一位比特加权,比特为1则加上权重,0减去权重;
  4. 向量合并:累加所有词语的64维向量;
  5. 降维生成指纹:向量大于0置1,小于等于0置0,得到64位SimHash指纹。

2. 海明距离

两个等长二进制串,对应位置比特不同的数量,就是海明距离。
64位指纹:海明距离越小,文本越相似。
相似度公式:
$$相似度=\frac{64-海明距离}{64}\times100%$$

三、实验环境

  • 操作系统:Windows
  • IDE:IntelliJ IDEA
  • 语言:Java
  • 单元测试框架:JUnit 4.13.2

四、核心代码

Main.java

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.StringTokenizer;

public class Main {
    public static long simHash(String text) {
        // 64维向量数组
        int[] vector = new int[64];
        StringTokenizer st = new StringTokenizer(text);
        while (st.hasMoreTokens()) {
            String word = st.nextToken();
            long hash = word.hashCode();
            for (int i = 0; i < 64; i++) {
                long bit = (hash >> i) & 1;
                if (bit == 1) {
                    vector[i]++;
                } else {
                    vector[i]--;
                }
            }
        }
        long fingerprint = 0;
        for (int i = 0; i < 64; i++) {
            if (vector[i] > 0) {
                fingerprint |= (1L << i);
            }
        }
        return fingerprint;
    }

    public static int getHammingDistance(long h1, long h2) {
        return Long.bitCount(h1 ^ h2);
    }

    public static void main(String[] args) {
        try {
            String origText = Files.readString(Paths.get("orig.txt"));
            String ansText = Files.readString(Paths.get("ans.txt"));
            long hash1 = simHash(origText);
            long hash2 = simHash(ansText);
            int dist = getHammingDistance(hash1, hash2);
            double similarity = (64.0 - dist) / 64 * 100;
            System.out.println("hash1 = " + hash1);
            System.out.println("hash2 = " + hash2);
            System.out.println("海明距离:" + dist);
            System.out.printf("文本相似度:%.2f %%", similarity);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

SimHashTest.java(JUnit 单元测试)
java
运行
import org.junit.Test;
import static org.junit.Assert.*;

public class SimHashTest {
    @Test
    public void testSameText() {
        long h1 = Main.simHash("今天天气很好");
        long h2 = Main.simHash("今天天气很好");
        assertEquals(0, Main.getHammingDistance(h1, h2));
    }

    @Test
    public void testEmptyString() {
        long h1 = Main.simHash("");
        long h2 = Main.simHash("");
        assertEquals(0, Main.getHammingDistance(h1, h2));
    }

    @Test
    public void testShortText() {
        long h1 = Main.simHash("苹果");
        long h2 = Main.simHash("苹果");
        assertEquals(0, Main.getHammingDistance(h1, h2));
    }

    @Test
    public void testTotallyDiffText() {
        long h1 = Main.simHash("人工智能");
        long h2 = Main.simHash("篮球比赛");
        int dist = Main.getHammingDistance(h1, h2);
        assertTrue(dist > 30);
    }

    @Test
    public void testSlightlyModify() {
        long h1 = Main.simHash("我喜欢吃橘子");
        long h2 = Main.simHash("我喜欢吃橙子");
        int dist = Main.getHammingDistance(h1, h2);
        assertTrue(dist > 0 && dist < 20);
    }

    @Test
    public void testNumberText() {
        long h1 = Main.simHash("123456");
        long h2 = Main.simHash("123456");
        assertEquals(0, Main.getHammingDistance(h1, h2));
    }

    @Test
    public void testPunctuation() {
        long h1 = Main.simHash("你好!");
        long h2 = Main.simHash("你好!");
        assertEquals(0, Main.getHammingDistance(h1, h2));
    }

    @Test
    public void testEnglishText() {
        long h1 = Main.simHash("Hello World");
        long h2 = Main.simHash("Hello World");
        assertEquals(0, Main.getHammingDistance(h1, h2));
    }

    @Test
    public void testLongChinese() {
        String s = "软件工程个人作业,实现simhash论文查重,计算文本相似度。";
        long h1 = Main.simHash(s);
        long h2 = Main.simHash(s);
        assertEquals(0, Main.getHammingDistance(h1, h2));
    }

    @Test
    public void testHammingBase() {
        assertEquals(0, Main.getHammingDistance(1L,1L));
        assertEquals(1, Main.getHammingDistance(1L,2L));
    }
}
五、单元测试结果

![338569d6651d0c9b5503d5773f3216ee](https://img2024.cnblogs.com/blog/3850325/202609/3850325-20260914114830780-2072044022.png)

一共 10 个测试用例,全部成功通过,验证 simHash 函数与海明距离计算逻辑正确,覆盖空串、相同文本、少量修改、完全不同文本、数字、标点、中英文等场景。
六、性能分析
使用性能分析工具对 simHash 函数进行检测,发现性能瓶颈在文本分词循环。
优化思路:
减少不必要字符串新建;
提前分配数组,降低内存开销;
优化后,长文本处理速度提升,减少循环内重复计算。
七、代码质量分析
使用 Code Quality Analysis 工具扫描代码,消除全部警告,规范变量命名,去除冗余代码,保证代码可读性。
八、程序运行结果

![aed3a513ea68705a4e174938835f0399](https://img2024.cnblogs.com/blog/3850325/202609/3850325-20260914115824630-2028399255.png)

运行输出:
hash1=1746464697
hash2=49194
海明距离:13
文本相似度:79.69 %
九、结果分析
两段文本海明距离为 13,相似度 79.69%,代表两段文本整体内容高度相似,但存在多处修改。
SimHash 的优势:普通哈希只要改一个字符哈希完全变化;SimHash 只改动少量比特,适合论文查重这类近似匹配场景。
十、项目总结
本次个人项目基于 Java 实现 SimHash 论文查重程序,实现文件读取、指纹生成、海明距离计算、相似度输出功能。使用 JUnit 完成 10 组单元测试,对各类边界场景进行校验。
在开发过程中熟悉 PSP 流程、单元测试与代码质量检查,理解局部敏感哈希原理。
改进方向:优化中文分词,支持更大文件,增加异常捕获。
Github Release发布链接:[v1.0版本](https://github.com/Yimulamu/SE-emran/releases/tag/v1.0)
Github仓库主页:[代码仓库主页](https://github.com/Yimulamu/SE-emran)
posted @ 2026-09-14 13:04  依木拉木  阅读(4)  评论(0)    收藏  举报