数据结构之map

miloyang
0 评论
/ /
989 阅读
/
5907 字
31 2023-08

在Go语言中,map 是一种用于存储键值对的数据结构,也被称为字典或哈希表。它是一种无序的集合类型,用于在键(key)和值(value)之间建立映射关系。map 在Go语言中非常常用,适用于需要快速查找和检索数据的情况。

一些基本和使用方法

  • 创建map: 可以使用make函数来创建一个空的map,并指定键和值的数据类型。
myMap := make(map[string]int)  // 创建一个键为字符串,值为整数的空map
  • 插入和获取数据: 可以使用键来插入和获取数据。例如:
myMap["key1"] = 100
value := myMap["key1"]  // 获取键为"key1"的值
  • 删除数据: 使用delete函数来删除map中的一个键值对。例如:
delete(myMap, "key1")  // 删除键为"key1"的键值对,如果key1不存在myMap中,也是不报错的
  • 检查键是否存在: 使用以下方式可以检查键是否存在于map中:
value, ok := myMap["key1"]
if ok {
    // 键存在,进行处理
} else {
    // 键不存在
}
  • 迭代map: 使用range关键字可以迭代map中的所有键值对。迭代的顺序是不确定的,因为map是无序的。例如:
for key, value := range myMap {
    fmt.Println(key, value)
}
  • 注意事项:
    • map 是引用类型,传递给函数时会传递引用,因此对map的修改会影响原始map。
    • map 的键必须是可比较的类型(例如:整数、浮点数、字符串、指针、通道、接口和数组)。
    • map 的值可以是任意类型。
    • map 是无序的,无法保证迭代的顺序与插入的顺序一致
    • 使用map需要注意并发访问的情况,如果多个协程同时访问和修改同一个map,需要采取适当的同步措施,例如使用互斥锁来保证数据的一致性。
    • 那么,map为什么是无序的呢?
哈希表中的数据项并不是按照插入顺序进行存储的,而是根据键的哈希值计算出的索引位置来存储。这意味着,在map中迭代元素的顺序是不确定的,因为它们实际上不是按照某种特定顺序进行存储的。这样有以下的优点:
1:快速查找: map 的主要目的是提供一种快速的键值查找机制。为了实现这一点,map 内部使用了哈希表(hash table)或类似的数据结构。这使得按键查找元素的时间复杂度是平均 O(1),几乎是实时的。
2:内存管理: 无序的设计可以让map更好地适应动态内存管理。当元素被添加或删除时,内部数据结构可以更灵活地重新组织,而不需要保持稳定的顺序。
3:性能: 无序map的内部实现通常比有序map更加高效。有序的数据结构需要维护元素的顺序,这可能会导致插入和删除元素时的额外开销。

另外需要注意的是,由于哈希表的内部实现和优化策略可能会随着Go语言版本的不同而有所改变,所以在不同版本的Go中,map的迭代顺序可能也会发生变化。

练习一下吧

题目:实现一个缓存系统,有如下功能:

  • 可以设置缓存的过期时间,如redis的过期时间
  • 可以设置最大的内存大小,
  • 可以获取当前的key数量以及清空内存。
  • 定时清除已经过期的缓存数据

定义缓存的接口

type Cache interface {
    SetMaxMemory(size string) bool
    Set(key string, val interface{}, expire time.Duration) bool
    Get(key string) (interface{}, bool)
    Del(key string) bool
    Flush() bool
    Keys() int64
}

定义结构体

func NewMemCache() Cache {
    mc := &memCache{
        values:                      make(map[string]*memCacheValue, 0),
        clearExpireItemTimeInterval: time.Second * 10,
    }
    go mc.clearExpireItem()
    return mc
}

type memCache struct {
    maxMemorySize    int64
    maxMemorySizeStr string //最大内存字符串表示
    currMemorySize   int64
    values map[string]*memCacheValue
    //锁:map非线程安全 需要加锁
    locker sync.RWMutex //读写锁
    clearExpireItemTimeInterval time.Duration
}

type memCacheValue struct {
    val        interface{}
    expireTime time.Time
    expire     time.Duration
    size       int64
}

具体实现

// SetMaxMemory 设置最大内存值
func (mc *memCache) SetMaxMemory(size string) bool {
    mc.maxMemorySize, mc.maxMemorySizeStr = ParseSize(size)
    return false
}

// Set 设置值进缓存,需要带过期时间
func (mc *memCache) Set(key string, val interface{}, expire time.Duration) bool {
    mc.locker.Lock()
    defer mc.locker.Unlock()
    v := &memCacheValue{
        val:        val,
        expireTime: time.Now().Add(expire),
        expire:     expire,
        size:       GetValSize(val),
    }

    if (mc.maxMemorySize - mc.currMemorySize) < v.size {
        log.Printf(fmt.Sprintf("max memory size %v \n", mc.maxMemorySize))
        return false
    }
    mc.del(key)
    mc.add(key, v)
    return false
}

// Get 从缓存中获取值,如果过期了则立马删掉
func (mc *memCache) Get(key string) (interface{}, bool) {
    mc.locker.Lock()
    defer mc.locker.Unlock()

    mcv, ok := mc.get(key)
    if ok {
        if mcv.expire != 0 && mcv.expireTime.Before(time.Now()) {
            mc.del(key)
            return nil, false
        }
    }
    return mcv, true
}

// Del 删除缓存中的数据
func (mc *memCache) Del(key string) bool {
    mc.locker.Lock()
    defer mc.locker.Unlock()

    mc.del(key)

    return false
}

// Exist 判断key是否存在缓存中
func (mc *memCache) Exist(key string) bool {
    mc.locker.Lock()
    defer mc.locker.Unlock()
    _, ok := mc.values[key]
    return ok
}

func (mc *memCache) get(key string) (*memCacheValue, bool) {
    val, ok := mc.values[key]
    return val, ok
}

func (mc *memCache) del(key string) {
    tmp, ok := mc.get(key)
    if ok && tmp != nil {
        mc.currMemorySize -= tmp.size
        delete(mc.values, key)
    }
}

func (mc *memCache) add(key string, val *memCacheValue) {
    mc.values[key] = val
    mc.currMemorySize += val.size
}

func (mc *memCache) Flush() bool {
    mc.locker.Lock()
    defer mc.locker.Unlock()
    mc.values = make(map[string]*memCacheValue, 0)
    mc.currMemorySize = 0
    return false
}

func (mc *memCache) Keys() int64 {
    mc.locker.Lock()
    defer mc.locker.Unlock()
    return int64(len(mc.values))
}

// 定期的去检查当前过期的key,过期了移除掉
func (mc *memCache) clearExpireItem() {
    timeTicker := time.NewTicker(mc.clearExpireItemTimeInterval)
    defer timeTicker.Stop()
    for {
        select {
        case <-timeTicker.C:
            for key, item := range mc.values {
                if item.expire != 0 && time.Now().After(item.expireTime) {
                    mc.locker.Lock()
                    mc.del(key)
                    mc.locker.Unlock()
                }
            }
        }
    }
}

使用方式

cache := pkg.NewMemCache()
    cache.SetMaxMemory("200MB")
    cache.Set("int", 1)
    cache.Get("int")
    cache.Del("int")
    cache.Flush()
    cache.Keys()
人未眠
工作数十年
脚步未曾歇,学习未曾停
乍回首
路程虽丰富,知识未记录
   借此博客,与之共进步