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
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
在 Go 中,使用带缓冲的通道(buffered channel)代替切片传参是一种常见的优化策略,尤其是在并发编程中,可以避免直接传递切片可能带来的性能问题或内存使用问题。带缓冲的通道能够在不阻塞发送方的情况下存储一定数量的数据,使得多个协程可以并发地进行数据传输。 使用带缓冲的通道代替切片传参 1. 为什么使用带缓冲的通道代替切片传参? 并发性:使用带缓冲的通道可以提高并发性,尤其是在并发任务较多时,能够避免频繁的内存拷贝,且通道能够控制数据的传输。 内存效率:切片传递通常会涉及内存的拷贝或者引用传递,如果数据量较大,使用切片传参可能会增加内存使用。而使用带缓冲的通道,数据可以被按需传输,避免不必要的内存拷贝。 灵活性和同步:带缓冲的通道可以作为一个同步机制,提供更细粒度的控制,而不仅仅是一个数据传递工具。 2. 带缓冲的通道的工作原理 带缓冲的通道与普通通道的不同之处在于,带缓冲的通道允许在没有接收方的情况下先存储一定量的数据。发送数据不会立即阻塞,直到缓冲区已满。接收方则需要从通道中取出数据。 make(chan T, n) 创建一个缓冲区大小为 n 的通道。 发送数据到通道时.... 使用带缓冲的通道(buffered channel)代替切片传参 chan
// Or read one or more channels into one channel, will close when any readin channel is closed. // Play: https://go.dev/play/p/Wqz9rwioPww func (c *Channel[T]) Or(channels ...<-chan T) <-chan T { switch len(channels) { case 0: return nil case 1: return channels[0] } orDone := make(chan T) go func() { defer close(orDone) switch len(channels) { case 2: select { case <-channels[0]: case <-channels[1]: } default: select { case <-channels[0]: case <-channels[1]: case <-channels[2.... lancet concurrency Or(channels ...<-chan T) <-chan T lancet
在 Go 语言中,使用 range 遍历通道(channel)是一种常见的方式,用于接收通道中的数据。range 会自动处理通道的接收操作,直到通道关闭并且所有数据都被接收完为止。 range 遍历通道的基本用法 当你使用 range 遍历一个通道时,Go 会从通道中不断接收数据,直到通道关闭并且数据接收完毕。重要的一点是,只有在通道关闭后,range 才会停止接收数据。 示例 1:基本的通道遍历 下面的示例展示了如何使用 range 遍历一个通道,接收数据直到通道关闭: package main import "fmt" func main() { // 创建一个带缓冲区的通道 ch := make(chan int, 3) // 向通道发送数据 ch <- 1 ch <- 2 ch <- 3 // 关闭通道 close(ch) // 使用 range 遍历通道 for v := range ch { fmt.Println("接收到的值:", v) } } 输出: 接收到的值: 1 接收到的值: 2 接收到的值: 3 解释: 我们首先创建一个带缓冲区的通道 c.... 用range遍历channel chan