github.com/bytedance/go-tagexpr/v2 是字节跳动公司开源的一个高性能、灵活的 Go 语言库,主要用于解析和执行标签表达式(Tag Expression)。标签表达式通常用于在结构体、数据记录或其他上下文中动态地评估条件,例如在监控系统、数据过滤和规则引擎等场景中非常有用。 该库允许开发者通过结构体标签定义表达式,表达式可以基于某些条件来决定数据是否满足特定规则。它支持常见的条件运算符(如 =、>、<= 等),并且可以扩展自定义函数和操作符,具有很强的灵活性。 核心特性: 高性能:优化的解析器和计算引擎,能够处理大规模数据。 灵活的表达式语法:支持逻辑运算符、比较运算符、函数调用等多种表达式形式。 可扩展:支持自定义函数和扩展运算符,能够灵活满足不同需求。 结构体标签解析:可以解析结构体的标签,允许在结构体的字段标签中嵌入表达式,动态计算字段值。 主要功能: 通过结构体标签(tag)来定义表达式。 支持基本的逻辑运算符(and, or, not)和比较运算符(=, !=, >, <, >= 等)。 支持内置函数(如 len().... 有更新! golang每日一库之bytedance/go-tagexpr golang每日一库
Go语言的泛型(Generics)在Go 1.18版本中得到了引入,它使得Go语言能够编写更灵活和可复用的代码,而无需丧失类型安全。Go的泛型主要通过类型参数(type parameters)来实现,允许你编写接受不同类型的函数、结构体或接口。 1. 类型参数 Go 泛型的核心概念是 类型参数,它允许你定义接受不同类型的函数、结构体或接口。你可以把类型参数看作是占位符,编译器会根据调用时的实际类型来替换它。 基本语法 func Print[T any](value T) { fmt.Println(value) } T 是类型参数,可以代表任何类型。 any 是类型约束,表示 T 可以是任意类型,相当于旧版本的 interface{}。 2. 类型约束 Go 泛型允许你对类型参数指定 类型约束,以确保类型参数满足某些条件。你可以通过接口来定义这些约束。 2.1 定义类型约束 类型约束是通过接口来实现的,接口可以声明类型必须实现的方法或属性。 package main import "fmt" // 定义一个接口约束,要求类型参数 T 必须实现 String() 方法 type Str.... 泛型 go
shopspring/decimal 是一个用于处理任意精度十进制浮点数的 Go 语言库,通常用于金融计算、货币相关计算等场景。标准的 float64 类型可能无法满足精确度要求,因为浮点数的表示方式是近似的,特别是在进行累加、除法和精确比较时可能会导致舍入误差。 shopspring/decimal 提供了一个高精度的十进制类型 decimal.Decimal,它确保在进行数学运算时不丢失精度。这个库特别适合用于需要严格计算精度的场景,例如处理货币、财务、税务等领域中的数值计算。 1. 安装 shopspring/decimal 要使用 shopspring/decimal,首先需要安装它。可以使用以下命令来安装: go get github.com/shopspring/decimal 2. 基本用法 创建 decimal.Decimal 值 从字符串创建:使用 decimal.NewFromString 方法从字符串创建 Decimal。 从浮点数创建:使用 decimal.NewFromFloat 方法从浮动数创建。 从整数创建:使用 decimal.NewFromInt 方.... golang每日一库之shopspring/decimal golang每日一库
BigCache 是一个为 Go 语言设计的高效内存缓存库,专门针对大规模缓存应用场景进行了优化。其设计目标是让开发者能够在高并发、高吞吐量的情况下,轻松处理大量缓存项而不牺牲性能。 主要特点 内存优化: BigCache 在内存管理方面进行了大量优化,特别是当你需要存储非常多的小对象时,BigCache 可以有效减少内存的碎片化,避免内存浪费。 使用分区(sharding)技术来分布缓存,避免了单个大内存块的管理问题,从而提升了性能。 高并发支持: 采用无锁设计,能够在高并发的环境下保持优异的性能表现。每个缓存项都有独立的锁,减少了竞争和阻塞。 缓存项过期: 支持设置缓存项的过期时间(TTL)。当缓存项超过设定的过期时间后,BigCache 会自动清除这些缓存项。 提供自动清理的机制,不需要手动管理过期项。 高效的内存回收: 采用定时清理策略定期清理过期缓存,确保内存不会因过多的无效缓存而占用过多资源。 简易的接口: 提供了简单的缓存操作接口,支持常见的 Set、Get、Delete 操作,易于集成到现有的应用中。 使用场景 高并发缓存:适合需要高并发、高吞吐量的缓存系统,.... golang每日一库之bigcache golang每日一库
Lancet 是一个 Go 语言的开源工具库,旨在为 Go 开发者提供一些常见且实用的功能,使得 Go 编程更加简洁和高效。Lancet 提供了大量的工具函数和扩展,类似于 JavaScript 中的 Lodash 或者 Python 中的工具库,它主要集中在集合(数组、切片、映射)、函数式编程、数据转换等领域。 主要特性 集合操作: Lancet 提供了很多用于操作切片和映射的函数,比如去重、排序、查找、映射等,极大地提高了在 Go 中处理集合的效率。 函数式编程支持: 支持函数式编程风格,如 Map、Filter、Reduce 等,能够简化代码并使其更具可读性。 类型转换和辅助函数: 提供了常用的类型转换工具(例如从 string 转换为 int 或 float),以及一些常用的辅助函数。 性能优化: Lancet 的设计目标之一是提供高效的实现,以确保在大型应用程序中也能提供优异的性能。 简化代码: Lancet 提供了许多通用功能的封装,让开发者不需要重复编写常见操作,从而提高代码的简洁度和可维护性。 常用功能 1. 集合操作 去重:去除切片中的重复元素。 排序:对切.... golang每日一库之 lancet golang每日一库
在 Go 语言中,有很多高质量的工具库可以简化开发工作,提高效率。以下是一些常用的 Go 工具库,涵盖了从日志处理到 HTTP 请求、配置管理、并发控制等各个方面。 1. 日志处理库 logrus:一个功能强大的结构化日志记录库,支持日志级别、钩子、日志格式化等功能。 zap:由 Uber 开发的高性能、结构化日志库,特别适合高并发环境。 zerolog:一个零分配、非常高效的 JSON 日志库。 2. 配置管理 viper:一个强大的配置管理库,支持从环境变量、JSON、YAML、TOML 等多种格式读取配置。 envconfig:一个简单的环境变量解析库,能够轻松读取和解析结构体中的环境变量配置。 3. HTTP 请求和路由 gin:一个高效、快速的 HTTP Web 框架,具有类似 Express.js 的 API,支持路由、JSON 验证、请求中间件等。 echo:一个简洁而强大的 Web 框架,拥有强大的中间件机制,支持高性能的 HTTP 路由。 gorilla/mux:一个功能强大的 HTTP 路由库,支持正则表达式匹配、路由参数等。 4. 数据库和 ORM gorm:一个.... go的常用工具库 go
节流函数(Throttling)是通过控制函数的执行频率来提高性能的一种技术,常用于处理频繁触发的事件,例如滚动事件、窗口大小调整、按键输入、鼠标移动等。 在没有节流的情况下,某些事件会在很短的时间内被触发多次,可能导致性能问题或不必要的资源消耗。例如,用户在滚动页面时,每次滚动都会触发一个事件,如果不加以控制,可能会导致浏览器频繁地执行相关回调函数,影响性能。 节流函数的工作原理 节流函数通过限制函数的执行频率来减少不必要的调用。它可以让函数在规定时间内只能执行一次,忽略其他调用,直到指定时间间隔结束才允许下一次执行。 例如 假设我们有一个需要响应滚动事件的回调函数,如果没有节流,用户每次滚动页面都会触发该函数,导致函数被执行非常频繁。通过节流,我们可以确保函数在一定时间内(比如每 200 毫秒)最多执行一次。 节流与防抖的区别 节流(Throttling):限制函数执行的频率。即在指定的时间间隔内,只允许函数执行一次。 防抖(Debouncing):限制函数的执行时机,直到事件停止触发一定时间后才执行。 节流函数的常见实现 通常通过 setTimeout 或 requestAnim.... 节流函数 go
修改yarn.lock文件里registry.nlark.com 改为 registry.npmmirror.com yarn 出现 【error Error: getaddrinfo ENOTFOUND registry.nlark.com at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:72:26) yarn
堆(Heap) 是一种特殊的完全二叉树,具有以下特点:每个节点的值都与其子节点的值存在某种特定的关系,通常有两种类型的堆:最大堆和最小堆。 最大堆(Max-Heap):每个节点的值大于或等于其子节点的值,堆顶元素是所有元素中的最大值。 最小堆(Min-Heap):每个节点的值小于或等于其子节点的值,堆顶元素是所有元素中的最小值。 1. 堆的基本特性 完全二叉树:堆是完全二叉树,这意味着树的每一层都被填满,除最底层外,最底层的节点按从左到右的顺序排列。 堆序性质: 最大堆:对于每个节点 i,都有 A[i] >= A[2i + 1] 且 A[i] >= A[2i + 2](子节点的值小于等于父节点的值)。 最小堆:对于每个节点 i,都有 A[i] <= A[2i + 1] 且 A[i] <= A[2i + 2](子节点的值大于等于父节点的值)。 2. 堆的常见操作 堆提供了以下几种常见的操作: 1. 插入(Insert) 将一个新元素插入堆中,插入后需要进行“上浮”操作(heapify-up),以确保堆序性质得以保持。 对于最大堆,插入元素时,需要与父节点比较,如.... 堆 数据结构
栈(Stack) 是一种特殊的线性数据结构,它遵循“后进先出”(LIFO, Last In First Out)原则,即最后插入的元素最先被删除。栈的操作仅限于一端(栈顶),所有的插入(入栈)和删除(出栈)操作都在栈顶进行。 1. 栈的基本操作 栈支持以下几种常见操作: 入栈(Push): 将一个元素添加到栈顶。 例如:Push(x) 将元素 x 插入到栈顶。 出栈(Pop): 从栈顶移除一个元素,并返回该元素。 例如:Pop() 移除并返回栈顶元素。 查看栈顶元素(Peek 或 Top): 返回栈顶的元素,但不删除它。 例如:Peek() 或 Top() 返回栈顶元素,但不出栈。 栈是否为空(isEmpty): 检查栈是否为空。 例如:isEmpty() 如果栈为空返回 true,否则返回 false。 栈的大小(Size): 返回栈中元素的个数。 例如:Size() 返回栈的元素个数。 2. 栈的应用 栈是计算机科学中非常重要的数据结构,它广泛应用于以下几种场景: 表达式求值: 中缀表达式转后缀(逆波兰表示法)和计算后缀表达式时使用栈。 函数调用: 栈用于存储函数调用.... 栈 数据结构
线性表(Linear List)是数据结构中最基础的一类,指的是由一系列相同类型的数据元素组成的集合,且每个元素有一个明确的前驱和后继元素。线性表的元素之间存在线性关系(顺序关系),因此可以通过位置或顺序来访问元素。 线性表是很多其他数据结构的基础,它是顺序存储和链式存储的集合。 1. 线性表的基本定义 线性表(Linear List)是一个线性结构,它包含以下特点: 元素之间有顺序关系:每个元素都有唯一的位置(除第一个和最后一个元素外,每个元素都有前驱和后继)。 线性关系:线性表中的元素可以通过顺序(索引)来访问。 2. 线性表的分类 线性表主要有两种常见的存储方式: 顺序存储结构(顺序表) 链式存储结构(链表) 顺序存储结构(顺序表) 顺序表使用一块连续的内存空间来存储线性表的元素,元素在内存中按顺序排列。 优点:存储密度高,访问速度快,支持通过索引直接访问元素。 缺点:插入和删除操作效率较低,特别是在中间插入或删除元素时需要移动大量元素。 链式存储结构(链表) 链表是由一系列节点构成的,每个节点包含数据部分和指向下一个节点的指针。节点在内存中并不需要连续存储。 优点:插入和删除操.... 线性表 数据结构
单向链表和双向链表是链式数据结构的两种主要形式,它们之间有几个关键的区别和各自的优缺点。下面我们将对这两者进行对比,以帮助你更好地理解它们的特点和应用场景。 1. 结构对比 单向链表: 每个节点只有一个指向下一个节点的指针(next)。 链表中的节点只能单向地遍历。 每个节点只知道如何访问下一个节点,不能反向访问。 双向链表: 每个节点有两个指针,一个指向下一个节点(next),另一个指向前一个节点(prev)。 可以在链表中进行双向遍历,从头节点到尾节点,或者从尾节点到头节点。 每个节点不仅知道如何访问下一个节点,还能访问到前一个节点,支持更灵活的操作。 2. 内存占用 单向链表:每个节点需要存储一个数据项和一个指向下一个节点的指针,因此每个节点只占用较少的内存。 双向链表:每个节点需要存储一个数据项、一个指向下一个节点的指针和一个指向前一个节点的指针,因此每个节点占用的内存是单向链表节点的两倍。 3. 插入与删除操作 单向链表: 插入操作:在头部插入节点的时间复杂度是 O(1),但在尾部插入时需要遍历到链表的尾部,时间复杂度为 O(n)。 删除操作:删除头节点是 O(1),但.... 单向链表和双向链表比较 数据结构
单向链表(Singly Linked List)是一种链式数据结构,其中的每个节点包含两个部分:数据和指向下一个节点的指针。单向链表中的每个节点只能向前指向下一个节点,无法向后访问,意味着只能从链表的头部开始遍历,直到最后一个节点。 单向链表的结构: 单向链表的节点通常包含以下两个部分: 数据域(data):存储节点的数据。 指向下一个节点的指针(next):指向链表中的下一个节点。 单向链表的操作: 插入操作: 在链表的头部插入节点。 在链表的尾部插入节点。 在链表的中间插入节点。 删除操作: 删除头节点。 删除尾节点(需要遍历整个链表)。 删除中间的某个节点。 查找操作: 查找指定数据的节点。 单向链表的优势: 节省空间:每个节点只需要一个指针来指向下一个节点,内存消耗比双向链表少。 结构简单:因为只需要一个指针来连接节点,代码实现相对简单。 单向链表的缺点: 只能单向遍历:只能从头节点开始,向后遍历链表,无法进行反向遍历。 删除操作较麻烦:如果想删除一个中间节点,需要访问该节点的前一个节点(通常从头节点开始遍历,找到该节点的前一个节点)。 单向链表的示意图: Head →.... 单向链表 数据结构
双向链表(Doubly Linked List)是一种链式数据结构,与单向链表不同,双向链表中的每个节点不仅包含指向下一个节点的指针(即 next),还包含指向前一个节点的指针(即 prev)。这种结构使得在链表中进行双向遍历更加方便。 双向链表的结构: 一个双向链表的节点通常包含以下三个部分: 数据域(data):存储节点的实际数据。 指向下一个节点的指针(next):指向链表中的下一个节点。 指向前一个节点的指针(prev):指向链表中的前一个节点。 双向链表的优势: 双向遍历:可以从头到尾遍历,也可以从尾到头遍历。 删除操作:删除节点时不需要从头遍历,能够直接通过 prev 指针找到前一个节点,相对单向链表效率更高。 双向链表的操作: 插入操作: 在链表的头部插入节点。 在链表的尾部插入节点。 在链表的中间插入节点。 删除操作: 删除头节点。 删除尾节点。 删除中间的某个节点。 查找操作: 查找指定数据的节点。 双向链表的示意图: Head <-> Node1 <-> Node2 <-> Node3 <-> Tail 在这.... 数据结构-双向链表 数据结构
// Bridge link multiply channels into one channel. // Play: https://go.dev/play/p/qmWSy1NVF-Y func (c *Channel[T]) Bridge(ctx context.Context, chanStream <-chan <-chan T) <-chan T { valStream := make(chan T) go func() { defer close(valStream) for { var stream <-chan T select { case maybeStream, ok := <-chanStream: if !ok { return } stream = maybeStream case <-ctx.Done(): return } for val := range c.OrDone(ctx, stream) { select { case valStream <- val: case <-ctx.Done(): }.... lancet concurrency Bridge lancet
// FanIn merge multiple channels into one channel. // Play: https://go.dev/play/p/2VYFMexEvTm func (c *Channel[T]) FanIn(ctx context.Context, channels ...<-chan T) <-chan T { out := make(chan T) go func() { var wg sync.WaitGroup wg.Add(len(channels)) for _, c := range channels { go func(c <-chan T) { defer wg.Done() for v := range c { select { case <-ctx.Done(): return case out <- v: } } }(c) } wg.Wait() close(out) }() return out } 你提供的 FanIn 函数用于将多个输入通道(channels)合并成一个输出通道(out)。这.... 有更新! lancet concurrency FanIn lancet
// Tee split one chanel into two channels, until cancel the context. // Play: https://go.dev/play/p/3TQPKnCirrP func (c *Channel[T]) Tee(ctx context.Context, in <-chan T) (<-chan T, <-chan T) { out1 := make(chan T) out2 := make(chan T) go func() { defer close(out1) defer close(out2) for val := range c.OrDone(ctx, in) { var out1, out2 = out1, out2 for i := 0; i < 2; i++ { select { case <-ctx.Done(): case out1 <- val: out1 = nil case out2 <- val: out2 = nil } } } }() return ou.... lancet concurrency Tee lancet
Go Channel 介绍 在 Go 语言中,Channel 是一种内置的数据结构,用于在多个 goroutine 之间进行通信。它是 Go 语言并发模型的核心之一,与 goroutine 配合使用,可以实现安全、有效的并发编程。 Channel 可以被看作是 goroutine 之间的管道,它提供了一种类型安全的通信方式,允许一个 goroutine 发送数据,另一个 goroutine 接收数据。Go 语言通过这种机制简化了并发编程的复杂性,避免了传统多线程编程中常见的锁(mutex)和共享变量的复杂性。 Channel 的基本特性 类型安全: 每个 Channel 都有一个指定的数据类型,只有相同类型的数据才能被传输。 同步机制: Channel 实现了同步机制,当一个 goroutine 向 Channel 发送数据时,发送操作会阻塞,直到有另一个 goroutine 从 Channel 中接收数据;反之亦然。也就是说,Channel 默认是同步的。 无锁通信: Go 的 Channel 提供了无锁的通信方式,避免了显式使用锁(如 mutex)来保证数据安全。 可以缓冲(.... 有更新! 认识协程间通讯的神器channel chan
import ( "context" "fmt" "github.com/duke-git/lancet/v2/concurrency" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := concurrency.NewChannel[int]() genVals := func() <-chan <-chan int { out := make(chan (<-chan int)) go func() { defer close(out) for i := 1; i <= 5; i++ { stream := make(chan int, 1) stream <- i close(stream) out <- stream } }() return out } for v := range c.Bridge(ctx, genVals()) { fmt.Println(v) } // Output: // 1 // 2 .... lacet concurrency Bridge示例 lancet
带缓冲的通道相较于直接传递切片,在某些情况下可以更高效,主要体现在以下几个方面: 1. 避免内存拷贝 在 Go 中,切片是引用类型,传递切片本身只是传递一个指向底层数组的指针。如果切片的大小很大,或者是多个 goroutine 同时处理不同部分的数据,传递切片可能会导致额外的内存开销。例如,多个 goroutine 如果对同一个切片进行操作,可能会产生竞态条件或者需要同步,额外的内存拷贝和同步开销可能导致性能下降。 带缓冲通道的优势: 带缓冲的通道通过本身的缓冲区(一个固定大小的内存区域)存储数据,避免了在多个 goroutine 之间传递大量切片时需要复制数据的问题。数据只会在需要时被传递,这样可以降低内存开销。 2. 减少内存分配和垃圾回收负担 当使用切片时,如果每次都创建新的切片或者修改切片内容,会增加内存分配和垃圾回收的负担,尤其是在传递大型切片或者切片内容非常频繁的情况下。 带缓冲通道的优势: 带缓冲的通道通常会在创建时就分配一定的内存空间,通道的数据存储在这个缓冲区中,因此在数据传递时不会频繁分配内存。通道的缓冲区会自动管理内存的使用,减少了手动分配和回收内存的次数。 由于.... 相较于直接传递切片,带缓冲的通道在什么情况可以更高效 chan