@ios
準備從嵌入式往go後端轉,今年準備學習一下gin框架,決定先從這個輕量級的路由請求器着手,本文講講它用到的壓縮字典樹算法。c++
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
查詢比較簡單,後面看代碼也比較快。
一、先找共同前綴。
二、再找目錄。
三、循環上面兩步,知道當前path相等。
這裏註冊了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/")(); }
結果:
節點信息: