C++ 轻量级搜索引擎实战:正/倒排索引构建指南
基于 C++ 实现轻量级搜索引擎核心模块,重点讲解正排与倒排索引的数据结构设计及构建流程。利用 cppjieba 进行中文分词,通过文件读取清洗后的数据,建立文档 ID 与内容的映射关系(正排),以及关键词与文档 ID 列表的映射关系(倒排)。采用单例模式管理索引对象,结合 STL 容器与哈希表优化检索性能,完成从分词到索引生成的完整逻辑。

基于 C++ 实现轻量级搜索引擎核心模块,重点讲解正排与倒排索引的数据结构设计及构建流程。利用 cppjieba 进行中文分词,通过文件读取清洗后的数据,建立文档 ID 与内容的映射关系(正排),以及关键词与文档 ID 列表的映射关系(倒排)。采用单例模式管理索引对象,结合 STL 容器与哈希表优化检索性能,完成从分词到索引生成的完整逻辑。

这是一个聚焦基础搜索引擎核心工作流的实操项目,基于 C/C++ 技术生态落地:从全网爬虫抓取网页资源,到服务器端完成'去标签 - 数据清洗 - 索引构建'的预处理,再通过 HTTP 服务接收客户端请求、检索索引并拼接结果页返回 —— 完整覆盖了轻量级搜索引擎的端到端逻辑。项目采用 C++11、STL、Boost 等核心技术栈,搭配 CentOS 7 云服务器 + GCC 编译环境部署,既适配后端工程的性能需求,也能通过可选的前端技术优化用户交互,是理解搜索引擎底层原理与 C++ 工程实践的典型案例。
在使用倒排索引时需要用到'关键词',这个关键词由每个.html 文档的标题和内容而来,因此涉及分词。我们使用 cppjieba 分词工具来完成。
如果需要使用到 cppjieba 分词工具,可以直接在本地上传到服务器:
然后对 cppjieba/include/cppjieba 和 cppjieba/dict 分别建立软链接:头文件和词库。
把 cppjieba 移动到上级目录,然后更新一下这两个软链接。
//正排结构
typedef struct Forward_index {
std::string title;
std::string source;
std::string chain;
uint64_t doc_id;
} Forwardindex;
//倒排结构
typedef struct Inverted_index {
int doc_id;
std::string word;
int weight;
} Invertedindex;
正排:根据 ID 映射对应的文档(标题、内容、URL),ID 利用 vector 的下标。 倒排:根据'关键字'映射对应的 ID,利用 unordered_map 快速的搜索特性。
//正排存储
std::vector<Forwardindex> Forward;
typedef std::vector<Invertedindex> Stock_Inverted;
//倒排存储
std::unordered_map<std::string, Stock_Inverted> Inverted;
含义:即正排外部的实现,根据 ID 返回 vector 中对应的具体内容。
Forwardindex* GetForward_index(const long long& id) {
if(id >= Forward.size()) {
std::cerr << "GetForward_index is errno" << std::endl;
return nullptr;
}
return &Forward[id];
}
含义:倒排外部实现,根据'关键字'返回对应的 ID,即返回对应的 vector 成员。 解释:每个关键词涉及到的 ID 肯定不止一个,所以需要 vector 代表一个关键词涉及到的所有文档。
Stock_Inverted* GetInverted_index(const std::string word) {
auto it = Inverted.find(word);
if(it == Inverted.end()) {
std::cerr << "GetInverted_index is errno" << std::endl;
return {};
}
return &it->second;
}
现在我们已经有了给外部的接口:由 ID 返回文档内容、由关键词返回文档 ID。 那么接下来就是填变量、建立索引关系:
//正排存储
std::vector<Forwardindex> Forward;
typedef std::vector<Invertedindex> Stock_Inverted;
//倒排存储
std::unordered_map<std::string, Stock_Inverted> Inverted;
原理:利用上一篇文章完成的数据清洗,最终每个.html 文档内容都被输出到了 data_clean.txt 中。 我们先利用 getline 按行读取(也就是按单个文档读取,因为 data_clean.txt 中也是按行写的)到 line 变量里面,这样就拿到了单个文档的内容,因此 while 就是拿取 data_clean.txt 的全部文档内容! 建索引也就是同时建立正排和倒排的内容,也就是填 vector 和 Unordered_map 的操作!
bool Buildindex(const std::string &input) {
std::ifstream in(input, std::ios::in | std::ios::binary);
if(!in.is_open()) {
std::cerr << "Buildindex is errno" << std::endl;
return false;
}
std::string line;
while(std::getline(in, line)) {
Forwardindex *doc = Build_Forward_Index(line);
printf("正在建立索引:%lld\ntitle:%s\nchain:%s\n", doc->doc_id, doc->title.c_str(), doc->chain.c_str());
if(doc == nullptr) continue;
Build_Inverted_Index(*doc);
}
return true;
}
思路:首先我们拿到了单个文档的内容,也就是 line(string)变量,然后对这个内容进行解析,提取出标题、内容、URL(每个 line 变量都是:标题 | 内容 | URL 的格式,分割即可!)然后填入到 vector 里面,该文档的 ID 刚好就是 vector 的下标。
Forwardindex* Build_Forward_Index(const std::string& line) {
Forwardindex* index = new Forwardindex();
size_t set_pos1 = line.find('\3');
if(set_pos1 == std::string::npos) {
delete index;
return nullptr;
}
index->title = line.substr(0, set_pos1);
if (index->title.empty()) index->title = "空";
size_t set_pos2 = line.find('\3', set_pos1+1);
if(set_pos2 == std::string::npos) {
delete index;
return nullptr;
}
index->source = line.substr(set_pos1+1, set_pos2 - (set_pos1+1));
if (index->source.empty()) index->source = "空";
index->chain = line.substr(set_pos2+1);
if (index->chain.empty()) index->chain = "空";
index->doc_id = Forward.size();
Forward.push_back(*index);
return index;
}
typedef struct Forward_index {
std::string title;
std::string source;
std::string chain;
uint64_t doc_id;
} Forwardindex;
bool Build_Inverted_Index(const Forwardindex& doc) {
JiebaUtil jieba;
std::vector<std::string> S;
S = jieba.Tokenize(doc.title);
struct culculate {
int title_size=0;
int source_size=0;
};
std::unordered_map<std::string, culculate> V;
for(auto e : S) {
(V[e].title_size)++;
}
S.clear();
S = jieba.Tokenize(doc.source);
for(auto e : S) {
(V[e].source_size)++;
}
for(auto it : V) {
Invertedindex index_t;
index_t.word = it.first;
index_t.doc_id = doc.doc_id;
index_t.weight = ((it.second.title_size)*2 + (it.second.source_size)*1);
Inverted[it.first].push_back(std::move(index_t));
}
return true;
}
单例模式:只允许创建一个 Index 对象,后面只能由 index 指针调用该对象。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
class Index {
public:
typedef std::vector<Invertedindex> Stock_Inverted;
Index(const Index&) = delete;
Index& operator=(const Index&) = delete;
static Index* handle() {
if(instance == nullptr) {
pthread_mutex_lock(&mutex);
if (instance == nullptr) {
instance = new Index;
}
pthread_mutex_unlock(&mutex);
}
return instance;
}
......
};
Index* Index::instance = nullptr;

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online