前缀树——以Gin路由为例
前缀树是父节点是子节点前缀的N叉树。
其主要性质是
- 根节点不包括字符
- 每个节点的子节点字符不同
- 节点对应的字符串为从根节点到该节点路径上字符的组合
在gin中也存在着非常巧妙运用前缀树进行路由匹配的结构,本文将以gin路由为例学习一下前缀树
本文代码皆是参考gin@v1.7.4版本源码所构建的更适合理解改造版,并且省略了诸如标记是否是通配符的node wildChild等属性和方法
Gin前缀树结构
gin为每个方法都创建了前缀树。每个前缀树包含了根节点,以及方法名。
每个节点包含了
- 当前路径
- 全路径:也就是根节点到该节点的路径组成的字符串
- 优先级:子节点越多优先级越高
- 子节点:
- 子节点首字母组成的字符串:用于快速确认新增或者查询的路径是否存在了
type engine struct {trees methodsTrees
}type methodsTrees []methodTreetype methodTree struct {method stringroot *node
}type node struct {// 当前节点pathpath string// 子节点首字符组成的stringindices string// 按照优先级排序的子节点children []*node// 优先级priority uint32// 根节点到该节点的字符串fullPath string// 处理器handlers []Handler
}// 遍历树,若存在对应方法所在的树就返回
func (t methodsTrees) get(method string) *node {for _, tree := range t {if tree.method == method {return tree.root}}return nil
}
插入
前缀树插入过程如下
- 若根节点为空,直接设置根节点路径为新增路径,并返回
- 若当前节点路径与新增路径不存在公共前缀,那么就使当前节点的所有子节点作为当前节点的其中一个子节点,新增路径为另一个子节点
- 若新增路径的非公共前缀首字符在当前节点的子节点存在,那么就将该新增路径插入到该子节点,并返回
- 在新增路径与节点路径完全匹配的时候,覆盖当前节点处理器和路径
func (n *node) addRoute(path string, handlers []Handler) {fullPath := pathn.priority++// 若为空节点,则直接插入子节点if len(n.path) == 0 && len(n.children) == 0 {n.path = pathn.fullPath = fullPathn.handlers = handlersreturn}// 根据路径插入到节点n.addChild(path, fullPath, 0, handlers)
}func (n *node) addChild(path, fullPath string, parentFullPathIndex int, handlers []Handler) {// 找到新增路径与节点路径的最长公共前缀i := longestCommonPrefix(path, n.path)// 节点的path不是最长公共前缀,分裂原子节点和新增节点if i < len(n.path) {child := &node{path: n.path[i:],indices: n.indices,children: n.children,priority: n.priority - 1,fullPath: n.fullPath,}n.children = []*node{child}n.indices = string(n.path[i])n.path = path[:i]n.fullPath = fullPath[:parentFullPathIndex+i]}// 在新增路径存在公共前缀时,添加含有该路径的子节点if i < len(path) {path = path[i:]c := path[0]// 若该路径的非公共前缀首字符在节点children已经存在,那么就将该节点添加到子节点中for j, max := 0, len(n.indices); j < max; j++ {if c == n.indices[j] {parentFullPathIndex += len(n.path)j = n.incrementChildPriority(j)n.children[j].addChild(path, fullPath, parentFullPathIndex, handlers)return}}// 若该路径的非公共前缀首字符在节点children不存在,就直接添加为节点的子节点n.indices += string(c)child := &node{fullPath: fullPath, path: path, handlers: handlers}n.children = append(n.children, child)n.incrementChildPriority(len(n.indices) - 1)return}// 路径与节点路径完全匹配时,直接覆盖原全路径和处理器n.handlers = handlersn.fullPath = fullPathreturn
}// 增加子节点的优先级
func (n *node) incrementChildPriority(pos int) int {// 增加该子节点的优先级cs := n.childrencs[pos].priority++prio := cs[pos].priority// 重新根据优先级排序newPos := posfor ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]}// 重新组合indicesif newPos != pos {n.indices = n.indices[:newPos] + n.indices[pos:pos+1] + n.indices[newPos:pos] + n.indices[pos+1:]}return newPos
}func (e *engine) addRoute(method, path string, handlers []Handler) {// 1. 根据方法获取对应的前缀树。若root为nil,则初始化root := e.trees.get(method)if root == nil {root = &node{fullPath: "/",}e.trees = append(e.trees, methodTree{method: method,root: root,})}// 2. 添加路径到前缀树root.addRoute(path, handlers)
}func longestCommonPrefix(a, b string) int {i := 0max := minInt(len(a), len(b))for i < max && a[i] == b[i] {i++}return i
}func minInt(x, y int) int {if x < y {return x}return y
}
匹配
标记为tsr就是在为true情况下并且可以Redirect Trailing Slash,那么就可以重定向到原url附加’/'的地址
前缀树匹配过程如下
- 若路径不完全匹配当前节点路径,尝试找到匹配的子节点,并在子节点中继续匹配;若无法找到匹配子节点且无法匹配的字符为’/',那就标记tsr并返回
- 若路径完全匹配当前节点路径。在处理器有效的情况下,就返回匹配到的处理器。若无效就尝试找到仅含有’/'路径的子节点,标记为tsr并返回
- 若路径完全不匹配当前节点路径,返回空
type nodeValue struct {handlers []Handlertsr bool
}func (n *node) getValue(path string) *nodeValue {prefix := n.path// 若路径不完全匹配当前节点路径if len(path) > len(prefix) {// 若路径存在该前缀if path[:len(prefix)] == prefix {path = path[len(prefix):]// 尝试找到匹配该路径的子节点idxc := path[0]for i := 0; i < len(n.indices); i++ {if idxc == n.indices[i] {n.children[i].getValue(path)}}// 若无法找到匹配子节点且无法匹配的路径为'/',那么就标记tsrreturn &nodeValue{tsr: path == "/" && n.handlers != nil,}}}// 路径与节点路径完全匹配if path == prefix {// 若节点处理器存在,则返回if n.handlers != nil {return &nodeValue{handlers: n.handlers,}}for i := 0; i < len(n.indices); i++ {// 若存在'/'路径的子节点,则标记tsrif n.indices[i] == '/' {child := n.children[i]return &nodeValue{tsr: len(child.path) == 1 && child.handlers != nil,}}}}return nil
}
前缀树——以Gin路由为例相关推荐
- Seq2Seq+前缀树:检索任务新范式(以KgCLUE为例)
©PaperWeekly 原创 · 作者 |苏剑林 单位 |追一科技 研究方向 |NLP.神经网络 两年前,在<万能的seq2seq:基于seq2seq的阅读理解问答>和<" ...
- golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正原理
文章目录 全系列总结博客链接 前引 golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正远原理 1.再列示例代码 从示例代码入手 2.r.Run ...
- Trie(前缀树/字典树)及其应用
from:https://www.cnblogs.com/justinh/p/7716421.html Trie,又经常叫前缀树,字典树等等.它有很多变种,如后缀树,Radix Tree/Trie,P ...
- Java 实现 Trie (前缀树)
LeetCode:https://leetcode-cn.com/problems/implement-trie-prefix-tree/ 什么是前缀树 Trie(发音类似 "try&quo ...
- trie树查找前缀串_Trie数据结构(前缀树)
trie树查找前缀串 by Julia Geist Julia·盖斯特(Julia Geist) A Trie, (also known as a prefix tree) is a special ...
- Leetcode 208:实现Trie(前缀树)
问题描述: 思路简述: 借用一张图 前缀树又叫做单词查找树,是一种树形数据结构,可以用于存储大量的字符串,优点是可以利用不同字符串的公共前缀来节省大量的存储空间,避免了顺序存储字符串列表的冗余,可以看 ...
- 【LeetCode】720. 词典中最长的单词 【前缀树】
题目链接:https://leetcode-cn.com/problems/longest-word-in-dictionary/ 题目描述 给出一个字符串数组words组成的一本英语词典.从中找出最 ...
- golang学习笔记(19)-gin路由分组和中间件
gin路由分组和中间件 这里写目录标题 gin路由分组和中间件 路由分组 中间件 使用中间件 创建中间件 简单中间件应用实验 路由分组 路由分组可以使路由结构更加清晰,更加方便管理路由. 官方演示代码 ...
- 算法补天系列之——前缀树+贪心算法
介绍前缀树 经典的前缀树,字符都是在路上的,节点是为了边的存在而存在的,有路就可以复用. 那么前缀树对应的数据结构是什么样的呢? 对于节点构建结构,设置一个int pass(表示路径途径这个节点的次数 ...
最新文章
- 程序员网购18年不拆快递!意外离世后满屋快递被拆开,价值3500万!
- linux系统进程的内存布局
- pytorch随笔-6
- 程序设计囚犯与灯泡 C语言代码,100个囚犯和灯泡的那些事儿(下)
- 经典面试题(41):以下代码将输出的结果是什么?
- nginx1.8.1反向代理、负载均衡功能的实现
- 美国的时间格式 -- 复杂格式的日期转换 使用Date.parse(“复杂的格式”)方法。
- C++ 练习题(一:布尔表达式与真值表图文详解)
- Yum离线安装(一) --- RPM 打包技术与典型 SPEC 文件分析
- 12.15 小程序验证码点击刷新
- qt 导出word中插入图片
- 当前位置 计算机英语,计算机常用英语词汇,计算机常用英语词汇
- Java编程到底是用idea好还是eclipse好?
- thingworx学习
- C语言实现简单的线程池【转】
- Retina、非Retina、点、像素、iPhone分辨率
- 如何用ssh隧道绕过防火墙
- git 撤销未提交的修改
- 保研华东师范计算机专硕,华东师范大学2020年硕士生推免生缺口很大,考研的学子机会来了...
- swust oj 996 打印杨辉三角形
热门文章
- cesium 相机跟随
- 达梦数据库key文件更换
- 2022高频面试题之css篇
- php zen kaku代表什么,Convert kana one from another (zen-kaku, han-kaku and more) - PHP 7 中文文档...
- win8系统保护服务器,Win8整合SmartScreen升级功能保护系统安全
- React中的SVG陷阱
- 使用吉特哈布Actions对C++代码进行分析
- IDEA 启动tomcat 或者springboot 卡住问题
- 计算机视觉基础知识复习
- 无法定位程序输入点_except1于动态链接库