跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 基于正倒排索引的搜索引擎核心实现与代码详解

C++ 搜索引擎正倒排索引模块设计涉及 DocInfo 与 InvertedElem 数据结构,采用 vector 存储正排索引,unordered_map 配合倒排拉链存储倒排索引。通过单例模式管理全局实例以保障线程安全与资源复用。构建过程包含文档分词、词频统计及权重计算,标题权重高于正文。提供索引构建与检索接口,支持二进制文件读取与日志记录,适用于搜索系统基础架构开发。

laoliangsh发布于 2026/2/9更新于 2026/7/741 浏览
C++ 基于正倒排索引的搜索引擎核心实现与代码详解

首先,正排索引与倒排索引在搜索引擎项目中至关重要。正排索引将文档内容映射到文档 ID,倒排索引则根据关键词映射到文档 ID。搜索引擎在处理文档时,先创建正排索引,再基于其生成倒排索引。当用户查询时,利用倒排索引快速定位文档,结合正排索引展示结果。

1. 正倒排索引的结构

1.1 正排索引

正排索引存储文档内容及对应 ID。

// 正排索引结构体
typedef struct DocInfo {
    std::string title;   // 文档标题
    std::string content; // 文档内容
    std::string url;     // 文档 URL
    int doc_id;          // 文档 ID
} DocInfo1;

1.2 倒排索引

倒排索引存储文档 ID、关键字及权重。InvertedList 通常称为倒排拉链,一个关键字可能对应多个文档。

// 倒排索引元素结构体
struct InvertedElem {
    int doc_id;      // 文档 ID
    std::string word;// 关键字
    int weight;      // 权重
};
typedef std::vector<InvertedElem> InvertedList;

2. 正倒排序部分 Class 的 Private 部分

2.1 准备工作

正排索引使用 vector,下标即为文档 ID,便于访问。倒排索引使用哈希表(unordered_map),实现关键字到倒排文档列表的映射。

private:
    // 正排索引
    std::vector<DocInfo1> forward_index;
    // 倒排索引
    std::unordered_map<std::string, InvertedList> inverted_index;

2.2 单例模式

采用单例模式管理索引实例,减少资源浪费,确保全局逻辑统一,简化资源管理。需禁用拷贝构造函数和赋值运算符,并使用互斥锁防止多线程并发创建多实例。

private:
    Index() {};
    Index(const Index&) = delete;
    Index& operator=(const Index&) = delete;

    static Index* instance;
     std::mutex log;

:
    ~();

    {
         (instance == ) {
            log.();
             (instance == ) {
                instance =  ();
            }
            log.();
        }
         instance;
    }
static
public
Index
static Index* Getinstance()
if
nullptr
lock
if
nullptr
new
Index
unlock
return

3. 去标签后的文档构建正倒排索引

3.1 创建正排索引

通过 Split 函数分词,将切分后的数据填入临时变量 doc,再存入 forward_index。ID 由当前 size 决定。

DocInfo1* BuildForwardIndex(const std::string& line) {
    std::vector<std::string> results;
    ns_util::StringUtil::Split(line, &results, "\3"); // 字符串切割,分离 title, content, url
    if (results.size() != 3) return nullptr;

    DocInfo1 doc;
    doc.title = results[0];
    doc.content = results[1];
    doc.url = results[2];
    doc.doc_id = forward_index.size();

    forward_index.push_back(doc);
    return &forward_index.back();
}

3.2 创建倒排索引

流程:分词 -> 词频统计 -> 权重计算 -> 填充倒排索引。使用 word_cnt 结构体存储词频,CutString 进行分词,to_lower 统一大小写。

bool BuildInvertedIndex(const DocInfo1& doc) {
    struct word_cnt {
        int title_cnt;
        int content_cnt;
        word_cnt() : title_cnt(0), content_cnt(0) {}
    };

    std::unordered_map<std::string, word_cnt> word_map;
    std::vector<std::string> title_words;
    ns_util::JiebaUtil::CutString(doc.title, &title_words);
    for (auto& tw : title_words) {
        boost::to_lower(tw);
        word_map[tw].title_cnt++;
    }

    std::vector<std::string> content_words;
    ns_util::JiebaUtil::CutString(doc.content, &content_words);
    for (auto& cw : content_words) {
        boost::to_lower(cw);
        word_map[cw].content_cnt++;
    }

    #define X 10
    #define Y 1
    for (auto& word_pair : word_map) {
        InvertedElem item;
        item.doc_id = doc.doc_id;
        item.word = word_pair.first;
        item.weight = X * word_pair.second.title_cnt + Y * word_pair.second.content_cnt;
        inverted_index[word_pair.first].push_back(item);
    }
    return true;
}

3.3 同时调用构建索引

以二进制方式读取原始文档,逐行处理并调用上述两个函数。包含简单的日志记录。

bool BuildIndex(const std::string& input) {
    std::ifstream in(input, std::ios::in | std::ios::binary);
    if (!in.is_open()) {
        std::cout << input << "open error" << std::endl;
        return false;
    }

    int count = 0;
    std::string line;
    while (std::getline(in, line)) {
        DocInfo1* doc = BuildForwardIndex(line);
        if (doc == nullptr) {
            std::cout << "BuildIndex error" << std::endl;
            continue;
        }
        BuildInvertedIndex(*doc);
        count++;
        if (count % 50 == 0) LOG1(NORMAL, "索引建立到:" + std::to_string(count));
    }
    return true;
}

4. 获取正倒排索引

4.1 获取正排索引

防御性编程检查 doc_id 范围,返回对应文档内容。

DocInfo1* GetForwardIndex(uint64_t doc_id) {
    if (doc_id >= forward_index.size()) {
        std::cout << "doc_id out range, error!" << std::endl;
        return nullptr;
    }
    return &forward_index[doc_id];
}

4.2 获取倒排索引

查找关键字是否存在,存在则返回对应的倒排拉链地址。

InvertedList* GetInvertedList(const std::string& word) {
    auto iter = inverted_index.find(word);
    if (iter == inverted_index.end()) {
        std::cout << word << "get error" << std::endl;
        return nullptr;
    }
    return &(iter->second);
}

5. 总结

本文档围绕搜索引擎核心的正倒排索引模块,从设计思路、数据结构、实现流程到核心接口,系统梳理了完整实现逻辑。以下是这一部分的完整代码:

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <mutex>
#include "usuallytool.hpp"
#include <boost/algorithm/string.hpp>
#include "log.hpp"

namespace ns_index {
    typedef struct DocInfo {
        std::string title;
        std::string content;
        std::string url;
        int doc_id;
    } DocInfo1;

    struct InvertedElem {
        int doc_id;
        std::string word;
        int weight;
    };
    typedef std::vector<InvertedElem> InvertedList;

    class Index {
    private:
        std::vector<DocInfo1> forward_index;
        std::unordered_map<std::string, InvertedList> inverted_index;

        Index() {};
        Index(const Index&) = delete;
        Index& operator=(const Index&) = delete;

        static Index* instance;
        static std::mutex log;

    public:
        ~Index();

        static Index* Getinstance() {
            if (instance == nullptr) {
                log.lock();
                if (instance == nullptr) {
                    instance = new Index();
                }
                log.unlock();
            }
            return instance;
        }

        DocInfo1* GetForwardIndex(uint64_t doc_id) {
            if (doc_id >= forward_index.size()) {
                std::cout << "doc_id out range, error!" << std::endl;
                return nullptr;
            }
            return &forward_index[doc_id];
        }

        InvertedList* GetInvertedList(const std::string& word) {
            auto iter = inverted_index.find(word);
            if (iter == inverted_index.end()) {
                std::cout << word << "get error" << std::endl;
                return nullptr;
            }
            return &(iter->second);
        }

        bool BuildIndex(const std::string& input) {
            std::ifstream in(input, std::ios::in | std::ios::binary);
            if (!in.is_open()) {
                std::cout << input << "open error" << std::endl;
                return false;
            }
            int count = 0;
            std::string line;
            while (std::getline(in, line)) {
                DocInfo1* doc = BuildForwardIndex(line);
                if (doc == nullptr) {
                    std::cout << "BuildIndex error" << std::endl;
                    continue;
                }
                BuildInvertedIndex(*doc);
                count++;
                if (count % 50 == 0) LOG1(NORMAL, "索引建立到:" + std::to_string(count));
            }
            return true;
        }

    private:
        DocInfo1* BuildForwardIndex(const std::string& line) {
            std::vector<std::string> results;
            ns_util::StringUtil::Split(line, &results, "\3");
            if (results.size() != 3) return nullptr;
            DocInfo1 doc;
            doc.title = results[0];
            doc.content = results[1];
            doc.url = results[2];
            doc.doc_id = forward_index.size();
            forward_index.push_back(doc);
            return &forward_index.back();
        }

        bool BuildInvertedIndex(const DocInfo1& doc) {
            struct word_cnt {
                int title_cnt;
                int content_cnt;
                word_cnt() : title_cnt(0), content_cnt(0) {}
            };
            std::unordered_map<std::string, word_cnt> word_map;
            std::vector<std::string> title_words;
            ns_util::JiebaUtil::CutString(doc.title, &title_words);
            for (auto& tw : title_words) {
                boost::to_lower(tw);
                word_map[tw].title_cnt++;
            }
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::CutString(doc.content, &content_words);
            for (auto& cw : content_words) {
                boost::to_lower(cw);
                word_map[cw].content_cnt++;
            }
            #define X 10
            #define Y 1
            for (auto& word_pair : word_map) {
                InvertedElem item;
                item.doc_id = doc.doc_id;
                item.word = word_pair.first;
                item.weight = X * word_pair.second.title_cnt + Y * word_pair.second.content_cnt;
                inverted_index[word_pair.first].push_back(item);
            }
            return true;
        }
    };
    Index* Index::instance = nullptr;
    std::mutex Index::log;
}

目录

  1. 1. 正倒排索引的结构
  2. 1.1 正排索引
  3. 1.2 倒排索引
  4. 2. 正倒排序部分 Class 的 Private 部分
  5. 2.1 准备工作
  6. 2.2 单例模式
  7. 3. 去标签后的文档构建正倒排索引
  8. 3.1 创建正排索引
  9. 3.2 创建倒排索引
  10. 3.3 同时调用构建索引
  11. 4. 获取正倒排索引
  12. 4.1 获取正排索引
  13. 4.2 获取倒排索引
  14. 5. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • GraphRAG:基于 PolarDB、通义千问和 LangChain 的知识图谱与大模型融合方案
  • AI 提示词的应用场景、编写技巧与防御策略
  • 前端开发常用开源 JavaScript 库、框架与工具
  • 非科班转码者 AI 学习路径:从 0 到 1
  • 自进化医疗智能体:动态记忆与持续运行 Python 架构编程(下)
  • 归并排序时间复杂度 O(nlogn) 解析:LeetCode 148 排序链表
  • Git 入门实战:从零理解版本控制与团队协作
  • Java JCache 缓存驱逐与缓存过期的本质区别及触发机制解析
  • Python 技术副业实战指南:从入门学习到数据变现路径
  • GraphQL 在 Python 中的完整实现:从基础到企业级实战
  • GraphRAG 知识图谱构建全流程解析
  • C++ DFS 与 BFS 算法实战详解
  • AI 与存储的结合:智能存储的实践与挑战
  • 二叉排序树与堆的区别
  • Elasticsearch 与 Kibana 实战:安装部署及 C++ 客户端封装
  • AI 技术演进:从 Function Calling 到 MCP
  • GTC Taipei 2025 医疗领域前瞻:AI 代理与医疗生态变革
  • 鸿蒙金融理财全栈项目——生态合作、用户运营、数据变现
  • AR 眼镜光学镜头设计实例及核心技巧解析
  • 基于五条标注数据快速完成快递单信息抽取
  • 相关免费在线工具

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

    • Markdown转HTML

      将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

    • HTML转Markdown

      将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online