路由功能是web框架中乙個很重要的功能,它將不同的請求**給不同的函式(handler)處理,很容易能想到,我們可以用乙個字典儲存它們之間的對應關係,字典的key存放path,value存放handler。當乙個請求過來後,使用routers.get(path, none)就可以找到對應的handler。
利用字典實現路由可以參考我的這篇文章:動手實現web框架 。
使用字典有乙個問題,不支援動態路由。如果路由像這樣呢?
/hello/:name/profile
name前面是萬用字元:,表示這是個動態的值。乙個解決辦法是使用字首樹trie。
leetcode中有這個演算法,點這裡 檢視。字首樹字首樹,首先是一棵樹。不同的是樹中乙個節點的所有子孫都有相同的字首。字首樹將單詞中的每個字母依次插入樹中,插入前首先確認該單詞是否存在,不存在才建立新節點,如果乙個單詞已經全部插入,則將末尾單詞設定為標誌位。
type node struct
type trie struct
以單詞leetcode,leetd和code為例,首先一次插入leetcode中的每個單詞,然後插入leetd的時候,leet在樹中已經存在,跳過往下,現在要插入字母d,不存在,所以新建節點插入樹中,並將該節點的isword置位true,表明到了單詞末尾。
最終插入結果為:
}那麼,當我們要搜尋單詞leetd的時候,從根節點開始查詢,如果找到某條路徑是leetd,並且末尾的d是單詞標誌位,則表示搜尋成功。
func (this *trie) search(word string) bool
cur = cur.next[c]
}return cur.isword
}
明白了字首樹的原理,我們來看看路由匹配是如何利用字首樹來實現的。
go語言中gin框架的路由實現就是利用字首樹,可以看看它的源**是如何實現的。
考慮一下,路由或者說路徑的特點,是以/分隔的單詞組成的,那我們將/的每一部分掛靠在字首樹上就可以了。如下圖所示:
還有一點需要考慮,我們在用web框架定義路由的時候,常見的做法是根據不同的http方法來定義。比如:
// 以go語言gin框架為例
g := gin.new()
g.get("/hello", hello)
g.post("/form", form)
對於同乙個路徑,可能有多個方法支援。所以我們要以不同http方法為樹根建立字首樹。當乙個get請求過來的時候,就從get樹上搜尋,post請求就從post樹上搜尋。
除了為不同的http方法定義樹之外,還要給那些是萬用字元的節點增加乙個標誌位。所以,我們的路由字首樹結構看起來像這樣:
type node struct
type router struct
依照上面的字首樹演算法的實現,照葫蘆畫瓢,我們可以寫出插入路由和搜尋路由的方法:
// addroute 繫結路由到handler
func (r *router) addroute(method, path string, handler handlerfunc)
}root := r.root[method]
key := method + "-" + path
// 將parts插入到路由樹
for _, part := range parts
}root = root.children[part]
}root.path = path
// 繫結路由和handler
r.route[key] = handler
}// getroute 獲取路由樹節點以及路由變數
func (r *router) getroute(method, path string) (node *node, params map[string]string)
searchparts := parsepath(path)
// get method trie
var ok bool
if node, ok = r.root[method]; !ok
// 在該方法的路由樹上查詢該路徑
for i, part := range searchparts
if child.part[0] == ':'
temp = child.part}}
// 遇到萬用字元*,直接返回
if temp[0] == '*'
node = node.children[temp]
}return
}
上面的**是我自己實現的乙個web框架 gaga 中路由字首樹相關的**,有需要的可以去看看源**。另外,歡迎star呀。
其中的addroute用來將路由插入到對應method的路由樹中,如果節點是萬用字元,將其設定為iswild, 同時繫結路由和handler方法。
getroute方法首先查詢路由方法對應的路由字首樹,然後在樹中查詢是否存在該路徑。
字首樹trie演算法不光可以用在路由的實現上,搜尋引擎中自動補全的實現,拼寫檢查等等都是用trie實現的。trie樹查詢的時間和空間複雜度都是線性的,效率很高,很適合路由這種場景使用。
路由的實現上,go語言中httprouter這個庫除了使用字首樹之外,還加入了優先順序,有興趣的可以看看它的原始碼了解下。
參考:
KMP演算法原理解析
這種演算法不太容易理解,網上有很多解釋,但讀起來都很費勁。直到讀到jake boxer的文章,我才真正理解這種演算法。下面,我用自己的語言,試圖寫一篇比較好懂的kmp演算法解釋。首先,字串 bbc abcdab abcdabcdabde 的第乙個字元與搜尋詞 abcdabd 的第乙個字元,進行比較。...
EM演算法原理解析
em演算法,即最大期望演算法 expectation maximization algorithm,又譯期望最大化演算法 是一種迭代演算法,用於含有隱變數 latent variable 的概率引數模型的最大似然估計或極大後驗概率估計。em演算法應用於高斯混合模型 gmm 聚類 隱式馬爾科夫演算法 ...
KMP 演算法原理解析與 C 實現
kmp 演算法包含兩個部分 利用 pattern 構建 next 陣列 利用 next 資料去匹配長字串 使用 kmp 演算法的好處是,對於被匹配的長字串,我們始終不需要往回走。next 陣列中的每乙個元素,比如說 next i 代表的是在 pattern 中第 i 個位置之前存在的字首和字尾相等的...