基数树(radix),也称压缩前缀树,前缀树的查找速度是跟 树的深度有关的。 并且从在单一分支的情况,且对单一超长的 word 或者是比较稀疏的单词的时候,存储很费力不讨好,会导致树总是只有单个分支,大大提高树的深度 和 存储空间。
应对大量的路由匹配, radix 树可以提高你应对大量匹配的能力。
我们以 gin 的路由匹配我例:

CODE
注:以下代码只实现了一个简陋的radix,包含了该算法的核心思想
package mainimport ("fmt")type Handlers []func()type node struct {// 保存这个节点上的URL路径path string// 和children[]对应, 保存的是分裂的分支的第一个字符// 例如search和support, 那么s节点的indices对应的"eu"// 代表有两个分支, 分支的首字母分别是e和uindices stringchildren []*nodehandlers Handlers}func NewNode() *node {return &node{path: "/"}}func (n *node) addRoute(path string, handlers Handlers) {if len(n.children) < 1 {n.insertChild(path, handlers, 0)return}loop:for {//找到前缀相等的部分i := 0max := min(len(path), len(n.path))for max > i && path[i] == n.path[i] {i++}//判断是否有前缀相同,如果有相同的则把目前这个节点提取出来作为子节点//再把相同前缀的path部分作为 父节点//比如n.path = romaned 现在新增路由的path = romanus 相同前缀为 roman//步骤为://1. 提取ed 新建一个child节点 把原来n的属性都复制过去//2. 把原来的n的path改为相同前缀:roman, 为indices添加 子节点的第一个字符:e// 注意:i == 0 时,没有相同的前缀,不需要重新更改节点// n.path != "/" 这个条件可以不用,但写上更直观些if i < len(n.path) && i > 0 && n.path != "/" {//新建nodechild := node{path: n.path[i:],indices: n.indices,handlers: n.handlers,children: n.children,}n.children = []*node{&child}n.indices = string(n.path[i])n.path = path[:i]n.handlers = handlers}//原先的节点n现在已经分成2个节点了 结构为://roman 父节点// ed 子节点[0]//那么现在需要把传入的路由添加到这个父节点中//最终结构为//roman 父节点// ed 子节点[0]// us 子节点[1]// 其中还有一些情况需要自调用 相当于递归 举例说明://roman// ed// uie//当判断父节点n 本来就有一个uie子节点 这时候uie和us 又有相同前缀u 这个时候需要把这个u再次提取出来作为父节点 所以需要递归调用walk//最终结果为 三层结构//roman// ed// u// ie// s//还有一种情况是如果是带有参数的路由 则也会再次调用walk// 如果 i>=len(path),说明 这个path 被之前的path包含了,上面的逻辑已经做了处理if i < len(path) {c := path[i] // 按上面的例子,这里的c(第一个不相同的字符) 为 'u'// 按上面的注释// 最终两层时,n.indices 为 'e'// 最终三层时,n.indices 为 'eu'for index := 0; index < len(n.indices); index++ {if c == n.indices[index] {n = n.children[index]path = path[i:]continue loop}}//把新请求的path加入到router中n.insertChild(path, handlers, i)return}return}}func (n *node) insertChild(fullPath string, handlers Handlers, index int) {child := node{}child.handlers = handlerschild.indices = ""child.path = fullPath[index:]n.indices += string(fullPath[index])n.children = append(n.children, &child)}func min(a, b int) int {if a > b {return b}return a}func (n *node) getValue(path string) (handlers Handlers) {index := 1walk:for {if len(path) > len(n.path) {// 这个边界要注意,因为除了根节点,其它节点不能以"/"开头if n.path != "/" {path = path[len(n.path):]}c := path[0]for i := 0; i < len(n.indices); i++ {if c == n.indices[i] {n = n.children[i]index++goto walk}}} else if path == n.path {return n.handlers} else {return nil}}}func main() {node := NewNode()node.addRoute("romaned", Handlers{func() {fmt.Println("romaned")}})node.addRoute("romanus", Handlers{func() {fmt.Println("romanus")}})node.addRoute("romanuie", Handlers{func() {fmt.Println("romanuie")}})node.getValue("romaned")[0]()node.getValue("romanus")[0]()node.getValue("romanuie")[0]()}
