diff --git a/ch6-web/ch6-02-router.md b/ch6-web/ch6-02-router.md index 0a2591f..e562d48 100644 --- a/ch6-web/ch6-02-router.md +++ b/ch6-web/ch6-02-router.md @@ -91,13 +91,13 @@ httprouter 和众多衍生 router 使用的数据结构被称为 radix tree, ![trie tree](../images/ch6-02-trie.png) -字典树常用来进行字符串检索,例如用给定的字符串序列建立字典树。对于目标字符串,只要从根结点开始深度优先搜索,即可判断出该字符串是否曾经出现过,时间复杂度为 O(n),n 可以认为是目标字符串的长度。为什么要这样做?字符串本身不像数值类型可以进行数值比较,两个字符串对比的时间复杂度取决于字符串长度。如果不用字典树来完成上述功能,要对历史字符串进行排序,再利用二分查找之类的算法去搜索,时间复杂度只高不低。可认为字典树是一种空间换时间的典型做法。 +字典树常用来进行字符串检索,例如用给定的字符串序列建立字典树。对于目标字符串,只要从根节点开始深度优先搜索,即可判断出该字符串是否曾经出现过,时间复杂度为 O(n),n 可以认为是目标字符串的长度。为什么要这样做?字符串本身不像数值类型可以进行数值比较,两个字符串对比的时间复杂度取决于字符串长度。如果不用字典树来完成上述功能,要对历史字符串进行排序,再利用二分查找之类的算法去搜索,时间复杂度只高不低。可认为字典树是一种空间换时间的典型做法。 -普通的字典树有一个比较明显的缺点,就是每个字母都需要建立一个孩子结点,这样会导致字典树的层树比较深,压缩字典树相对好地平衡了字典树的优点和缺点。下图是典型的压缩字典树结构: +普通的字典树有一个比较明显的缺点,就是每个字母都需要建立一个孩子节点,这样会导致字典树的层树比较深,压缩字典树相对好地平衡了字典树的优点和缺点。下图是典型的压缩字典树结构: ![radix tree](../images/ch6-02-radix.png) -每个结点上不只存储一个字母了,这也是压缩字典树中“压缩”的主要含义。使用压缩字典树可以减少树的层数,同时因为每个结点上数据存储也比通常的字典树要多,所以程序的局部性较好(一个结点的 path 加载到 cache 即可进行多个字符的对比),从而对 CPU 缓存友好。 +每个节点上不只存储一个字母了,这也是压缩字典树中“压缩”的主要含义。使用压缩字典树可以减少树的层数,同时因为每个节点上数据存储也比通常的字典树要多,所以程序的局部性较好(一个节点的 path 加载到 cache 即可进行多个字符的对比),从而对 CPU 缓存友好。 ## 压缩字典树创建过程 我们来跟踪一下 httprouter 中,一个典型的压缩字典树的创建过程,路由设定如下: @@ -115,7 +115,7 @@ GET /repos/:owner/:repo/issues/events ``` 这些 API 均来自于 api.github.com。 -### root 结点创建 +### root 节点创建 httprouter 的 Router struct 中存储压缩字典树使用的是下述数据结构: ```go // 略去了其它部分的 Router struct @@ -140,17 +140,36 @@ DELETE 每一种 method 对应的都是一棵独立的压缩字典树,这些树彼此之间不共享数据。具体到我们上面用到的路由,PUT 和 GET 是两棵树而非一棵。 -简单来讲,某个 method 第一次插入的路由就会导致对应字典树的根结点被创建,我们按顺序,先是一个 PUT: +简单来讲,某个 method 第一次插入的路由就会导致对应字典树的根节点被创建,我们按顺序,先是一个 PUT: ```go r := httprouter.New() r.PUT("/user/installations/:installation_id/repositories/:reposit", Hello) ``` -这样 PUT 对应的根结点就会被创建出来。把这棵 PUT 的树画出来: -### 子结点插入 -TODO +这样 PUT 对应的根节点就会被创建出来。把这棵 PUT 的树画出来: +![put radix tree](../images/ch6-02-radix-put.png) -### 子结点冲突处理 +radix 的节点类型为 `*httprouter.node`,为了说明方便,我们留下了目前关心的几个字段: + +``` +path: 当前节点对应的路径中的字符串 + +wildCard: 子节点是否为参数节点,即 wildCard node,或者说 :id 这种类型的节点 + +nType: 当前节点类型,有四个枚举值: 分别为 static/root/param/catchAll。 + static // 非根节点的普通字符串节点 + root // 根节点 + param // 参数节点,例如 :id + catchAll // 通配符节点,例如 *anyway + +indices: 子节点索引,当子节点为非参数类型,即本节点的 wildCard 为 false 时,会将每个子节点的首字母放在该索引数组。说是数组,实际上是个 string。 +``` +当然,PUT 路由只有唯一的一条路径。接下来,我们以后续的多条 GET 路径为例,讲解子节点的插入过程。 + +### 子节点插入 +当插入 `GET /search` 时, + +### 子节点冲突处理 ### 边分裂 diff --git a/images/ch6-02-radix-put.png b/images/ch6-02-radix-put.png new file mode 100644 index 0000000..541c744 Binary files /dev/null and b/images/ch6-02-radix-put.png differ