go路由httprouter中的壓縮字典樹算法圖解及c++實現


go路由httprouter中的壓縮字典樹算法圖解及c++實現

@


前言

准備從嵌入式往go后端轉,今年准備學習一下gin框架,決定先從這個輕量級的路由請求器着手,本文講講它用到的壓縮字典樹算法。

httprouter簡介

HttpRouter是一個Go編寫的輕量級的高性能Http請求路由器(也可稱為多路選擇器multiplexer簡稱mux)

與Go的net/http包的默認mux不同,該路由器支持路由中的變量與請求方法進行匹配,同時具有很好的伸縮性。

該路由具有高性能的同時,也優化了內存的占用,即是是很長的路徑和大量的路徑,他都能很好的擴展,采用壓縮字典樹(基數樹)結構實現高效的匹配。

壓縮字典樹

概念

壓縮字典樹,是trie樹的一種,也稱單詞查找樹、前綴樹,善於進行字符串的檢索、取字符串最長公共前綴、以及排序,常應用在搜索引擎中例如百度輸入可能自動彈出能匹配到的單詞出來.
Alt text

壓縮tire和標准trie最大的不同點就節點的數量與插入字符串的個數成正比,而不是與字符串的長度成正比,所以當字符串數量越來越多,越密集且相似度極高的情況下,會退化成標准trie樹。

下面分別是/,/bear,/bell,/bid,/bull,/buy,/sell,/stock,/stop 的標准tire 和壓縮 tire
Alt text
Alt text

插入操作

下面圖解一串子串插入壓縮trie過程,/,/serach,/support,/blog , 在httprouter上截的一段例子,我們只插到/blog
Alt text

插入/
Alt text

插入/serach
Alt text

插入/support
Alt text

插入/blog
Alt text

查詢操作

查詢比較簡單,后面看代碼也比較快。
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/")();

}

結果:
Alt text

節點信息:
Alt text


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM