Steins;Lab

  • 项目
  • 折腾
  • 笔记
  • 图册
  • 杂谈
  • 文章索引 - 博主自荐博文
  • 关于/留言
Steins;Lab
某团的自留研究所
  1. 首页
  2. 学习笔记
  3. 正文

100 Go Mistakes 阅读随记 - 02 Data type 数据类型

2022年12月22日 2227点热度 0人点赞 0条评论

100 Go Mistakes and How to Avoid Them 一书主要描述了使用 Go 语言编程时的常见问题。本文是博主对第三章 - 数据类型 Data type 的阅读笔记。

引言

最近发现了一本书籍,叫做 100 Go Mistakes and How to Avoid Them。主要描述了使用 Go 语言编程时的常见问题。
其中第一章文中即很有趣地引用了句谚语:

Tell me and I forget.
Teach me and I remember.
Involve me and I learn.

于是博主决定挑出其中自己阅读过程中印象深刻的示例,结合自己粗浅的经验,跑一跑例程,同时也激励自己勤耕不辍写一点点笔记出来。
笔者不想完全 copy 书中的内容,所以随记尽量精简。如果读者能在碎片化时间中对某些 "Mistakes" 有印象,是本系列笔记的荣幸!同时推荐有时间的读者直接阅读原文。

第 3 章:数据类型 Data type

#18 忽略 integer 溢出

溢出在 c/c++ 中也比较常见。主要原因是 Go 虽然会在编译时检查溢出,但在运行时候忽略潜在的溢出。
在关键的流程控制代码中使用以下防御性编程:

func IncUint(counter uint) uint {
    if counter == math.MaxUint {
        panic("uint overflow")
    }
    return counter + 1
}
func MultiplyInt(a, b int) int {
    if a == 0 || b == 0 {
        return 0
    }
    result := a * b
    if a == 1 || b == 1 {
        return result
    }
    if a == math.MinInt || b == math.MinInt {
        panic("integer overflow")
    }
    if result/b != a {
        panic("integer overflow")
    }
    return result
}

如果涉及到大数且必须精确,需要使用 math/big

#19 不理解浮点数表示原理

此部分和 c/c++ 很像。总之是要注意浮点计算带来的误差,以及写单元测试时,比较浮点数,控制在一个小的 \delta 误差即可。

#20 不理解 slice 的 length 和 capacity

是 Go 语言 slice 的基本原理。

Tips: slice 扩增的时候,小于 1024 个元素每次扩增 1 倍;之后每次扩增 1/4。

#21 低效率的 slice 初始化

slice 的原理和扩充原理本笔记不再赘述。

对于长度固定的情况,可以提前 make slice,避免循环中多次扩充 slice 导致的内存复制。

func convert(foos []Foo) []Bar {
    n := len(foos)
    bars := make([]Bar, n)
    for i, foo := range foos {
        bars[i] = fooToBar(foo)
    }
    return bars
}

#22 不明确 slice 的 empty 和 nil

func main() {
    var s []string
    log(1, s)
    s = []string(nil)
    log(2, s)
    s = []string{}
    log(3, s)
    s = make([]string, 0)
    log(4, s)
}
func log(i int, s []string) {
    fmt.Printf("%d: empty=%t\tnil=%t\n", i, len(s) == 0, s == nil)
}
1: empty=true nil=true
2: empty=true nil=true
3: empty=true nil=false
4: empty=true nil=false

注意:JSON 数据具有 null 和空数组及对象的概念。
json marshel 会根据传入切片是否为 null 有所区别。可能会影响业务序列化/反序列化过程。

{"ID":"foo","Operations":null}
{"ID":"bar","Operations":[]}

另外,reflect.DeepEqual 对两者 slice 也是为不相等的。

#23 判断 slice 是否为空不正确

nil 切片为 len 做了适配。
正确的方式是使用 len() 是否为0判断。

  • 当切片为 nil , len(xxx) == 0
  • 切片不为 nil 但为空时, len(xxx) == 0
    func main() {
    var s1 []string
    if len(s1) == 0 {
        println("s1 is empty")
    }
    s2 := []string(nil)
    if len(s2) == 0 {
        println("s2 is empty")
    }
    }

output:

s1 is empty
s2 is empty

#26 slice 内存泄露

考虑以下代码

func consumeMessages() {
    for {
    msg := receiveMessage()
        // Do something with msg
        storeMessageType(getMessageType(msg))
    }
}
func getMessageType(msg []byte) []byte {
    return msg[:5]
}

如果 receiveMessage() 返回了数量 1000 的 msg 切片回来,由于 slice 后的数组和 GC 机制,getMessageType 取前 5 个元素的操作,导致近千个元素的内存暂时无法回收。会导致潜在的高内存使用问题。
同样地,对于结构体切片,也会导致潜在的高内存使用。

#27 低效的 map 初始化

map 是 Go 语言的一个内置类型。map 的实现原理如下:

  • 有一个 array 桶作为 hash table
  • 通过 hash(key) 来找到具体的 key-value bucket 数据结构指针
  • 一个 bucket 填满后,会链接下一个 bucket
  • array 初始化时大小为 4,遇到一下条件,会翻倍
    • bucket 内元素平均大小超过 load factor = 6.5 (在后续可能会修改,该数字也是 Go 内部经验调优得到)
    • bucket 满的个数超过一定规模

因此各个操作最坏的时间复杂度为

  • insert: O(n),n 为元素总个数
  • read/update/delete: O(p),p 为一个 hash 指针对应 buckets 总的元素个数 (可能有多个 buckets)

因此,我们初始化一个已知大规模的 map,可以用 make:

m := make(map[string]int, 1_000_000)

值得指出的是,make 参数中的 10000000并不是限制了最大个数或指定初始的元素个数,是为了更好的初始化数据结构。我们仍然可以插入超过 10000000 个元素。

Extra:
为什么因子是 6.5 呢? 可看看煎鱼大大的博客
为什么 Go 的负载因子是 6.5?

#28 map 内存泄露

#27 中我们提到了 map 的扩容原理。但是 map 是不能缩容的。
因此一个巨大的 map 清除元素后,仍然会消耗较多的内存。

  • 如果我们要累计大量数据的 map,可以通过定时复制到新的 map 达到变相缩容的效果。

  • 可以通过强行指定 value 为指针类型节省 map 内存。

  • key 或者 value 如果大于 128 bytes,数据结构中会自动使用指针。

#29 错误地比较量值

== 有诸多限制。

  • Bool
  • Numerics: int, float. complex types
  • Strings
  • Channel: 比较两个 channel 是不是在同一次调用创建的,或者是不是都为 nil
  • Interfaces: 比较两者是不是有一致的动态类型和动态值,或者是不是都为 nil
  • Pointers: 比较两者是不是统一地址空间,或者同为 nil
  • Structs/arrays: 比较两者是否相同元素组成。
    • 结构体内部成员必须都是可以比较的

因此像如下带有 slice 的结构体会被认为是不可比较的。

type customer struct {
    id string
    operations []float64
}
func main() {
    cust1 := customer{id: "x", operations: []float64{1.}}
    cust2 := customer{id: "x", operations: []float64{1.}}
    fmt.Println(cust1 == cust2)
}
invalid operation:
cust1 == cust2 (struct containing []float64 cannot be compared)

可以使用反射来比较 reflect.DeepEqual。但是效率很低,一般不在运行环境中使用,在 unit test 环境中使用得比较多。注意,其有较为详细的比较规则,使用之前要详细阅读文档。
如果需要比较结构体,可以自己实现一个 Equal() 方法,逐个元素比较。

相关

标签: 暂无
最后更新:2023年1月18日

SPtuan

SPtuan 是一名普通的工程师,最大的愿望是度过平静的时光。 当前从事网络/CDN/对象存储研发。

点赞
< 上一篇
下一篇 >
0 0 votes
文章评分
Subscribe
Login
提醒
guest

guest

0 评论
最新
最旧 得票最多
Inline Feedbacks
View all comments

SPtuan

SPtuan 是一名普通的工程师,最大的愿望是度过平静的时光。
当前从事网络/CDN/对象存储研发。

  • 引言
  • 第 3 章:数据类型 Data type
    • #18 忽略 integer 溢出
    • #19 不理解浮点数表示原理
    • #20 不理解 slice 的 length 和 capacity
    • #21 低效率的 slice 初始化
    • #22 不明确 slice 的 empty 和 nil
    • #23 判断 slice 是否为空不正确
    • #26 slice 内存泄露
    • #27 低效的 map 初始化
    • #28 map 内存泄露
    • #29 错误地比较量值
分类
  • Uncategorized
  • 图册
  • 学习笔记
  • 库
  • 折腾
  • 杂谈
  • 瞎**扯
  • 碎碎念
  • 项目跟踪
最近评论
SPtuan 发布于 2 个月前(03月22日) 书签: 关于 disk-io 的经验, 异步/同步 io 系统设计的经验 https://you...
SPtuan 发布于 2 个月前(03月21日) 如果公司不是对外提供这些服务的,这种岗位都是 infra 部门,平均年龄确实会大一些。尤其构建和维护...
HUA 发布于 2 个月前(03月19日) 想请问博主对于国内CDN行业,以及CDN调度、DNS托管类服务相关岗位的看法,以及是否还推荐校招新人...
SPtuan 发布于 3 个月前(02月03日) 2025 注: 长辈对于只身去深圳的担忧,更多地来自于 80s/90s 治安情况。近几年了解了严打...
SPtuan 发布于 4 个月前(01月16日) 哈哈,100就100吧,新年快乐!
热门主题 & 页面
  • 全球互联网拓扑探索 (1) : 互联网是如何工作的
  • Intel Movidius Neural Compute Stick - 英特尔Movidius神经计算棒上手体验
  • iowait 到底是什么?
  • 使用 WSL2 + X11 转发 - 在 Windows10 中打造 GNU/Linux 学习生产环境
  • 动手做!基于nRF24L01P的Arduino无线通信
归档
  • 2025 年 5 月
  • 2025 年 3 月
  • 2024 年 12 月
  • 2024 年 9 月
  • 2024 年 8 月
  • 2024 年 5 月
  • 2024 年 3 月
  • 2024 年 2 月
  • 2023 年 12 月
  • 2023 年 11 月
  • 2023 年 9 月
  • 2023 年 8 月
  • 2023 年 4 月
  • 2023 年 1 月
  • 2022 年 12 月
  • 2022 年 10 月
  • 2022 年 9 月
  • 2022 年 7 月
  • 2022 年 6 月
  • 2022 年 2 月
  • 2021 年 12 月
  • 2021 年 11 月
  • 2021 年 2 月
  • 2021 年 1 月
  • 2020 年 9 月
  • 2020 年 4 月
  • 2020 年 3 月
  • 2020 年 1 月
  • 2019 年 8 月
  • 2019 年 7 月
  • 2019 年 5 月
  • 2019 年 4 月
  • 2019 年 3 月
  • 2019 年 2 月
  • 2018 年 12 月
  • 2018 年 10 月
  • 2018 年 9 月
  • 2018 年 8 月
  • 2018 年 5 月
  • 2018 年 2 月
  • 2018 年 1 月
  • 2017 年 11 月
  • 2017 年 9 月
  • 2017 年 7 月
  • 2017 年 6 月
  • 2017 年 5 月
  • 2017 年 4 月
  • 2017 年 3 月
  • 2017 年 2 月
  • 2017 年 1 月
  • 2016 年 12 月
  • 2016 年 11 月
  • 2016 年 10 月
  • 2016 年 9 月
  • 2016 年 8 月
  • 2016 年 7 月
  • 2016 年 6 月
  • 2016 年 5 月
  • 2016 年 4 月
  • 2016 年 3 月
  • 2016 年 2 月
  • 2016 年 1 月
  • 2015 年 12 月
  • 2015 年 11 月
  • 2015 年 9 月

友情链接:

Blessing Studio hahaschool 绘枫和畅 魔法少女Fandy monsterx Clarke的博客 Luminous’ Home Shintaku's Blog
蓝黑的博客 haruhi.club Yida的博客 Bo2SS 涛叔 TangBao 同和君Hocassian

Steins;Lab 团子神社 zdfmc.net

steinslab.io built with ❤. Thanks for all 2015-2025.

Theme Kratos Made By Seaton Jiang

wpDiscuz