什么是Radix Tree
在计算机科学中,基数树,或称Patricia trie/tree,或crit bit tree,压缩前缀树,是一种更节省空间的Trie(前缀树)。对于基数树的每个节点,如果该节点是唯一的子树的话,就和父节点合并。
golang的web框架echo和gin都使用了radix tree
作为路由查找的算法,我们以gin的实现来分析一下。
在gin的路由中,每一个Http Method
(GET, PUT, POST…)都对应了一棵 radix tree
|
|
radix tree
可以被认为是一棵简洁版的前缀树。拥有共同前缀的节点也共享同一个父节点。下面是一个GET
方法对应的路由树的结构:
|
|
*<num>
是方法(handler)对应的指针,从根节点遍历到叶子节点我们就能得到完整的路由表,图中的示例实现了以下路由:
|
|
:post
是真实的post name
的一个占位符(就是一个参数)。这里体现了radix tree相较于hash-map的一个优点,树结构允许我们的路径中存在动态的部分(参数),因为我们匹配的是路由的模式而不是hash值
为了更具扩展性,每一层的节点按照priority排序,priority是节点的子节点(儿子节点,孙子节点等)注册的handler的数量,这样做有两个好处:
- 被最多路径包含的节点会被最先评估。这样可以让尽量多的路由快速被定位。
- 有点像成本补偿。最长的路径可以被最先评估,补偿体现在最长的路径需要花费更长的时间来定位,如果最长路径的节点能被优先评估(即每次拿子节点都命中),那么所花时间不一定比短路径的路由长。下面展示了节点(每个
-
可以看做一个节点)评估的路径:从左到右,从上到下
|
|
节点数据结构
节点的数据结构如下:
|
|
添加路由
|
|
此函数的主要目的是找到插入节点的位置,如果和现有节点存在相同的前缀,那么要将现有节点进行分裂,然后再插入,下面是insertChild
函数
插入子节点
|
|
insertChild
函数是根据path
本身进行分割, 将/
分开的部分分别作为节点保存, 形成一棵树结构. 注意参数匹配中的:
和*
的区别, 前者是匹配一个字段, 后者是匹配后面所有的路径
路径查找
匹配每个children的path,最长匹配
|
|
之前总听大家说数据结构与算法有什么用,工作中又用不到,上面就是一个很好的示例。我们平时还是要多关注底层原理,做后端的同学多看看框架的代码,一定受益匪浅~