
为什么不用 std::map 或 trie 而选基数树?
因为路由匹配要支持前缀通配(如 /api/v1/users/{id})和最长前缀匹配,std::map 只能做精确或字典序查找,不支持路径段级跳转;普通 trie 节点太多、内存碎片高,而基数树把连续单分支压缩成边标签,查一次 /api/v1 可能只需 2–3 次内存访问,不是 6 次。
典型坑是误把「路径分段」当「字符流」处理:基数树节点该按 / 切分后的字符串(如 "api"、"v1")建边,不是按 ASCII 字符逐个插。否则会退化成普通 trie,还更慢。
如何构造带通配符的基数树节点?
核心是区分三类边:普通路径段("users")、命名参数(":id")、通配符("*")。不能全用字符串比较,得在节点里存一个标记字段,比如 is_wildcard 和 is_param,并在匹配时按优先级处理:精确匹配 > 命名参数 > 通配符。
- 插入
/api/v1/users/:id时,":id"边对应节点设is_param = true,并记下参数名"id" - 插入
/api/v1/users/*时,"*"边单独存在,且必须保证它不与":id"冲突——即同一层不能既有":id"又有"*",否则语义模糊 - 查询
/api/v1/users/123时,先走"api"→"v1"→"users",再对"123"尝试匹配":id"边(成功),不尝试"*"边(除非没其他选择)
立即学习“C++免费学习笔记(深入)”;
match() 函数怎么写才不漏匹配、不超时?
关键在递归回溯控制:基数树匹配本质是 DFS,但路由场景下不允许暴力试所有分支。必须提前剪枝——一旦某条路径走到叶子且完整消耗了请求路径,就立即返回,不继续搜其他分支;若中途某步既不精确匹配、参数也不兼容(比如 ":id" 要求数字但来了 "abc"),直接跳过该子树。
- 输入路径先用
std::vector<:string></:string>存分段结果(避免反复split()) - 递归函数签名建议为
bool match(Node* node, const std::vector<:string>& parts, size_t idx, std::unordered_map<:string std::string>& params)</:string></:string> - 每次只比对当前
parts[idx]和当前节点的所有出边:先试精确边,再试is_param边(无条件接受),最后试is_wildcard边(仅当idx是最后一段时才允许) - 别忘了:空路径(
"")要对应根节点的is_handler标志,即GET /的处理逻辑
实际部署时最常崩在哪?
不是算法错,是生命周期管理失控。C++ 里树节点通常用裸指针或 std::unique_ptr 管理,但路由树往往全局单例,又频繁增删(比如热更新 API 定义)。常见崩溃点:match() 正在遍历,另一线程调用 insert() 修改了子节点指针,导致野指针访问。
- 简单方案:读多写少就用
std::shared_mutex,读路径加共享锁,写操作加独占锁 - 激进方案:构建新树 + 原子指针交换(
std::atomic_load/store),旧树延迟释放(需引用计数或 RCU 风格) - 千万别用
std::mutex包裹整个match()—— 单次匹配本应是 O(log N),锁住就变串行瓶颈
还有个隐形坑:路径中混用斜杠风格,比如 /api//v1 或 /api/v1/,不标准化就可能匹配失败。插入前务必 normalize:去重斜杠、删末尾斜杠(除非你明确支持 GET /api/ 和 GET /api 视为不同路由)。











