gin通过radix tree实现毫秒级路由匹配:将路径按/切分后构建压缩前缀树,/user/:id和/user/list共享user节点,匹配时仅沿树逐段跳转且惰性提取参数,静态、参数、通配三类路径在节点上分别以static、param、catchall类型存储。

想搞懂Gin为什么能毫秒级匹配上万条路由,就得看清它底层那棵Radix Tree——不是靠遍历、不拼字符串、不反射调用,而是用压缩前缀和惰性提取把路径拆解成树上的一条确定路径。
Radix Tree怎么把/user/:id和/user/list压进同一棵树
注册r.GET("/user/:id", handlerA)和r.GET("/user/list", handlerB)时,Gin不会为每条路由单独存一个字符串,而是把路径按/切分后逐段塞进一棵树:先建根节点,再建user子节点;接着在user下并列生成两个子节点——:id(标记wildChild=true、paramName="id")和list(普通静态节点)。【/user是公共前缀,必须合并到同一父节点下,否则失去O(k)查找意义】
这一步操作起来很简单,直接调用r.GET()就行,框架自动完成路径解析与树节点插入。
注意:如果写成/user/:id/profile和/user/list/profile,树深度会变成3层,但只要前两段相同,依然共享/user节点;一旦出现/user/a/b/c/d/e/f/g这种超深路径,实测超过7层后匹配耗时开始明显上升。
路由匹配时为什么不遍历所有注册路径
请求GET /user/123进来,Gin只做三件事:先定位trees["GET"]这棵树的根节点→沿path字段逐段跳转→在user节点下查indices字符串找首字符'1'→发现没有静态子节点匹配,但wildChild=true,于是命中:id节点。
整个过程不new string、不strings.Split(path, "/")、不range所有路由表。静态路径如/health走完全匹配分支,时间复杂度O(1);参数路径如/user/:id只在节点打标isParam,参数值123等到c.Param("id")被调用时才从URL里截取——这就是惰性提取。
你写r.GET("/user/*filepath", handler)时,通配符会单独挂为子树,不影响/user/:id这类常规路由的匹配效率。
三种路径类型在节点上的存储差异
方法一:静态路径(如/login)
对应node.path = "login",nType = static,children为空或仅含其他静态子节点,handlers直接指向处理函数。
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
方法二:参数路径(如/user/:id)
node.path = ":id",nType = param,paramName = "id",wildChild = false,但该节点必有handlers;它的父节点(user)indices里会包含':'字符索引。
方法三:通配路径(如/assets/*filepath)
node.path = "*",nType = catchAll,wildChild = true,paramName = "filepath";该节点必须是叶子节点,不能再挂子节点——【若在*节点下再注册子路由,Gin会panic】
路由树构建全过程拆解
第一步:调用r.GET("/v1/users/:uid", handler) → 触发group.handle("GET", "/v1/users/:uid", handler)
第二步:absolutePath = group.calculateAbsolutePath("/v1/users/:uid") → 拼出完整路径/v1/users/:uid
第三步:engine.addRoute("GET", "/v1/users/:uid", handlers) → 获取trees["GET"]根节点,若为空则new(node)
第四步:root.addRoute("/v1/users/:uid", handlers) → 把路径切分为[]string{"v1","users",":uid"},逐段向下创建或复用节点
第五步:走到最后一段":uid"时,设置node.handlers = handlers,并标记nType=param、paramName="uid"










