个人项目

软件工程作业

项目 内容
这个作业属于哪个课程 软件工程
这个作业要求在哪里 作业要求

github


如何使用? :terminal输入java -jar out/artifacts/SoftwareHomework_jar/SoftwareHomework.jar
(使用相对路径运行)(内置了输入用例)

psp2.1

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate 估计这个任务需要多少时间 80 90
Development 开发
· Analysis 需求分析 (包括学习新技术) 250 270
· Design Spec 生成设计文档 130 120
· Design Review 设计复审 40 55
· Coding Standard 代码规范 (为目前的开发制定合适的规范) 50 60
· Design 具体设计 130 140
· Coding 具体编码 150 160
· Code Review 代码复审 50 65
· Test 测试(自我测试,修改代码,提交修改) 190 220
Reporting 报告
· Test Report 测试报告 100 110
· Size Measurement 计算工作量 30 25
· Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 50 65
Total 合计 1200 1340

项目结构

HomeWork [SoftwareHomework]
├── out#运行jar包
│ ├── artifacts
│ │ ├── SoftwareHomework_jar
│ │ │ ├── SoftwareHomework.jar
├── src#项目源文件
│ ├── main
│ │ ├── java
│ │ │ ├── com.cong
│ │ │ │ ├── utils#工具类
│ │ │ │ │ ├── FileUtils.java#读取文件类
│ │ │ │ │ ├── SimHash.java#simhash计算相似度
│ │ │ │ ├── Main.java
│ ├── resources
├── test#测试
│ ├── java
│ │ ├── FileUtilsTest.java
│ │ ├── SimHashTest.java
│ │ ├── orig.txt
│ │ ├── orig_0.8_add.txt
│ │ ├── orig_0.8_del.txt
│ │ ├── orig_0.8_dis_1.txt
│ │ ├── orig_0.8_dis_10.txt
│ │ ├── orig_0.8_dis_15.txt

计算模块接口的设计与实现过程

1. 代码组织结构

本计算模块主要包括以下三个类,每个类具有不同的功能:

SimHash(核心计算类):

  • 计算文本的 SimHash 值。
  • 计算汉明距离。
  • 计算相似度。

FileUtils(文件处理类):

  • 读取文本文件内容。
  • 写入文本文件内容。

Main(主程序类):

  • 读取原文件及多个待比较文件。
  • 计算 SimHash 值。
  • 计算相似度并输出结果。

2. 类与函数关系

类名 主要函数 作用
SimHash getHash(String str) 计算字符串的 MD5 哈希值,并转换为二进制字符串
SimHash getSimHash(String text) 计算文本的 128 位 SimHash 值
SimHash getHammingDistance(String hash1, String hash2) 计算两个 SimHash 之间的汉明距离
SimHash getSimilarity(String hash1, String hash2) 计算两个 SimHash 值的相似度
SimHash getSimilarityPercentage(String hash1, String hash2) 计算相似度并返回格式化百分比
FileUtils readTxt(String txtPath) 读取指定路径的文本文件
FileUtils writeTxt(String content, String txtPath) 向指定路径的文本文件写入内容
Main main(String[] args) 主函数,执行 SimHash 计算与相似度比较

3. 关键算法解析

3.1 SimHash 计算过程

  1. 使用 HanLP 提取文本关键词。
  2. 计算每个关键词的 MD5 哈希值,并转换为 128 位二进制字符串。
  3. 统计 128 维特征向量:
    • 如果某位为 1,则 +1;否则 -1。
  4. 生成最终的 SimHash:
    • 统计结果大于 0 的位置设为 1,否则设为 0。

3.2 汉明距离计算

  1. 遍历两个 SimHash 字符串的每一位。
  2. 计算对应位置不同的个数,即汉明距离。

3.3 相似度计算

  1. 计算汉明距离。
  2. 归一化:相似度 = 1 - (汉明距离 / 128)

4. 代码实现的独到之处

  • 优化关键词提取:使用 HanLP 提取关键词,提高文本特征提取的质量。
  • 高效计算 SimHash:使用 128 维权重数组,减少不必要的字符串处理操作。
  • 文件处理模块化FileUtils 使得文件读取和写入更加简洁、复用性强。
  • 相似度格式化输出:提供“百分之XX.XX”格式,方便直观理解。

5.流程图

simhash流程

文件读取

性能分析

Cpu flame

内存

原因

-关键词提取耗时较长:HanLP.extractKeyword(text, text.length()) 处理大文本时,计算量较大,成为瓶颈。
-MD5 哈希计算较慢:getHash(String str) 需要频繁计算 MD5,可能存在优化空间。
-汉明距离计算存在冗余:getHammingDistance(String hash1, String hash2) 逐位比较时未做优化。

改进思路

-减少关键词提取时间:只提取 固定数量的关键词(如 Math.min(text.length(), 20)),而非 text.length() 个。使用缓存,避免对相同文本重复计算关键词。
-优化哈希计算:采用 更快的哈希算法(如 MurmurHash 代替 MD5),减少 CPU 计算开销。
在 SimHash 计算过程中,避免重复哈希相同的关键词。
-优化汉明距离计算:使用 位操作(Integer.bitCount(xorResult))计算不同位数,替代逐位遍历。

计算模块部分单元测试展示

1. 单元测试代码

1.1 单元测试FileUtils

import com.cong.utils.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class FileUtilsTest {

    private final String testFilePath = "testFile.txt";

    @BeforeEach
    void setup() throws IOException {
        Files.deleteIfExists(Path.of(testFilePath)); // 确保每次测试前文件不存在
    }

    @AfterEach
    void tearDown() throws IOException {
        Files.deleteIfExists(Path.of(testFilePath)); // 测试后清理文件
    }

    @Test
    public void testReadTxt_ValidFile() throws IOException {
        Files.writeString(Path.of(testFilePath), "Hello World!", StandardCharsets.UTF_8);
        String content = FileUtils.readTxt(testFilePath);
        assertEquals("Hello World!", content, "文件内容应与预期值匹配");
    }

    @Test
    public void testReadTxt_FileNotExist() {
        String content = FileUtils.readTxt("nonexistent.txt");
        assertEquals("", content, "不存在的文件应返回空字符串");
    }

    @Test
    public void testWriteTxt_AppendsContent() throws IOException {
        FileUtils.writeTxt("First Line", testFilePath);
        FileUtils.writeTxt("Second Line", testFilePath);

        String content = Files.readString(Path.of(testFilePath), StandardCharsets.UTF_8);
        assertTrue(content.contains("First Line"), "文件应包含第一行内容");
        assertTrue(content.contains("Second Line"), "文件应包含第二行内容");
    }
}

1.2 单元测试SimHash

import com.cong.utils.SimHash;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;

public class SimHashTest {

    @Test
    public void testGetSimHash_NonEmptyForValidText() {
        String text = "今天是星期天,天气晴朗,我去看电影。";
        String simHash = SimHash.getSimHash(text);
        assertNotEquals("", simHash);
        assertEquals(128, simHash.length());
    }

    @Test
    public void testGetHammingDistance_SameHashes() {
        String identical = "1".repeat(128);
        int distance = SimHash.getHammingDistance(identical, identical);
        assertEquals(0, distance);
    }

    @Test
    public void testGetSimilarity_SameHashes() {
        String hash = "1".repeat(128);
        double similarity = SimHash.getSimilarity(hash, hash);
        assertEquals(1.0, similarity, 0.0001);
    }

    @Test
    public void testGetSimilarityPercentage_Format() {
        String hash1 = "1".repeat(128);
        String hash2 = "0".repeat(128);
        String percent = SimHash.getSimilarityPercentage(hash1, hash2);
        assertEquals("百分之0.00", percent);
    }
}

2. 测试数据构造思路

测试点

  1. 正常情况:测试文件读取、文本相似度计算等核心功能是否正确执行。
  2. 边界情况:如空文本、超长文本、特殊字符等输入的处理情况。
  3. 异常情况:如文件不存在、数据格式错误等。

构造方式

  • 采用手动编写测试数据,如 和 作为测试用例。"Hello World!"``"你好,世界!"
  • 对于 SimHash,使用固定长度 128 位哈希值,测试不同汉明距离的影响。

3. 测试覆盖率截图

4.测试截图


计算模块部分异常处理说明

1. 设计目标

在计算模块中,我们可能会遇到以下异常情况:

  • 文件不存在异常:用户尝试读取一个不存在的文件。
  • 文本输入为空异常:SimHash 计算时,输入文本为空。
  • 无效哈希值异常:计算汉明距离时,输入的哈希值格式错误。

2. 异常单元测试示例

2.1 文件不存在异常

java复制编辑@Test
public void testReadTxt_FileNotExist() {
    String content = FileUtils.readTxt("nonexistent.txt");
    assertEquals("", content, "不存在的文件应返回空字符串");
}

错误场景:用户提供了一个不存在的文件路径。


2.2 空文本异常

java复制编辑@Test
public void testGetSimHash_EmptyForShortText() {
    String text = "";
    String simHash = SimHash.getSimHash(text);
    assertEquals("", simHash, "空文本应返回空哈希值");
}

错误场景:SimHash 计算时输入的文本为空。


2.3 无效哈希值异常

java复制编辑@Test
public void testGetHammingDistance_InvalidHash() {
    String hash1 = "abc";  // 非 128 位二进制字符串
    String hash2 = "1".repeat(128);
    assertThrows(IllegalArgumentException.class, () -> {
        SimHash.getHammingDistance(hash1, hash2);
    }, "无效哈希值应抛出异常");
}

错误场景:计算汉明距离时,输入的哈希值格式不正确。

github记录

方法局限性说明

尽管该计算模块能够有效计算文本的 SimHash 值并进行相似度比较,但仍然存在一些局限性:

  1. 对短文本处理效果有限

    • SimHash 依赖文本特征的分布,短文本可能无法形成足够的特征向量,导致哈希值不稳定。
    • testGetSimHash_EmptyForShortText 例子中,过短文本返回空哈希值,可能影响后续计算。
  2. 无法处理语义相似度

    • SimHash 仅基于关键词和哈希计算相似度,不考虑语义信息。例如,"我去看电影" 和 "我去电影院" 可能具有不同的 SimHash 值。
  3. 汉明距离计算存在误差

    • SimHash 计算基于二进制位,计算汉明距离时未考虑词频或权重变化,因此在某些文本类型下可能导致误差。
  4. 对文本修改的鲁棒性不足

    • 如果文本只修改了少量高权重词语,SimHash 可能会发生较大变化,导致相似度计算不准确。
  5. 文件处理的错误恢复能力有限

    • FileUtils.readTxt() 仅返回空字符串,而不是抛出异常,可能导致用户误以为文件内容为空,而不是文件缺失。
  6. 对于超长文本计算性能受限

    • 由于 SimHash 计算需要对文本进行关键词提取和哈希转换,超长文本处理可能会导致计算时间增加。
    • 解决方案可以是采用分块计算 SimHash 或使用局部敏感哈希(LSH)优化大规模文本对比。

5. 未来优化方向

针对上述局限性,可以考虑以下优化方案:

  • 改进短文本处理:使用 NLP 方法对短文本进行扩展,使其能形成稳定特征。
  • 引入语义分析:结合 Word2Vec 或 BERT 提升语义理解能力,避免仅靠关键词匹配。
  • 优化汉明距离计算:在计算汉明距离时考虑词频和权重,提升相似度计算精度。
  • 增强异常处理机制:区分文件不存在与文件为空的情况,提高文件操作的可靠性。
  • 提升计算效率:采用哈希索引或并行计算方式优化超长文本 SimHash 计算速度。
posted @ 2025-03-07 12:16  ALESK  阅读(72)  评论(0)    收藏  举报