LiSheng's blog LiSheng's blog
首页
笔记
个人简历
随笔集
GitHub (opens new window)
首页
笔记
个人简历
随笔集
GitHub (opens new window)
  • golang

    • 并发编程

      • GMP并发模型
      • 锁相关
      • groutine并发相关
      • go如何实现原子操作
    • 内存管理

    • 数组和切片的区别
    • new和make
    • go defer
    • context
    • channel
    • go map
      • interface
      • 对象系统
      • rune 类型
      • 字符串拼接的几种方式
    • cplus

    • leetcode

    • 存储技术

    • 分布式系统

    • 计算机网络

    • Linux操作系统

    • Redis

    • 其他

    • 笔记
    • golang
    lisheng
    2024-09-10
    目录

    go map

    1. map 使用注意的点,并发安全?
    2. map 循环是有序的还是无序的?
    3. map 中删除一个 key,它的内存会释放么?
    4. 怎么处理对 map 进行并发访问?有没有其他方案? 区别是什么?
    5. nil map 和空 map 有何不同?
    6. map 的数据结构是什么?是怎么实现扩容?

    基础知识、性能优化、线程安全、以及实际应用等方面的内容。

    # 1. Go 语言中的 map 是什么?有什么特点?

    • 问题: 请解释 Go 语言中的 map 结构,及其主要特点。
    • 回答要点:
      • map 是一种键值对(key-value)的数据结构。
      • map 是无序的,键值对的顺序不固定。
      • map 的键必须是可以比较的类型,如字符串、数字、指针等。
      • map 的值可以是任意类型,包括另一个 map。

    # 2. 如何初始化一个 map?

    • 问题: 请写出初始化一个 map 的几种方式。
    • 回答要点:
      // 方法1:使用 make 函数
      m1 := make(map[string]int)
      
      // 方法2:使用字面量初始化
      m2 := map[string]int{
          "one": 1,
          "two": 2,
      }
      
      // 方法3:空 map
      var m3 map[string]int // m3 == nil
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

    # 3. map 的常见操作有哪些?

    • 问题: 你如何进行 map 的插入、删除、查找和更新操作?
    • 回答要点:
      // 插入或更新
      m := make(map[string]int)
      m["key"] = 1
      
      // 查找
      value, exists := m["key"]
      if exists {
          fmt.Println("key found:", value)
      } else {
          fmt.Println("key not found")
      }
      
      // 删除
      delete(m, "key")
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

    # 4. map 的并发安全性

    • 问题: 在多个 Goroutine 并发访问同一个 map 时,会出现什么问题?如何解决?
    • 回答要点:
      • Go 中的 map 默认不是并发安全的。
      • 多个 Goroutine 并发读写同一个 map 会导致数据竞争,可能引发程序崩溃。
      • 解决方案:
        • 使用 sync.Mutex 或 sync.RWMutex 锁来保护 map 的访问。
        • 使用 sync.Map 替代普通 map,它是线程安全的。

    # 5. map 的容量与扩容机制

    • 问题: 当 map 中元素不断增加时,Go 是如何处理容量的问题的?会自动扩容吗?
    • 回答要点:
      • map 会根据元素的数量自动扩容。
      • 扩容时,会分配更多的内存并重新哈希现有的键值对。
      • 扩容是一个比较耗时的操作,因此在性能敏感的场合下需要注意。

    -在 Go 语言中,map 是一种哈希表的数据结构,为了保持高效的查找、插入和删除操作,当 map 的容量不足时,Go 会自动进行扩容。下面是 Go 中 map 扩容的基本机制和过程。

    # (1). 初始分配

    • 当创建一个 map 时,Go 会分配一个初始大小的哈希桶数组(bucket array)。每个桶包含若干个键值对的槽位。
    • 初始桶的数量是固定的,但具体的值可以由编译器设置。

    # (2). 负载因子

    • 负载因子是 map 的已使用槽位数与总槽位数的比例。
    • Go 使用负载因子来决定何时扩容。通常,当负载因子达到一定阈值时(大约为 6.5),map 会进行扩容。

    # (3). 扩容过程

    当 map 需要扩容时,Go 采取以下步骤:

      1. **创建新的哈希桶数组**:
         - 扩容时,Go 会分配一个新的哈希桶数组,新的数组大小是原来的两倍。每个桶包含的槽位数量保持不变。
        
      2. **重新分配键值对**:
         - 原哈希桶中的所有键值对将被重新分配到新的哈希桶中。
         - 重新分配时,Go 通过检查键的哈希值来决定新的桶位置。由于桶数量翻倍,哈希值的某些比特位会影响到新桶的位置。
    
      3. **增量扩容**:
         - 为了避免一次性扩容带来的性能抖动,Go 采用了**增量扩容**的策略。每次对 `map` 进行插入或删除操作时,部分桶会被迁移到新的哈希桶数组中,而不是一次性完成所有迁移。
         - 这种增量扩容策略有助于将扩容操作分摊到多次操作中,从而避免性能峰值。
    

    # (4). 平衡性能与内存的设计

    • Go 的 map 实现通过双倍扩容的策略在性能和内存使用之间取得了平衡。虽然这意味着在扩容时可能会消耗更多的内存,但它能够保持 O(1) 的平均时间复杂度。

    # (5). 溢出桶(overflow buckets)

    • 当一个桶中的槽位装满后,如果还需要插入更多的键值对,Go 会使用溢出桶(overflow buckets)。这些溢出桶与原始桶链式连接,直到有足够的空间为止。
    • 在扩容时,这些溢出桶中的数据也会被重新分配到新的哈希桶中。

    # 6. map 的 key 类型要求

    • 问题: 哪些类型可以作为 map 的 key?为什么某些类型不能作为 map 的 key?
    • 回答要点:
      • map 的 key 必须是可比较的类型,包括:布尔值、数字、字符串、指针、通道、接口(接口变量的动态类型是可比较的)、结构体(所有字段可比较)、数组(元素可比较)。
      • 不能作为 key 的类型包括:切片、map、函数等,因为它们在 Go 中是不可比较的。

    # 7. 如何遍历一个 map?遍历的顺序是固定的吗?

    • 问题: 请写出如何遍历一个 map,并解释遍历时的顺序是否固定。
    • 回答要点:
      m := map[string]int{"a": 1, "b": 2, "c": 3}
      for key, value := range m {
          fmt.Println(key, value)
      }
      
      1
      2
      3
      4
      • 遍历时,map 的顺序是随机的,且每次遍历的顺序可能不同。Go 的设计是为了防止依赖遍历顺序的 bug。

    # 8. 如何判断两个 map 是否相等?

    • 问题: Go 语言中如何比较两个 map 是否相等?
    • 回答要点:
      • Go 语言中不能直接用 == 来比较两个 map。
      • 需要手动遍历两个 map,检查每个键值对是否相同。
      • 示例:
        func mapsEqual(m1, m2 map[string]int) bool {
            if len(m1) != len(m2) {
                return false
            }
            for k, v := range m1 {
                if v2, ok := m2[k]; !ok || v != v2 {
                    return false
                }
            }
            return true
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

    # 9. 在高并发环境下,如何优化 map 的读写性能?

    • 问题: 有什么方法可以优化 map 在高并发场景下的性能?
    • 回答要点:
      • 使用 sync.Map,它专为并发设计,提供了良好的读写性能。
      • 如果 sync.Map 的性能不够好,可以考虑将 map 分片(sharding),用多个 map 处理不同部分的数据,并使用多个锁或 sync.Mutex 实现局部加锁,减少锁的争用。

    # 10. 什么是 map 的哈希碰撞?如何处理?

    • 问题: 解释 map 中的哈希碰撞及其处理方式。
    • 回答要点:
      • 哈希碰撞是指两个不同的键经过哈希函数后得到相同的哈希值。
      • Go 语言中的 map 使用开放寻址法和链地址法来处理哈希碰撞。
      • 开放寻址法是通过线性探测、二次探测等方法寻找下一个空闲位置。
      • 链地址法是为每个哈希桶存储一个链表,将冲突的键值对挂在链表上。
    编辑 (opens new window)
    上次更新: 2024/09/13, 11:59:12
    channel
    interface

    ← channel interface→

    最近更新
    01
    ceph分布式存储-对象存储(RGW)搭建
    10-27
    02
    ceph分布式存储-集群客户端连接
    10-27
    03
    ceph分布式存储-管理crushmap
    10-27
    更多文章>
    Theme by Vdoing
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式