golang学习笔记03——gin框架的核心数据结构

news2024/9/21 20:53:14

文章目录

    • 1.核心数据结构
      • 1.1 gin.Context
      • 1.2 前缀树
        • (1)前缀树
        • (2)压缩前缀树
        • (3)代码实现

上期文章我们讲到了golang中gin框架的基本原理和底层请求、渲染的流程,还不知道的小伙伴查看golang学习笔记02——gin框架及基本原理。

那么,本章节我们来更进一步,深入学习gin框架的核心数据结构gin.Context的讲解前缀树压缩前缀树代码实现

1.核心数据结构

1.1 gin.Context

gin.Context是我们基于gin框架业务开发时最常接触到的结构。

该结构是一个context.Context实现,因此可以将该结构传递到所有接收context.Context的方法或函数中。

type Context struct {
    writermem responseWriter
    Request   *http.Request  // http请求
    Writer    ResponseWriter // http响应输出流

    Params   Params // URL路径参数
    handlers HandlersChain   // 处理器链
    index    int8 // 当前的处理进度,即处理链路处于函数链的索引位置
    fullPath string

    engine       *Engine
  ...
    mu sync.RWMutex // 用于保护 map 的读写互斥锁

    // 提供对外暴露的 Get 和 Set 接口向用户提供了共享数据的存取服务,相关操作都在读写锁的保护之下,能够保证并发安全
    Keys map[string]any // 缓存 handlers 链上共享数据的 map,由于使用的map,避免了设置多个值时context形成链表

  ...
    queryCache url.Values // 查询参数缓存,使用时调用`Request.URL.Query()`,该方法每次都会对原始的查询字符串进行解析,所以这里设置缓存避免冗余的解析操作

    formCache url.Values // 表单参数缓存,作用同上
  ...
}

由于封装了http.Request和ResponseWriter(内部是http.ResponseWriter)对象,因此可以通过context对http请求响应进行操作。

context中还封装了处理器链HandlersChain和当前处理位置索引,因此可以很方便地访问处理器。

另外,我们知道,context能够以链表形式存储值(也就是说每个k-v会对应一个context,这些context之间之间以链表形式连接),当存在大量值时,访问效率比较低。因此gin.context在内部有一个map[string]any结构专门用于保存这些值,并且提供了线程安全访问方法。

func (c *Context) Set(key string, value any) {
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.Keys == nil {
        c.Keys = make(map[string]any)
    }

    c.Keys[key] = value
}

// Get returns the value for the given key, ie: (value, true).
// If the value does not exist it returns (nil, false)
func (c *Context) Get(key string) (value any, exists bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    value, exists = c.Keys[key]
    return
}

针对需要用到表单参数和查询字符串参数的场景,gin.Context进行了优化,设计了两个缓存结构(即queryCache和formCache)来提高重复访问时的效率。以表单参数为例:

func (c *Context) PostForm(key string) (value string) {
    value, _ = c.GetPostForm(key)
    return
}

func (c *Context) GetPostForm(key string) (string, bool) {
    if values, ok := c.GetPostFormArray(key); ok {
        return values[0], ok
    }
    return "", false
}

func (c *Context) PostFormArray(key string) (values []string) {
    values, _ = c.GetPostFormArray(key)
    return
}

func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
    c.initFormCache()
    values, ok = c.formCache[key]
    return
}

func (c *Context) initFormCache() {
    if c.formCache == nil {
        c.formCache = make(url.Values)
        req := c.Request
        // 从这里可以看出,如果不使用缓存,则每次都会解析请求,效率较低
        if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
            if !errors.Is(err, http.ErrNotMultipart) {
                debugPrint("error on parse multipart form array: %v", err)
            }
        }
        c.formCache = req.PostForm
    }
}

通过这样两个缓存结构,避免每次请求时都调用net/http库的方法。

1.2 前缀树

(1)前缀树

前缀树也称Trie树或字典树,是一种基于字符串公共前缀构建树形结构,来降低查询时间和提高效率的目的。前缀树一般用于统计和排序大量的字符串,其核心思想是空间换时间。

前缀树有三个重要特性:

  • 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
  • 从根节点到某一节点路径上所有字符连接起来,就是该节点对应的字符串。
  • 每个节点任意子节点包含的字符都不相同。

如下是普通前缀树的结构:
在这里插入图片描述

(2)压缩前缀树

上述前缀树实现起来比较简单,但是在空间利用上并不高效,因此有压缩前缀树。不同之处在于,压缩前缀树会对节点进行压缩,可以简单认为如果某一个节点是其父节点的唯一子节点,则会与父节点合并。

gin框架就采用的是压缩前缀树实现。

我们一般会将前缀树与哈希表结构进行对比,实际上标准库采用的就是哈希表实现。哈希表实现简单粗暴,但是有一些缺点,不太适合作为通用的路由结构。如:

  • 哈希表实现只支持简单的路径,不支持路径参数和通配
  • 路由的数量一般是有限的,使用map的优势并不明显
  • 哈希表需要存储完整的路径,相比较而言前缀树存储公共前缀只需要一个节点,空间效率更高
(3)代码实现

前面说过,gin针对每一个http请求方法,都构造了一棵前缀树,即:

type methodTree struct {
    method string
    root   *node // 该方法对应的路由树的根节点
}

其中method即http请求方法,root则是指向对应前缀树根节点的指针,node结构是前缀树的节点。

type node struct {
    path string // 节点路径(不包含父节点)
    indices string // 子节点数组中每个节点path的首字母
    wildChild bool // 是否存在通配类型的子节点
    nType nodeType // 节点类型,包括root(根节点)、static(静态节点)、catchAll(通配符*匹配的节点)、param(参数节点,即带:的节点)
    priority uint32 // 根据经过节点的路由数确定的节点优先级。同一个节点下的子节点会按照节点优先级降序排序,匹配时按序遍历children。优先级越高,越先被匹配。
    handlers HandlersChain // 处理器链
    fullPath string // 完整路径(路由树结构中根节点到当前节点的路径上的全部path的完整拼接)
}

如下是有关优先级的一部分代码:

func (n *node) incrementChildPrio(pos int) int {
    // 子节点数组
    cs := n.children
    // 增加对应的子节点的优先级
    cs[pos].priority++
    prio := cs[pos].priority

    // 调整节点位置,确保整个子节点数组是按照优先级倒序排列的,从而优先级更大的节点会被优先匹配
    newPos := pos
    for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
        // Swap node positions
        cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
    }

    // 调整前缀字符串,确保每个字母和子节点数组路径的首字母一致
    if newPos != pos {
        n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
            n.indices[pos:pos+1] + // The index char we move
            n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
    }

    return newPos
}

压缩前缀树部分是gin框架中最复杂的代码。

本人技术水平有限,文章中可能存在不足和遗漏,如果有同学愿意一起学习golang和gin的代码,也可以留言补充,一起学习共同成长!

关注我,带你发现更多有意思的技术和应用~👉👉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2106237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker 部署 Redis (图文并茂超详细)

部署 Redis ( Docker ) [Step 1] : 拉取 Redis 镜像, 推荐使用 7 的 Redis 版本 docker pull redis:7.0.12[Step 2] : 创建 Redis 相关目录 ➡️ 启动 Redis 容器 ➡️ 拷贝文件 ➡️ 授权文件夹 ➡️ 删除容器 # 创建 Redis 相关目录 mkdir -p /data/redis/{conf,data,log…

页面小组件-搜索栏(一)

样例展示 效果示例-折叠状态 效果示例-展开状态 代码示例 <custom-search-wrapper><!--showFoldBtn 需要展示折叠按钮时传值--><template slotleft><el-form:model"searchFormData"inlinesize"small"><el-form-item><e…

Linux 虚拟网络三大基石:Namespace、Veth pair 与 Bridge

引言 在 Linux 的世界里&#xff0c;虚拟网络技术是系统管理、云计算和容器化不可或缺的一部分。今天&#xff0c;我们将深入探讨构建这些虚拟网络的三大基石&#xff1a;Namespace、Veth 对和 Bridge&#xff0c;揭示它们如何在背后默默支撑起你的网络环境。 Namespace&…

PNP与NPN型传感器

PNP与NPN型传感器 一、磁性开关1、==磁性开关分类及原理==:2、==磁性开关配线==3、磁性开关串连和并联(不重要)4、磁性开关选型(不重要)二、PNP型与NPN型的选用1、PNP型传感器(高电平输出)1.1、对于`PNP-NO`(常开)型1.2、对于`PNP-NC`(常闭)型:2、NPN型传感器(低电…

Navicat 17 新特性 | 新增 Redis 哨兵部署模式

随着 Navicat 17 的发布&#xff0c;在业界引起了广泛的共鸣与热议。我们曾深入剖析其众多革新特性&#xff0c;包括新增 PolarDB 与 Garnet、模型设计创新与优化、增强的商业智能 BI 能力、高效的查询与配置、用户界面交互体验再升级&#xff0c;以及原生适配国产平台和操作系…

商品信息的标准化

销售环节的数字化见效最快 现在&#xff0c;企业的数字化是非常热的话题&#xff0c;工业&#xff14;.&#xff10;&#xff0c;人工智能&#xff0c;物联网&#xff0c;机器人都是企业数字化转型的主要方向&#xff0c;但是某些时候&#xff0c;我们走的太远&#xff0c;却忘…

双向链表的学习

双向链表是一种数据结构&#xff0c;它由节点组成&#xff0c;每个节点包含两个指针&#xff1a;一个指向前一个节点&#xff0c;另一个指向后一个节点。这种结构允许数据元素在两个方向上进行遍历&#xff0c;即既可以从前到后&#xff08;顺序&#xff09;&#xff0c;也可以…

【微处理器系统原理和应用设计第六讲】片上微处理器系统系统架构

一、概念辨析 首先来厘清以下概念&#xff1a;微处理器&#xff0c;微控制器&#xff0c;单片机&#xff0c;片上微处理器系统 &#xff08;1&#xff09;微处理器&#xff1a;即MPU&#xff08;Microprocessor Unit&#xff09;&#xff0c;微处理器是一种计算机的中央处理单…

Vue封装的过度与动画(transition-group、animate.css)

目录 1. Vue封装的过度与动画1.1 动画效果11.2 动态效果21.3 使用第三方动画库animate.css 1. Vue封装的过度与动画 作用&#xff1a;在插入、更新或移除DOM元素时&#xff0c;在合适的时候给元素添加样式类名 1.1 动画效果1 Test1.vue: transition内部只能包含一个子标签。…

电脑知识:如何恢复 Word、媒体和存档文件?

如果您是 Word 用户&#xff0c;那么您一定对无法打开 Word 文档的问题很熟悉。当文档包含大量关键信息时&#xff0c;情况会变得更加复杂。如果您遇到这种情况&#xff0c;那么您将如何处理&#xff1f; 我们再怎么强调在外部存储位置&#xff08;如外部硬盘、网络位置&#…

Ubuntu设置

1.查看版本:lsb_release -a 2.配置相关参数 配置root用户 设置 root 用户的登录密码&#xff0c;然后 su 登录。 1.改root密码&#xff1a;sudo passwd root 2.切换登录root用户&#xff1a; su root 3.root主目录在&#xff1a;cd ~ 4.开启 root 用户SSH远程登录权限 …

学习记录——day43 C++ 异常处理

一、异常处理的格式 1、在可能产生异常的地方使用关键字&#xff1a;throw 抛出异常 2、try { 可能会抛出异常的语句 }catch(接收异常的形参) { 处理异常 } 任何函数在定义时&#xff0c;可以指定能抛出的异常格式如下 返回值类型 函数…

前端:HTML、CSS、JS、Vue

1 前端 内容概要 了解前端三件套(HTML、CSS、JS)在前端所起的作用掌握HTML标签的功能&#xff0c;掌握重要标签(a标签&#xff0c;form标签)了解CSS了解JS的基础语法掌握Vue的基础语法重点掌握Vue项目怎么启动项目掌握前后端分离是什么。前端做什么事情&#xff0c;后端做什么…

统信UOS:快速修改主机名和计算机名

统信UOS&#xff1a;快速修改主机名和计算机名 1、修改主机名2、更改计算机显示名称 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、修改主机名 打开终端并获取root权限&#xff1a; 右键桌面空白处&#xff0c;选择“在终端中打开”。输…

制造业疲软引震荡,就业数据成市场焦点

周二&#xff0c;标普全球制造业PMI终值数据揭示了制造业的疲软态势&#xff0c;这一消息迅速在金融市场引发连锁反应&#xff0c;各类资产价格大幅跳水&#xff0c;交易员对经济前景的担忧情绪显著升温。在此背景下&#xff0c;即将于周五发布的美国非农就业数据无疑成为了本周…

软考 -- 软件设计师 -- 二轮复习(2) -- 程序设计语言(持续更新)

软考 – 软件设计师 – 二轮复习(2) – 程序设计语言(持续更新) 文章目录 软考 -- 软件设计师 -- 二轮复习(2) -- 程序设计语言(持续更新)前言一、编译、解释、基本控制结构二、数据类型三、变量和常量、逻辑表达式(短路&#xff1a;&&、||、&#xff01;)四、传值调用…

shell脚本编写之test命令

test命令用于测试某个条件是否成立&#xff0c;它可以进行数值、字符和文件三个方面的测试。 在shell文件中输入命令&#xff0c;通过特定的参数可以对数值、字符串进行比较&#xff0c;如下参数及示例。 1、数值比较参数 举例&#xff0c;在myshell.sh脚本中加入如下内容&am…

Github 2024-09-01 开源项目月报 Top16

根据Github Trendings的统计,本月(2024-09-01统计)共有16个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目9TypeScript项目5Dart项目2C项目1Jupyter Notebook项目1Rust项目1开发者职业成长指南 创建周期:2670 天开发语言:TypeScript协议类…

图像去噪:基于混合噪声处理的 UNF 滤波器与中值滤波器比较

在图像处理领域&#xff0c;去噪是一个非常重要的步骤。噪声会严重影响图像的质量&#xff0c;使得图像难以被理解或分析。本文将演示如何使用混合噪声处理技术和两种常见的滤波器&#xff08;UNF 滤波器与中值滤波器&#xff09;来去除图像中的噪声&#xff0c;并比较它们的性…

Bootstrap前端框架Glyphicons字体图标

115工具网收集提供Bootstrap前端框架Glyphicons字体图标库对照表​​​​​​​&#xff0c;Bootstrap前端UI,Glyphicons字体图标调用,Bootstrap按钮字体图标对照表,包括250多个来自Glyphicon Halflings的字体图标.项目中引用Bootstrap相关文件后即可直接调用下列图标class&quo…