一、项目背景与关键概念

在现代信息检索系统中,搜索引擎的核心任务是从海量数据中快速、准确地返回用户所需信息。为此,正排索引倒排索引成为不可或缺的两大数据结构。

  • 正排索引(Forward Index):以文档ID为主键,记录每篇文档的标题、内容、URL等元信息,便于快速获取文档详情。
  • 倒排索引(Inverted Index):以关键词为主键,记录包含该词的所有文档ID及其权重,是实现关键词检索的核心。

在 C++之基于正倒排索引的Boost搜索引擎项目正倒排索引部分代码及详解 中,开发者利用 Boost 库、cppjieba 分词器与哈希表结构,构建了一个轻量级但高效的站内搜索引擎,适用于技术文档、博客系统等场景。


二、核心技巧与架构设计

该项目采用模块化设计,主要分为以下三层:

模块功能
Parser清洗 HTML 标签,提取 title、content、URL
Index构建正排与倒排索引
Searcher接收关键词,返回搜索结果

其中,Index 模块是核心,其正倒排索引结构设计如下:

std::vector<DocInfo> forward_list;  // 正排索引
std::unordered_map<std::string, std::vector<InvertedElem>> inverted_list;  // 倒排索引
  • DocInfo 结构体保存每篇文档的完整信息;
  • InvertedElem 包含关键词、文档ID和权重;
  • 使用 unordered_map 实现 O 复杂度的关键词查找。

三、应用场景

该搜索引擎适用于以下场景:

  • 技术文档站:如 Boost 官方文档的本地检索;
  • 博客平台:支持全文搜索、关键词高亮;
  • 企业内部知识库:快速定位政策、手册、FAQ 等内容。

四、详细代码分析(重点)

以下为 正倒排索引构建 的核心代码分析,选自项目中的 index.hpp 文件。

1. 正排索引构建函数:BuildForwardIndex
DocInfo* BuildForwardIndex(const std::string& line)
{
    std::vector<std::string> results;
    boost::split(results, line, boost::is_any_of("\3"), boost::token_compress_on);

    if (results.size() != 3) return nullptr;

    DocInfo doc;
    doc.title = results[0];
    doc.content = results[1];
    doc.url = results[2];
    doc.doc_id = forward_list.size();  // 自动生成 doc_id
    forward_list.push_back(std::move(doc));
    return &forward_list.back();
}

解析

  • 每行数据格式为:title\3content\3url,使用 \3 作为分隔符;
  • boost::split 高效切分字符串;
  • doc_id 使用 forward_list 的下标,天然唯一且连续;
  • 使用 std::move 优化内存拷贝,提升性能。
2. 倒排索引构建函数:BuildInvertedIndex
bool BuildInvertedIndex(const DocInfo& doc)
{
    std::unordered_map<std::string, cnt> cnt_map;

    // 标题分词并统计词频
    std::vector<std::string> title_words;
    jieba_util::CutString(doc.title, &title_words);
    for (const auto& word : title_words) cnt_map[word].title_cnt++;

    // 内容分词并统计词频
    std::vector<std::string> content_words;
    jieba_util::CutString(doc.content, &content_words);
    for (const auto& word : content_words) cnt_map[word].content_cnt++;

    // 构建倒排元素并插入索引
    for (const auto& pair : cnt_map) {
        InvertedElem elem;
        elem.doc_id = doc.doc_id;
        elem.word = pair.first;
        elem.weight = pair.second.title_cnt * 9 + pair.second.content_cnt * 1;
        inverted_list[elem.word].push_back(std::move(elem));
    }
    return true;
}

解析

  • 使用 cppjieba 对标题和内容进行中文分词;
  • 标题词频权重为 9,内容为 1,体现标题的重要性;
  • 使用 unordered_map 临时统计词频,避免重复插入;
  • 最终构建 InvertedElem 并插入倒排索引;
  • 权重计算公式简单但有效,适合中小型文档集。
3. 索引单例模式设计
static index* GetInstance() {
    if (nullptr == instance) {
        mtx.lock();
        if (nullptr == instance) {
            instance = new index();
        }
        mtx.unlock();
    }
    return instance;
}

解析

  • 使用 双重检查锁(DCL) 实现线程安全的单例;
  • 避免每次调用都加锁,提高性能;
  • 适合在多线程环境下构建索引。

五、未来发展趋势
  1. 引入倒排压缩算法:如 Variable Byte Encoding、PForDelta,减少内存占用;
  2. 支持分布式索引构建:基于 Raft 或 Paxos 实现多节点一致性;
  3. 引入向量检索(Embedding):结合语义搜索,提升检索准确率;
  4. 支持增量更新:避免全量重建索引,提高实时性;
  5. 前端可视化集成:结合 Vue + Elasticsearch 实现更友好的搜索体验。

更多推荐