2 Go 常用容器类型

内容概要

  1. 数组(Array
  2. 切片(Slice
  3. string & []byte
  4. map 实现原理
  5. sync.Map 实现原理
  6. gocontiner

1、数组(Array)

数组定义:固定长度、特定类型组成的序列。
内存结构:[4]int{2,3,5,7}

数组内存结构

数组缺点:

  1. 不同长度的数组是不同的类型,无法直接赋值
  2. 长度固定,使用不便
  3. 作为参数传递表现为传值

2、切片(Slice)

切片
package main

import "fmt"

func main() {
    array := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    s := array[2:3]

    fmt.Println(len(s))
    fmt.Println(cap(s))

    fmt.Println(s)

    fmt.Println(&array[2] == &s[0])
}

编译结果:
1
8
[3]
true
package main

import "fmt"

func main() {
    sliceA := []int{1, 2, 3}

    editElem(sliceA)

    fmt.Println(sliceA)
}

func editElem(sliceTmp []int) {
    sliceTmp[1] = 999
}

编译结果:
[1 999 3]

append 扩容规则:

  • if cap < 1024, cap * 2
  • else, cap * 1.25
package main

import "fmt"

func main() {
    slice := []int{1}
    fmt.Println(len(slice), cap(slice))

    slice = append(slice, 1)
    fmt.Println(len(slice), cap(slice))

    slice = append(slice, 1)
    fmt.Println(len(slice), cap(slice))

    slice = append(slice, 1)
    fmt.Println(len(slice), cap(slice))

    slice = append(slice, 1)
    fmt.Println(len(slice), cap(slice))
}

编译结果:
1 1
2 2
3 4
4 4
5 8
package main

import "fmt"

func main() {
    sliceA := make([]int, 4, 5)
    sliceB := addElem(sliceA)

    fmt.Println(sliceA)
    fmt.Println(sliceB)
    fmt.Println(&sliceA[0] == &sliceB[0])

    sliceC := make([]int, 5, 5)
    sliceD := addElem(sliceA)

    fmt.Println(sliceC)
    fmt.Println(sliceD)
    fmt.Println(&sliceC[0] == &sliceD[0])
}

func addElem(tmpSlice []int) []int {
    return append(tmpSlice, 1)
}

编译结果:
[0 0 0 0]
[0 0 0 0 1]
true
[0 0 0 0 0]
[0 0 0 0 1]
false
package main

import "fmt"

func main() {
    sliceA := make([]int, 0, 5)

    addElem(sliceA)

    fmt.Println(sliceA)
}

func addElem(sliceTmp []int) {
    sliceTmp = append(sliceTmp, 999)
}

编译结果:
[]
切片

3、string & []byte

string 对象不可以修改

  • 不包含内存空间:避免内存拷贝
  • 字符串字面量在只读段上分配,无法修改


    数据结构
package main

import "fmt"

func main() {
    a := "abcdefg"
    a = "qwertyuiop"

    fmt.Println(a)
}

编译结果:
qwertyuiop

string / []byte 强转

package main

import "fmt"

func main() {
    a := "qwertyuiop"
    bytes := []byte(a)
    bytes[4] = '6'

    fmt.Println(string(bytes))
}

编译结果:
qwer6yuiop
string / []byte 强转

4、map 实现原理

map 结构由 runtime/map.go/hmap 定义


image.png

hash 冲突导致bucket溢出


image.png

负载因子 = 键数量 / bucket 数量

rehash:

  • 负载因子 > 6.5 (增量扩容)
  • overflow 数量过多 (等量扩容)

增量扩容


image.png

等量扩容


image.png

同一个map连续两次遍历为什么顺序不同?

  • 表面原因:原生遍历map的函数中插入了随机数,导致无序
  • 深层原因:rehash,避免初学者依赖这个遍历结果的顺序

map 并发读写会panic

  • 通过 sync.RWMutex 加读写锁
  • 使用 并发安全的 sync.Map

5、sync.Map 实现原理

image.png

sync/map.go
读写分离,提高并发度

sync.Map 适用场景:读多写少


image.png

6、go 的 continer 包

continer/heap : 小根堆
continer/list : 双向链表
continer/ring : 双向环

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容