当前位置: 首页 > article >正文

【Golang 面试题】每日 3 题(二十)

✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/UWz06
📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

58. Go Map 中 mapextra 结构体实现

当 map 的 key 和 value 都不是指针类型时候,bmap 将完全不包含指针,那么 gc 时候就不用扫描 bmap。bmap 指向溢出桶的字段 overflow 是 uintptr 类型,为了防止这些 overflow 桶被 gc 掉,所以需要 mapextra.overflow 将它保存起来。如果 bmap 的 overflow 是 *bmap 类型,那么 gc 扫描的是一个个拉链表,效率明显不如直接扫描一段内存 (hmap.mapextra.overflow)。

type mapextra struct {
    overflow    *[]*bmap
    // overflow 包含的是 hmap.buckets 的 overflow 的 buckets
    oldoverflow *[]*bma
   // oldoverflow 包含扩容时 hmap.oldbuckets 的 overflow 的 bucket
    nextOverflow *bmap 
     // 指向空闲的 overflow bucket 的指针
}

总结

bmap(bucket)内存数据结构可视化如下:

注意到 key 和 value 是各自放在一起的,并不是 key/value/key/value/… 这样的形式,当 key 和 value 类型不一样的时候,key 和 value 占用字节大小不一样,使用 key/value 这种形式可能会因为内存对齐导致内存空间浪费,所以 Go 采用 key 和 value 分开存储的设计,更节省内存空间。

59. Go Map 遍历为什么是无序的?

使用 range 多次遍历 map 时输出的 key 和 value 的顺序可能不同。这是 Go 语言的设计者们有意为之,旨在提示开发者们,Go 底层实现并不保证 map 遍历顺序稳定,请大家不要依赖 range 遍历结果顺序。

主要原因有 2 点:

  • map 在遍历时,并不是从固定的 0 号 bucket 开始遍历的,每次遍历,都会从一个随机值序号的 bucket,再从其中随机的 cell 开始遍历。
  • map 遍历时,是按序遍历 bucket,同时按需遍历 bucket 中和其 overflow bucket 中的 cell。但是 map 在扩容后,会发生 key 的搬迁,这造成原来落在一个 bucket 中的 key,搬迁后,有可能会落到其他 bucket 中了,从这个角度看,遍历 map 的结果就不可能是按照原来的顺序了。

map 本身是无序的,且遍历时顺序还会被随机化,如果想顺序遍历 map,需要对 map key 先排序,再按照 key 的顺序遍历 map。

func TestMapRange(t *testing.T) {
    m := map[int]string{1: "a", 2: "b", 3: "c"}
    t.Log("first range:")
    for i, v := range m {
        t.Logf("m[%v]=%v ", i, v)
    }
    t.Log("second range:")
    for i, v := range m {
        t.Logf("m[%v]=%v ", i, v)
    }

    // 实现有序遍历
    var sl []int
    // 把 key 单独取出放到切片
    for k := range m {
        sl = append(sl, k)
    }
    // 排序切片
    sort.Ints(sl)
    // 以切片中的 key 顺序遍历 map 就是有序的了
    for _, k := range sl {
        t.Log(k, m[k])
    }
}

60. Go Map 为什么是非线程安全的?

map 默认是并发不安全的,同时对 map 进行并发读写时,程序会 panic,原因如下:

Go 官方在经过了长时间的讨论后,认为 Go map 更应适配典型使用场景(不需要从多个 goroutine 中进行安全访问),而不是为了小部分情况(并发访问),导致大部分程序付出加锁代价(性能),决定了不支持。

场景: 2 个协程同时读和写,以下程序会出现致命错误:fatal error: concurrent map writes

package main

import (
    "fmt"
    "time"
)

func main() {
    s := make(map[int]int)
    for i := 0; i < 100; i++ {
        go func(i int) {
            s[i] = i
        }(i)
    }
    for i := 0; i < 100; i++ {
        go func(i int) {
            fmt.Printf("map第%d个元素值是%d", i, s[i])
        }(i)
    }
    time.Sleep(1 * time.Second)
}

http://www.kler.cn/a/470192.html

相关文章:

  • HTML5实现喜庆的新年快乐网页源码
  • 均值聚类算法
  • Webstorm整合Tabnine AI 编码工具
  • ansible-api分析(Inventory)
  • PixPin—— 高效截图工具的下载与使用攻略
  • 大数据架构演变
  • Java基础 注解
  • C#版OpenCv常用函数大全
  • 手写RPC笔记
  • [Qt] 万字详解 | 常用控件 | Button | Label | LCD | ProgressBar
  • Redis(三)单线程架构介绍
  • QT:控件属性及常用控件(2)-----按钮类控件及显示类控件
  • Rtemis解题过程
  • 基于人脸识别和 MySQL 的考勤管理系统实现
  • 庐山派K230学习日记5 UART
  • LabVIEW软件侵权分析与应对
  • element组件el-select、el-tree-select有值,不渲染lable
  • GitLab创建用户,设置访问SSH Key
  • 数造科技荣获 2024 年“年度数据资源创新开发企业”
  • 软件体系结构与设计模式
  • 解决GitHub上的README.md文件的图片内容不能正常显示问题
  • Springboot启动报错:Failed to start bean ‘documentationPluginsBootstrapper‘
  • 软件项目体系建设文档,项目开发实施运维,审计,安全体系建设,验收交付,售前资料(word原件)
  • mybatisX插件的使用,以及打包成配置
  • 阿里云 Pod介绍
  • 如何利用Java爬虫批量获取商品信息案例指南