Go 语言Slice和Map知识点

149 阅读5分钟

在 Go 语言项目中,Slice 与 Map 是最常用的几个数据结构,虽然常用,但是也是最容易踩坑的,下面整理了下关于Slice和Map的基本知识点,也许会对你有些帮助。

Slice 切片

切片是一种数据结构,这种数据结构便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数 append 来实现的。这个函 数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的大小。因为切片的底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。

  • 使用[...]定义数组,使用[]定义切片
  • 切片之间不能使用 == 或者 != 来比较,可以通过 reflect.DeepEqual 函数来比较
  • 切片可以和nil进行比较
  • 切片通过 append 添加元素时,在超出切片容量时会进行扩容,Go 运行时需要时间来分配新的内存,并将数据从旧的内存空间拷贝到新的内存空间。
  • 在扩容时,规则为切片的容量小于 1024 时,将切片的容量加倍,之后每次扩容到少增加 25%
  • 一个常见的新手错误是使用 append 为切片添加初始元素: x := make([]int, 5) x = append(x, 10),其结果是: [0 0 0 0 0 10]
  • make 函数指定切片长度时,会初始化为0值。
  • 永远不要设置比较长度小的容量。
  • 声明 nil 值的切片:var data []int
  • 声明零值的切片:var data = []int{}
  • 零值切片和nil值切片比较会返回 false
  • 何时使用 make 来声明切片?如果希望设置切片的大小,但却无法确定具体的值时。
  • 调用 make 时的三种情况:
    • 指定非零长度的切片,可以将切片作为缓冲区
    • 如果已知所需大小,可以指定切片长度,然后通过索引设置值。这种方式一般用于将数据从一个切片存储到另一个切片中。但是需要注意,若长度定义错误,切片会在末尾包含额外的零值,或在索引时造成崩溃
    • make 定义零长度并指定容量。通过 append 向切片中添加值。不会因为元素数量偏少而包含多余的零值,也不会因为数量超出造成代码崩溃
  • 以上三种方式,第三种在有的情况下会比较慢(在扩容时),但是最不容易导致问题的。

Map 切片

映射是一个集合,可以使用类似处理数组和切片的方式迭代映射中的元素。但映射是无序的集合,意味着没有办法预测键值对被返回的顺序。即便使用同样的顺序保存键值对,每次迭代映射的时候顺序也可能不一样。无序的原因是映射的实现使用了散列表

  • 映射类型写作 map[keyType]valueType 。
  • 声明映射的几种方式:
    • var nimMap map[keyType]valueType,映射的零值为 nil ,值为 nil 的映射长度为0 。如果尝试对 nil 映射写数据则会导致崩溃
    • 使用 := 并通过映射字面量创建,即 nMap := map[keyType]valueType{}。这种声明方式可以对空映射进行读写操作而不会出现崩溃的情况
    • 如果已知映射中需要多少镇值对,但不知确切的值 ,可以使用 map 来创建一个有默认长度的映射,即 cMap := make([keyType]valueType, 100),但是其长度依然为 0。
  • map对value的类型没有限制,但是对key的类型有严格要求:可以是任何可比较的类型,即必须是可以通过 == 来进行比较的数据类型,所以也意味着切片和映射不能作为映射的键的。
  • map类型不支持“零值可用”,未显式赋初值的map类型变量的零值为nil。
  • 和切片一样,map也是引用类型,将map类型变量作为函数参数传入不会有很大的性能损耗,并且在函数内部对map变量的修改在函数外部也是可见的
  • 映射与切片的相似点
    • 向映射中添加键值对会增加映射的长度
    • 如果已知映射要插入多少键值对,可以通过 make 创建具有特定初始大小的映射
    • 可以使用 len 函数得到映射的键值对的数量
    • 映射的零值为 nil
    • 映射之间也不能直接进行比较,即不能使用 == 或 != 来比较,但是可以同 nil 来比较。
  • 如何在映射和切片之间选择?如果元素之间的顺序很关键时,就使用切片;相反,如果对元素顺序没有要求的话,可以使用映射
  • 因为Go运行时在初始化map迭代器时对起始位置做了随机处理。因此千万不要依赖遍历map所得到的元素次序。如果你需要一个稳定的遍历次序,那么一个比较通用的做法是使用另一种数据结构来按需要的次序保存key,比如切片。
  • map类型更多用在查找和数据读取场合,如何判断键不在映射中?正常情况下,如果键不存在的话,会得到 map 值类型的零值,如果值类型的 int 的话,会因为可能存在 0 而导致无法准确判断。所以需要使用 value, ok := nMap[mapKey],根据 ok 来判断键是否存在。
  • 注意,即便要删除的数据在map中不存在,delete也不会导致panic。
  • Go运行时使用一张哈希表来实现抽象的map类型。运行时实现了map操作的所有功能,包括查找、插入、删除、遍历等。在编译阶段,Go编译器会将语法层面的map操作重写成运行时对应的函数调用