1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00

update router

This commit is contained in:
Xargin 2018-01-07 16:10:36 +08:00
parent caef780949
commit 7cc752e8b7
2 changed files with 28 additions and 9 deletions

View File

@ -91,13 +91,13 @@ httprouter 和众多衍生 router 使用的数据结构被称为 radix tree
![trie tree](../images/ch6-02-trie.png) ![trie tree](../images/ch6-02-trie.png)
字典树常用来进行字符串检索,例如用给定的字符串序列建立字典树。对于目标字符串,只要从根点开始深度优先搜索,即可判断出该字符串是否曾经出现过,时间复杂度为 O(n)n 可以认为是目标字符串的长度。为什么要这样做?字符串本身不像数值类型可以进行数值比较,两个字符串对比的时间复杂度取决于字符串长度。如果不用字典树来完成上述功能,要对历史字符串进行排序,再利用二分查找之类的算法去搜索,时间复杂度只高不低。可认为字典树是一种空间换时间的典型做法。 字典树常用来进行字符串检索,例如用给定的字符串序列建立字典树。对于目标字符串,只要从根点开始深度优先搜索,即可判断出该字符串是否曾经出现过,时间复杂度为 O(n)n 可以认为是目标字符串的长度。为什么要这样做?字符串本身不像数值类型可以进行数值比较,两个字符串对比的时间复杂度取决于字符串长度。如果不用字典树来完成上述功能,要对历史字符串进行排序,再利用二分查找之类的算法去搜索,时间复杂度只高不低。可认为字典树是一种空间换时间的典型做法。
普通的字典树有一个比较明显的缺点,就是每个字母都需要建立一个孩子点,这样会导致字典树的层树比较深,压缩字典树相对好地平衡了字典树的优点和缺点。下图是典型的压缩字典树结构: 普通的字典树有一个比较明显的缺点,就是每个字母都需要建立一个孩子点,这样会导致字典树的层树比较深,压缩字典树相对好地平衡了字典树的优点和缺点。下图是典型的压缩字典树结构:
![radix tree](../images/ch6-02-radix.png) ![radix tree](../images/ch6-02-radix.png)
每个点上不只存储一个字母了,这也是压缩字典树中“压缩”的主要含义。使用压缩字典树可以减少树的层数,同时因为每个结点上数据存储也比通常的字典树要多,所以程序的局部性较好(一个结点的 path 加载到 cache 即可进行多个字符的对比),从而对 CPU 缓存友好。 每个点上不只存储一个字母了,这也是压缩字典树中“压缩”的主要含义。使用压缩字典树可以减少树的层数,同时因为每个节点上数据存储也比通常的字典树要多,所以程序的局部性较好(一个节点的 path 加载到 cache 即可进行多个字符的对比),从而对 CPU 缓存友好。
## 压缩字典树创建过程 ## 压缩字典树创建过程
我们来跟踪一下 httprouter 中,一个典型的压缩字典树的创建过程,路由设定如下: 我们来跟踪一下 httprouter 中,一个典型的压缩字典树的创建过程,路由设定如下:
@ -115,7 +115,7 @@ GET /repos/:owner/:repo/issues/events
``` ```
这些 API 均来自于 api.github.com。 这些 API 均来自于 api.github.com。
### root 点创建 ### root 点创建
httprouter 的 Router struct 中存储压缩字典树使用的是下述数据结构: httprouter 的 Router struct 中存储压缩字典树使用的是下述数据结构:
```go ```go
// 略去了其它部分的 Router struct // 略去了其它部分的 Router struct
@ -140,17 +140,36 @@ DELETE
每一种 method 对应的都是一棵独立的压缩字典树这些树彼此之间不共享数据。具体到我们上面用到的路由PUT 和 GET 是两棵树而非一棵。 每一种 method 对应的都是一棵独立的压缩字典树这些树彼此之间不共享数据。具体到我们上面用到的路由PUT 和 GET 是两棵树而非一棵。
简单来讲,某个 method 第一次插入的路由就会导致对应字典树的根点被创建,我们按顺序,先是一个 PUT 简单来讲,某个 method 第一次插入的路由就会导致对应字典树的根点被创建,我们按顺序,先是一个 PUT
```go ```go
r := httprouter.New() r := httprouter.New()
r.PUT("/user/installations/:installation_id/repositories/:reposit", Hello) r.PUT("/user/installations/:installation_id/repositories/:reposit", Hello)
``` ```
这样 PUT 对应的根结点就会被创建出来。把这棵 PUT 的树画出来:
### 子结点插入 这样 PUT 对应的根节点就会被创建出来。把这棵 PUT 的树画出来:
TODO ![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` 时,
### 子节点冲突处理
### 边分裂 ### 边分裂

BIN
images/ch6-02-radix-put.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB