Go 中使用 map 实现高效的数据缓存

简介: Go 中使用 map 实现高效的数据缓存

在 Go 语言中,使用 map 结合 LRU(Least Recently Used) 缓存算法,可以实现高效的数据缓存。以下是一个示例实现:

import (
    "container/list"
    "sync"
)

type Cache struct {
   
    capacity int
    mu       sync.Mutex
    list     *list.List
    m        map[string]*list.Element
}

type entry struct {
   
    key   string
    value interface{
   }
}

func NewCache(capacity int) *Cache {
   
    return &Cache{
   
        capacity: capacity,
        list:     list.New(),
        m:        make(map[string]*list.Element, capacity),
    }
}

func (c *Cache) Get(key string) (value interface{
   }, ok bool) {
   
    c.mu.Lock()
    defer c.mu.Unlock()

    if elem, ok := c.m[key]; ok {
   
        c.list.MoveToFront(elem)
        return elem.Value.(*entry).value, true
    }
    return nil, false
}

func (c *Cache) Put(key string, value interface{
   }) {
   
    c.mu.Lock()
    defer c.mu.Unlock()

    if elem, ok := c.m[key]; ok {
   
        c.list.MoveToFront(elem)
        elem.Value.(*entry).value = value
        return
    }

    if c.list.Len() == c.capacity {
   
        old := c.list.Back()
        c.list.Remove(old)
        delete(c.m, old.Value.(*entry).key)
    }

    elem := c.list.PushFront(&entry{
   key, value})
    c.m[key] = elem
}

这个缓存实现使用了 sync.Mutex 来确保线程安全,并使用 container/list 包中的双向链表来维护 LRU 顺序。

主要流程如下:

  1. Get(key) 方法先获取读锁,然后在 map 中查找键对应的值。如果找到,则将对应的链表节点移到链表头部,并返回值。
  2. Put(key, value) 方法先获取写锁,然后在 map 中查找键是否存在:
    • 如果存在,则将对应的链表节点移到链表头部,并更新值。
    • 如果不存在且缓存已满,则删除链表末尾的节点及其在 map 中的记录,然后添加新的节点到链表头部。

这种结构可以提供 O(1) 时间复杂度的 GetPut 操作,并能自动淘汰最久未使用的数据,非常适用于缓存场景。

此外,您也可以使用 Go 标准库中的 sync.Map 类型,它自带了并发安全的 LoadStoreDelete 方法,从而无需自行实现锁机制。

相关文章
|
6月前
|
人工智能 安全 Shell
Go并发编程避坑指南:从数据竞争到同步原语的解决方案
在高并发场景下,如钱包转账,数据一致性至关重要。本文通过实例演示了 Go 中如何利用 `sync.Mutex` 和 `sync.RWMutex` 解决数据竞争问题,帮助开发者掌握并发编程中的关键技能。
Go并发编程避坑指南:从数据竞争到同步原语的解决方案
|
8月前
|
Web App开发 存储 缓存
如何精准清除特定类型或标签的缓存数据?
如何精准清除特定类型或标签的缓存数据?
657 57
|
10月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
6月前
|
存储 监控 算法
企业电脑监控系统中基于 Go 语言的跳表结构设备数据索引算法研究
本文介绍基于Go语言的跳表算法在企业电脑监控系统中的应用,通过多层索引结构将数据查询、插入、删除操作优化至O(log n),显著提升海量设备数据管理效率,解决传统链表查询延迟问题,实现高效设备状态定位与异常筛选。
177 3
|
9月前
|
Go
Go语言同步原语与数据竞争:Mutex 与 RWMutex
在Go语言并发编程中,数据竞争是多个goroutine同时读写共享变量且未加控制导致的问题,可能引发程序崩溃或非确定性错误。为解决此问题,Go提供了`sync.Mutex`和`sync.RWMutex`两种同步机制。`Mutex`用于保护临界区,确保同一时间只有一个goroutine访问;`RWMutex`支持多读单写的细粒度控制,适合读多写少场景。使用时需避免死锁,并借助`-race`工具检测潜在的数据竞争,从而提升程序稳定性和性能。
265 51
|
7月前
|
存储 缓存 监控
一次缓存引发的文件系统数据不一致问题排查与深度解析
本文详述了一次由自研分布式文件系统客户端 EFC 的缓存架构更新所引发的严重数据不一致问题的完整排查过程。
一次缓存引发的文件系统数据不一致问题排查与深度解析
|
8月前
|
存储 人工智能 安全
深入理解 go sync.Map - 基本原理
本文介绍了 Go 语言中 `map` 在并发使用时的常见问题及其解决方案,重点对比了 `sync.Mutex`、`sync.RWMutex` 和 `sync.Map` 的性能差异及适用场景。文章指出,普通 `map` 不支持并发读写,容易引发错误;而 `sync.Map` 通过原子操作和优化设计,在某些场景下能显著提升性能。同时详细讲解了 `sync.Map` 的基本用法及其适合的应用环境,如读多写少或不同 goroutine 操作不同键的场景。
371 1
|
10月前
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
319 32
|
7月前
|
缓存 监控 安全
告别缓存击穿!Go 语言中的防并发神器:singleflight 包深度解析
在高并发场景中,多个请求同时访问同一资源易导致缓存击穿、数据库压力过大。Go 语言提供的 `singleflight` 包可将相同 key 的请求合并,仅执行一次实际操作,其余请求共享结果,有效降低系统负载。本文详解其原理、实现及典型应用场景,并附示例代码,助你掌握高并发优化技巧。
508 0
|
9月前
|
编译器 测试技术 Go
Go语言同步原语与数据竞争:数据竞争的检测工具
本文介绍了 Go 语言中数据竞争(Data Race)的概念及其检测方法。数据竞争发生在多个 Goroutine 无同步访问共享变量且至少一个为写操作时,可能导致程序行为不稳定或偶发崩溃。Go 提供了内置的竞态检测器(Race Detector),通过 `-race` 参数可轻松检测潜在问题。文章还展示了如何使用锁或原子操作修复数据竞争,并总结了在开发和 CI 流程中启用 `-race` 的最佳实践,以提升程序稳定性和可靠性。