第一次个人编程作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477
这个作业的目标 完成论文查重项目,学习如何进行单元测试和性能改进

一、GitHub链接

https://github.com/fanfanlilili/3123004282

二、PSP表格

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

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

层级 名称 作用
工具函数 generate_ngrams_from_bytes 把 UTF-8 中文内容切成 n-gram
工具函数 calculate_similarity_optimized 基于 n-gram 计算两文本重复率
工具函数 read_file 一次性读文件到 string
主流程 main 顺序调用上面 3 个函数,完成“读→算→写”

1.主函数流程图

3be3b0c345eada71ed8a2f3b265e4c1c

2.calculate_similarity_optimized流程图

2c031bd73eb47af8cdb3cff08a5ef790

3.generate_ngrams_from_bytes流程图

generate_ngrams_from_bytes

4.关键部分

(1)N-Gram生成:基于UTF-8编码过滤非中文字符,滑动窗口生成连续的中文字符
(2)相似度计算:通过哈希集合快速匹配N-Gram,统计重复比例
(3)预分配内存:提升性能

四、性能改进(耗时180分钟)

1.改进思路

主要从UTF-8解析、内存分配以及查找优化着手,其中查找优化是最主要的改进,明显降低了时间复杂度
(1)使用指针p直接便利字节流,避免索引计算,同时引入滑动窗口机制以避免存储所有中文字符
(2)预分配ngram向量和char_buffer空间以减少动态扩容次数
(3)使用哈希查找法,将时间复杂度从n^2降低到1

2.改进前CPU占用情况

性能1

3.改进后CPU占用情况

性能2
很明显,占用最多的函数就是get_chinese_chars函数

五、单元测试(将Gtest框架引入VS2022)

1.测试覆盖的函数和场景(一共12个用例)

函数 场景
get_chinese_chars 验证是否能正确提取中文字符以及不完整三序列、空输入的处理
generate_ngrams 验证不同长度输入下的N-Gram生成
calculate_similarity 验证完全相同、无重叠、部分重叠等场景的重复率计算
read_file 验证文件内容是否能正常读取

2.部分用例说明

(1)测试get_chinese_chars
TEST(GetChineseCharsTest, ExtractsChineseCharactersCorrectly) {
// 输入包含ASCII、双字节非中文、三字节中文、四字节字符
string input =
"A" // 单字节ASCII
"\xC3\xB1" // 双字节非中文(ñ)
"\xE4\xBD\xA0" // 三字节中文(你)
"\xE5\xA5\xBD" // 三字节中文(好)
"\xF0\x9F\x98\x8A";// 四字节字符(😊)

vector<string> expected = { "\xE4\xBD\xA0", "\xE5\xA5\xBD" };
vector<string> result = get_chinese_chars(input);
ASSERT_EQ(result, expected);

}
(2)测试generate_ngrams
TEST(GenerateNGramsTest, SufficientLength) {
vector chars = { "a", "b", "c", "d" }; // 假设每个元素是中文字符的三字节字符串
int n = 3;
vector expected = { "abc", "bcd" }; // 拼接后的N-Gram
vector result = generate_ngrams(chars, n);
ASSERT_EQ(result, expected);
}

TEST(GenerateNGramsTest, NotEnoughLength) {
vector chars = { "a", "b" }; // 长度2 < n=3
vector result = generate_ngrams(chars, 3);
ASSERT_TRUE(result.empty());
}

TEST(GenerateNGramsTest, ExactLength) {
vector chars = { "x", "y", "z" }; // 长度3 == n=3
vector expected = { "xyz" };
vector result = generate_ngrams(chars, 3);
ASSERT_EQ(result, expected);
}
(3)测试calculate_similarity
TEST(CalculateSimilarityTest, NoCommonNGrams) {
string orig_content = "我爱你中国"; // 字符:我、爱、你、中、国(5个字符,生成3个N-Gram)
string copy_content = "天地人和"; // 字符:天、地、人、和(4个字符,生成2个N-Gram)

string orig_path = create_temp_file(orig_content);
string copy_path = create_temp_file(copy_content);
string output_path = "test_output.txt";

double similarity = calculate_similarity(orig_path, copy_path, 3);

// 原N-Gram: ["我愛你", "愛中國"]
// 抄袭版N-Gram: ["天地人", "地和人"](假设无重叠)
EXPECT_DOUBLE_EQ(similarity, 0.0);

remove_temp_file(orig_path);
remove_temp_file(copy_path);
remove(output_path.c_str());

}
(4)测试read_file
TEST(ReadFileTest, ReadsContentCorrectly) {
string content = "测试文件读取";
string path = create_temp_file(content);
string read_content = read_file(path);
ASSERT_EQ(read_content, content);
remove_temp_file(path);
}

3.覆盖率

image

4.单元测试情况

image

12个测试用例通过十个,属于正常现象,该程序没有引入英文字符的解析(仅解析中文字符)

六、异常情况处理及其单元测试用例

1.空路径时报错

(1)原码

if (path.empty()) {
throw invalid_argument("错误:文件路径为空");
}

(2)用例

TEST(ReadFileExceptionTest, EmptyPathThrowsInvalidArgument) {
EXPECT_THROW({
read_file(""); // 空路径
}, invalid_argument);
}

2.文件不存在时报错

(1)原码

// 检查文件是否成功打开(新增详细错误信息)
if (!file.is_open()) {
    // 获取系统错误信息(跨平台)

ifdef _WIN32
DWORD error_code = GetLastError();
LPSTR error_msg = nullptr;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
nullptr,
error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&error_msg,
0,
nullptr
);
string msg = "错误:无法打开文件 '" + path + "'。系统错误:" + error_msg;
LocalFree(error_msg); // 必须释放 FormatMessageA 分配的内存
else
string msg = "错误:无法打开文件 '" + path + "'。错误码:" + to_string(errno);
endif
throw runtime_error(msg);
}

(2)用例

TEST(ReadFileExceptionTest, NonExistentFileThrowsRuntimeError) {
std::string non_existent_path;
ifdef _WIN32
non_existent_path = "C:\nonexistent_file_987654321.txt"; // Windows 绝对路径
else
non_existent_path = "/tmp/nonexistent_file_987654321.txt"; // Linux/macOS 绝对路径
endif
// 确保文件不存在(跨平台)
ifdef _WIN32
DeleteFileA(non_existent_path.c_str()); // 忽略错误(假设不存在)
else
unlink(non_existent_path.c_str()); // 忽略错误(假设不存在)
endif
EXPECT_THROW({
read_file(non_existent_path);
}, runtime_error);
}

3.空文件时报错

(1)原码

// 检查文件是否为空(新增)
file.seekg(0, ios::end);
if (file.tellg() == 0) {
    throw runtime_error("错误:文件 '" + path + "' 为空");
}
file.seekg(0, ios::beg);  // 回到文件开头

(2)用例

TEST(ReadFileExceptionTest, EmptyFileThrowsRuntimeError) {
std::string temp_path = create_temp_file(""); // 创建空文件
EXPECT_THROW({
read_file(temp_path);
}, runtime_error);
delete_temp_file(temp_path); // 清理临时文件
}

posted @ 2025-09-23 20:48  fanfanlilili  阅读(9)  评论(0)    收藏  举报