go路由httprouter中的壓縮字典樹算法圖解及c++實現
@
前言
准備從嵌入式往go后端轉,今年准備學習一下gin框架,決定先從這個輕量級的路由請求器着手,本文講講它用到的壓縮字典樹算法。
httprouter簡介
HttpRouter是一個Go編寫的輕量級的高性能Http請求路由器(也可稱為多路選擇器multiplexer簡稱mux)
與Go的net/http包的默認mux不同,該路由器支持路由中的變量與請求方法進行匹配,同時具有很好的伸縮性。
該路由具有高性能的同時,也優化了內存的占用,即是是很長的路徑和大量的路徑,他都能很好的擴展,采用壓縮字典樹(基數樹)結構實現高效的匹配。
壓縮字典樹
概念
壓縮字典樹,是trie樹的一種,也稱單詞查找樹、前綴樹,善於進行字符串的檢索、取字符串最長公共前綴、以及排序,常應用在搜索引擎中例如百度輸入蔡
可能自動彈出能匹配到的單詞出來.
壓縮tire和標准trie最大的不同點就節點的數量與插入字符串的個數成正比,而不是與字符串的長度成正比,所以當字符串數量越來越多,越密集且相似度極高的情況下,會退化成標准trie樹。
下面分別是/,/bear,/bell,/bid,/bull,/buy,/sell,/stock,/stop 的標准tire 和壓縮 tire
插入操作
下面圖解一串子串插入壓縮trie過程,/,/serach,/support,/blog
, 在httprouter上截的一段例子,我們只插到/blog
插入/
插入/serach
插入/support
插入/blog
查詢操作
查詢比較簡單,后面看代碼也比較快。
1、先找共同前綴。
2、再找目錄。
3、循環上面兩步,知道當前path相等。
c+++實現
這里注冊了4個路徑的回調函數,addRoute 即是插入操作,handler即是查詢。
// httprouter.hpp
#pragma once
#include <string>
#include <vector>
#include <functional>
#include <memory>
namespace httprouter{
typedef std::function<void(void)> handler_t;
typedef struct _tree_node {
std::string path;
std::string indices;
std::vector<std::shared_ptr<struct _tree_node>> children;
handler_t handle;
}tree_node_t;
class node
{
public:
//! ctor
node();
//! dtor
~node(void);
//! copy ctor
node(const node&) = delete;
//! assignment operator
node& operator=(const node&) = delete;
//! addRouter adds a node with the given handle to the path
//! Not concurrency-safe!
void addRoute(std::string path, handler_t handle);
//! get path handler
handler_t handler(std::string path);
private:
void insertChild(tree_node_t* node, std::string& path, handler_t handle);
private:
std::shared_ptr<tree_node_t> node_;
};
}
// httprouter.cpp
#include <algorithm>
#include "httprouter.hpp"
using namespace httprouter;
node::node()
:node_(new tree_node_t{
path: "",
indices: "",
children: {},
handle: nullptr,
})
{
}
node::~node(){
}
void node::addRoute(std::string path, handler_t handle) {
std::string fullPath = path;
auto node = node_;
// no-empty tree
if (node->path.size() > 0 || node->children.size() > 0) {
while (true) {
bool have_indices = false;
//find the longest common prefix.
std::size_t i = 0;
auto max = std::min(node->path.size(), path.size());
for (; i < max && path[i] == node->path[i];) {
i++;
}
// Split edge
if (i < node->path.size()) {
auto child = std::shared_ptr<tree_node_t>(new tree_node_t{
path : std::string(node->path.c_str() + i),
indices : node->indices,
children : std::move(node->children),
handle : node->handle,
});
node->children = std::vector<std::shared_ptr<tree_node_t>>{ child };
node->indices = std::string(node->path.c_str() + i, 1);
node->path = std::string(path.c_str(), i);
node->handle = nullptr;
}
// make new node a child of this node
if (i < path.size()) {
path = std::string(path.c_str() + i);
char ch = path[0];
// Check if a child with the next path byte exists
for (std::size_t i = 0; i < node->indices.size(); i++) {
if (ch == node->indices[i]) {
//i = node.incrementChildPrio(i);
node = node->children[i];
have_indices = true;
break;
}
}
if (have_indices) {
continue;
}
//otherwise insert it
if (ch != ':' && ch != '*') {
node->indices += ch;
auto child = std::shared_ptr<tree_node_t>(new tree_node_t{
path : "",
indices : "",
children : {},
handle : nullptr,
});
node->children.push_back(child);
node = child;
}
insertChild(node.get(), path, handle);
return;
}
else if (i == path.size()) {
if (node->handle) {
printf("error ! handle already exists.");
exit(1);
}
node->handle = handle;
}
return;
}
}
else { // Empty tree
insertChild(node.get(), path, handle);
}
}
void node::insertChild(tree_node_t* node, std::string& path, handler_t handle) {
node->path = std::string(path.c_str());
node->handle = handle;
}
handler_t node::handler(std::string path) {
auto node = node_;
while (true) {
if (path.size() > node->path.size()) {
if (std::string(path.c_str(), node->path.size()) == node->path) {
path = std::string(path.c_str() + node->path.size());
}
char ch = path[0];
for (std::size_t i = 0; i < node->indices.size(); i++) {
if (ch == node->indices[i]) {
node = node->children[i];
continue;
}
}
// handle wildcard child
// fix me
}
else if (path == node->path) {
return node->handle;
}
}
}
//main.cpp
#include "httprouter.hpp"
#include <iostream>
void hello1() {
std::cout << "hello1" << std::endl;
}
void hello2() {
std::cout << "hello2" << std::endl;
}
void hello3() {
std::cout << "hello3" << std::endl;
}
void hello4() {
std::cout << "hello4" << std::endl;
}
void hello5() {
std::cout << "hello5" << std::endl;
}
int main() {
httprouter::node no;
no.addRoute("/", hello1);
no.addRoute("/serach/", hello2);
no.addRoute("/support/", hello3);
no.addRoute("/blog/", hello4);
no.handler("/")();
no.handler("/serach/")();
no.handler("/support/")();
no.handler("/blog/")();
}
結果:
節點信息: