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

    应对大量的路由匹配, radix 树可以提高你应对大量匹配的能力。


    我们以 gin 的路由匹配我例:
    image.pngimage.png

    CODE
    注:以下代码只实现了一个简陋的radix,包含了该算法的核心思想

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. type Handlers []func()
    6. type node struct {
    7. // 保存这个节点上的URL路径
    8. path string
    9. // 和children[]对应, 保存的是分裂的分支的第一个字符
    10. // 例如search和support, 那么s节点的indices对应的"eu"
    11. // 代表有两个分支, 分支的首字母分别是e和u
    12. indices string
    13. children []*node
    14. handlers Handlers
    15. }
    16. func NewNode() *node {
    17. return &node{path: "/"}
    18. }
    19. func (n *node) addRoute(path string, handlers Handlers) {
    20. if len(n.children) < 1 {
    21. n.insertChild(path, handlers, 0)
    22. return
    23. }
    24. loop:
    25. for {
    26. //找到前缀相等的部分
    27. i := 0
    28. max := min(len(path), len(n.path))
    29. for max > i && path[i] == n.path[i] {
    30. i++
    31. }
    32. //判断是否有前缀相同,如果有相同的则把目前这个节点提取出来作为子节点
    33. //再把相同前缀的path部分作为 父节点
    34. //比如n.path = romaned 现在新增路由的path = romanus 相同前缀为 roman
    35. //步骤为:
    36. //1. 提取ed 新建一个child节点 把原来n的属性都复制过去
    37. //2. 把原来的n的path改为相同前缀:roman, 为indices添加 子节点的第一个字符:e
    38. // 注意:i == 0 时,没有相同的前缀,不需要重新更改节点
    39. // n.path != "/" 这个条件可以不用,但写上更直观些
    40. if i < len(n.path) && i > 0 && n.path != "/" {
    41. //新建node
    42. child := node{
    43. path: n.path[i:],
    44. indices: n.indices,
    45. handlers: n.handlers,
    46. children: n.children,
    47. }
    48. n.children = []*node{&child}
    49. n.indices = string(n.path[i])
    50. n.path = path[:i]
    51. n.handlers = handlers
    52. }
    53. //原先的节点n现在已经分成2个节点了 结构为:
    54. //roman 父节点
    55. // ed 子节点[0]
    56. //那么现在需要把传入的路由添加到这个父节点中
    57. //最终结构为
    58. //roman 父节点
    59. // ed 子节点[0]
    60. // us 子节点[1]
    61. // 其中还有一些情况需要自调用 相当于递归 举例说明:
    62. //roman
    63. // ed
    64. // uie
    65. //当判断父节点n 本来就有一个uie子节点 这时候uie和us 又有相同前缀u 这个时候需要把这个u再次提取出来作为父节点 所以需要递归调用walk
    66. //最终结果为 三层结构
    67. //roman
    68. // ed
    69. // u
    70. // ie
    71. // s
    72. //还有一种情况是如果是带有参数的路由 则也会再次调用walk
    73. // 如果 i>=len(path),说明 这个path 被之前的path包含了,上面的逻辑已经做了处理
    74. if i < len(path) {
    75. c := path[i] // 按上面的例子,这里的c(第一个不相同的字符) 为 'u'
    76. // 按上面的注释
    77. // 最终两层时,n.indices 为 'e'
    78. // 最终三层时,n.indices 为 'eu'
    79. for index := 0; index < len(n.indices); index++ {
    80. if c == n.indices[index] {
    81. n = n.children[index]
    82. path = path[i:]
    83. continue loop
    84. }
    85. }
    86. //把新请求的path加入到router中
    87. n.insertChild(path, handlers, i)
    88. return
    89. }
    90. return
    91. }
    92. }
    93. func (n *node) insertChild(fullPath string, handlers Handlers, index int) {
    94. child := node{}
    95. child.handlers = handlers
    96. child.indices = ""
    97. child.path = fullPath[index:]
    98. n.indices += string(fullPath[index])
    99. n.children = append(n.children, &child)
    100. }
    101. func min(a, b int) int {
    102. if a > b {
    103. return b
    104. }
    105. return a
    106. }
    107. func (n *node) getValue(path string) (handlers Handlers) {
    108. index := 1
    109. walk:
    110. for {
    111. if len(path) > len(n.path) {
    112. // 这个边界要注意,因为除了根节点,其它节点不能以"/"开头
    113. if n.path != "/" {
    114. path = path[len(n.path):]
    115. }
    116. c := path[0]
    117. for i := 0; i < len(n.indices); i++ {
    118. if c == n.indices[i] {
    119. n = n.children[i]
    120. index++
    121. goto walk
    122. }
    123. }
    124. } else if path == n.path {
    125. return n.handlers
    126. } else {
    127. return nil
    128. }
    129. }
    130. }
    131. func main() {
    132. node := NewNode()
    133. node.addRoute("romaned", Handlers{func() {
    134. fmt.Println("romaned")
    135. }})
    136. node.addRoute("romanus", Handlers{func() {
    137. fmt.Println("romanus")
    138. }})
    139. node.addRoute("romanuie", Handlers{func() {
    140. fmt.Println("romanuie")
    141. }})
    142. node.getValue("romaned")[0]()
    143. node.getValue("romanus")[0]()
    144. node.getValue("romanuie")[0]()
    145. }