Featured image of post 《Go专家编程》读书笔记 | 第1章 常见数据结构的实现原理 | 记忆篇

《Go专家编程》读书笔记 | 第1章 常见数据结构的实现原理 | 记忆篇

数据结构篇🟠chan🟠slice🟠map🟠struct🟠iota🟠string🟠sync.Map

系列文章

同系列其它文章

全部问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--------------------------------------------------------------------------------------
| ID | UpdateTime          | Question
--------------------------------------------------------------------------------------
| 1  | 2024-02-18 22:13:39 | chan
| 2  | 2024-02-19 16:09:05 | slice
| 3  | 2024-02-19 20:01:07 | map
| 4  | 2024-02-19 23:35:18 | struct
| 5  | 2024-02-20 13:20:05 | iota
| 6  | 2024-02-20 13:30:42 | string
| 7  | 2024-02-20 14:20:59 | sync.Map
--------------------------------------------------------------------------------------

chan

🌞 ︎阳历 2024年02月18日 星期日 | 🌙农历 龙年(2024)正月初九 | 🕛晚上 10点13分39秒 | 更新 🏳️‍🌈

全部知识点

1
2
3
4
5
6
7
8
--------------------------------------------------------------------------------------
├── 1.chan简介
├── 2.chan特征
├── 3.如何判断管道是否关闭?
├── 4.chan用法
├── 5.chan原理
├── 6.读等待队列和写等待队列会同时存在协程吗?| 什么情况下读等待队列和写等待队列会同时存在协程
--------------------------------------------------------------------------------------
chan简介
  1. 管道时Go在语言层面提供的协程间的通信方式,比UNIX管道更易用也更轻便。
chan特征
  1. FIFO,先入先出。
  2. 协程读取管道时,阻塞的条件有3个:①管道无缓冲区②管道缓冲区中无数据③管道为nil。
  3. 协程写入管道时,阻塞的条件有3个:①管道无缓冲区②管道缓冲区已满③管道为nil。
  4. panic的情况有两种,①向关闭的管道写数据会触发panic②关闭已经关闭的管道。
  5. 管道关闭后,仍然可以读取数据。
  6. 使用逗号模式(val,ok := <-ch1)从管道中读取数据,ok为bool类型,表示是否读取到数据。
  7. 管道关闭&管道缓冲区中无数据 ⇌ ok为false;管道关闭后读取管道需通过ok判断本次是否读取到数据,以防返回零值,误操作。
如何判断管道是否关闭?
  1. 可以根据逗号表达式的第二个返回值来判断,但有一定的限制;管道已经关闭且缓冲区中没有数据时,逗号表达式的第二个返回值才和管道关闭状态一致。
  2. 因为逗号表达式的第二个返回值的含义是是否从管道中读取到了数据。
chan用法
  1. 使用内置函数make()创建chan。
  2. 使用内置函数len()和cap()分别查询chan的长度和容量。
  3. 使用<-(箭头符号)读取和写入管道,管道在左表示向管道中写入数据,管道在右表示从管道中读取数据。
  4. 使用内置函数close()关闭管道。
  5. 单向管道,通过在函数参数或返回值中使用<-(箭头符号)来限定管道的只读或只写。
  6. 配合select使用,select可监控多个管道,当其中一个管道可操作时就触发响应的case分支,实现go在语言层面的多路复用机制。
  7. 使用for-range可以持续从管道中读取数据,好像在遍历一个数组一样,当管道中没有数据时,当前协程会阻塞;管道被关闭了,for-range也可以优雅结束。
chan原理
  1. chan的源码在go的runtime包下;src/runtime/chan.go:hchan;chan底层是一个结构体类型,从数据结构可以看出chan由环形队列、类型信息、协程等待队列、互斥锁等组成。
  2. 环形队列。chan内部实现了一个环形队列作为其缓冲区,队列的长度是创建chan时指定的。
  3. 等待队列。读阻塞的协程加入读等待队列,写阻塞的协程加入写等待队列;因读阻塞的协程会被向管道写入数据的协程唤醒,因写阻塞的协程会被从管道中读取数据的协程唤醒。
  4. 类型信息。chan结构体中维护了元素的类型信息,chan创建后只能传递一种类型的数据,如果想要传递任意类型的数据,则可以使用interface{}类型。
  5. 互斥锁。因为互斥锁的存在,一个管道同时仅允许一个协程读写,因此chan是并发安全的。
读等待队列和写等待队列会同时存在协程吗?| 什么情况下读等待队列和写等待队列会同时存在协程
  1. 会 | 同一个协程使用select向管道一边写数据,一边读数据。

示例代码

源码链接 stdlib/runtime/chan/features_and_usages/memory/001_test.go

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package memory

import (
  "fmt"
  "testing"
  "time"

  commonprint "github.com/gainovel/testcase/tools/common/print"
)

var (
  myfmt = commonprint.MyFmt
)

func sleepG() {
  time.Sleep(time.Hour)
}
func TestName_2024_01_09_14_59_43(t *testing.T) {
  // chan的基本用法
  // go test -v -run TestName_2024_01_09_14_59_43/chan_crud github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  t.Run("chan crud", func(t *testing.T) {
    var (
      ch1 chan int
      val int
      ok  bool
    )
    myfmt.VarInitPrintln(`var (
  ch1 chan int
  val int
  ok  bool
)`)
    // 内置函数make()进行初始化,可创建无缓冲管道和有缓冲管道
    ch1 = make(chan int, 2)

    // 向管道ch1中写数据1
    ch1 <- 1

    // len(ch1):缓冲区中元素个数 | cap(ch):缓冲区容量
    myfmt.ColorDescPrintln("ch1 = make(chan int, 2);ch1 <- 1")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1))

    // 向管道ch1中写数据1
    ch1 <- 2
    myfmt.ColorDescPrintln("ch1 <- 2")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1))

    // 从管道中读取数据,存入ch1中
    val = <-ch1
    myfmt.ColorDescPrintln("val = <-ch1")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1), "val", val)

    // 使用逗号模式从管道中读取数据,ok为bool类型,表示是否读取到数据
    val, ok = <-ch1
    myfmt.ColorDescPrintln("val, ok = <-ch1")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1), "val", val, "ok", ok)

    // 管道关闭&管道缓冲区中无数据 ⇌ ok为false
    // 管道关闭后读取管道需通过ok判断本次是否读取到数据,以防返回零值,误操作
    close(ch1)
    val, ok = <-ch1
    myfmt.ColorDescPrintln("val, ok = <-ch1")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1), "val", val, "ok", ok)
  })

  // 协程读取管道时,阻塞的条件有3个:①管道无缓冲区②管道缓冲区中无数据③管道为nil。
  // go test -v -run TestName_2024_01_09_14_59_43/1.read_no_buf github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  t.Run("1.read no buf", func(t *testing.T) {
    go sleepG()
    var (
      ch1 chan int
    )
    ch1 = make(chan int)
    fmt.Println("管道ch1无缓冲区,读取阻塞...")
    <-ch1
    fmt.Println("before return...")
  })
  // go test -v -run TestName_2024_01_09_14_59_43/2.read_no_data github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  t.Run("2.read no data", func(t *testing.T) {
    go sleepG()
    var (
      ch1 chan int
    )
    ch1 = make(chan int, 2)
    fmt.Println("管道ch1缓冲区中无数据,读取阻塞...")
    <-ch1
    fmt.Println("before return...")
  })
  // go test -v -run TestName_2024_01_09_14_59_43/3.read_nil_chan github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  t.Run("3.read nil chan", func(t *testing.T) {
    go sleepG()
    var (
      ch1 chan int
    )
    fmt.Println("管道ch1为nil,读取阻塞...")
    <-ch1
    fmt.Println("before return...")
  })

  // 协程写入管道时,阻塞的条件有3个:①管道无缓冲区②管道缓冲区已满③管道为nil。
  // go test -v -run TestName_2024_01_09_14_59_43/1.write_no_buf github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  t.Run("1.write no buf", func(t *testing.T) {
    go sleepG()
    var (
      ch1 chan int
    )
    ch1 = make(chan int)
    fmt.Println("管道ch1无缓冲区,写入阻塞...")
    ch1 <- 1
    fmt.Println("before return...")
  })
  // go test -v -run TestName_2024_01_09_14_59_43/2.write_full_data github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  t.Run("2.write full data", func(t *testing.T) {
    go sleepG()
    var (
      ch1 chan int
    )
    ch1 = make(chan int, 2)
    ch1 <- 1
    ch1 <- 1
    fmt.Println("管道ch1缓冲区已满,写入阻塞...")
    ch1 <- 1
    fmt.Println("before return...")
  })
  // go test -v -run TestName_2024_01_09_14_59_43/3.write_nil_chan github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  t.Run("3.write nil chan", func(t *testing.T) {
    go sleepG()
    var (
      ch1 chan int
    )
    fmt.Println("管道ch1为nil,写入阻塞...")
    ch1 <- 1
    fmt.Println("before return...")
  })

  // panic的情况有两种,①向关闭的管道写数据会触发panic②关闭已经关闭的管道

  // go test -v -run TestName_2024_01_09_14_59_43/1.write_to_a_closed_chan github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  // panic: send on closed channel
  t.Run("1.write to a closed chan", func(t *testing.T) {
    var (
      ch1 chan int
    )
    ch1 = make(chan int, 1)
    close(ch1)
    ch1 <- 1
  })
  // go test -v -run TestName_2024_01_09_14_59_43/2.close_a_closed_chan github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  // panic: close of closed channel
  t.Run("2.close a closed chan", func(t *testing.T) {
    var (
      ch1 chan int
    )
    ch1 = make(chan int, 1)
    close(ch1)
    close(ch1)
  })

  // 管道关闭后,仍然可以读取数据
  // 使用逗号模式(val,ok := <-ch1)从管道中读取数据,ok为bool类型,表示是否读取到数据
  // 管道关闭&管道缓冲区中无数据 ⇌ ok为false;管道关闭后读取管道需通过ok判断本次是否读取到数据,以防返回零值,误操作

  // go test -v -run TestName_2024_01_09_14_59_43/read_from_a_close_chan github.com/gainovel/testcase/stdlib/runtime/chan/features_and_usages/memory
  t.Run("read from a close chan", func(t *testing.T) {
    var (
      ch1 chan int
      val int
      ok  bool
    )
    ch1 = make(chan int, 2)
    ch1 <- 1
    ch1 <- 2
    close(ch1)
    myfmt.VarInitPrintln(`var (
  ch1 chan int
  val int
  ok  bool
)`)
    myfmt.ColorDescPrintln("ch1 = make(chan int, 2);ch1 <- 1;ch1 <- 2;close(ch1)")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1))
    val, ok = <-ch1
    myfmt.ColorDescPrintln("val, ok = <-ch1")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1), "val", val, "ok", ok)
    val, ok = <-ch1
    myfmt.ColorDescPrintln("val, ok = <-ch1")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1), "val", val, "ok", ok)
    val, ok = <-ch1
    myfmt.ColorDescPrintln("val, ok = <-ch1")
    myfmt.KeyValuePrintln("len(ch1)", len(ch1), "cap(ch1)", cap(ch1), "val", val, "ok", ok)
  })
}

输出结果

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
**************************************************************************************
* 测试用例运行方法
*  0.前置要求安装goversion>=1.20),安装make可选
*  1.git clone git@gitee.com:gainovel/go-test-case.git 👉 cd go-test-case
*    👉 cd docs/tests/stdlib/runtime/chan.md
*    👉 找到对应的命令依次复制执行即可回到根目录go-test-case执行
*  2.或者直接打开测试文件stdlib/runtime/chan/features_and_usages/memory/001_test.go
*    每个子测试上都有对应的命令直接执行即可回到根目录go-test-case执行
*  3.  注意所有命令都在根目录下执行
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/chan_crud -f Makefiles/stdlib/runtime/chan.mk
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  var (
*          ch1 chan int
*          val int
*          ok  bool
*  )
*  --------------------------------------------------------------------------------
*  👇
*  ch1 = make(chan int, 2);ch1 <- 1
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 1
*  |          cap(ch1) | 2
*  --------------------------------------------------------------------------------
*  👇
*  ch1 <- 2
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 2
*  |          cap(ch1) | 2
*  --------------------------------------------------------------------------------
*  👇
*  val = <-ch1
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 1
*  |          cap(ch1) | 2
*  |               val | 1
*  --------------------------------------------------------------------------------
*  👇
*  val, ok = <-ch1
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 0
*  |          cap(ch1) | 2
*  |               val | 2
*  |                ok | true
*  --------------------------------------------------------------------------------
*  👇
*  val, ok = <-ch1
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 0
*  |          cap(ch1) | 2
*  |               val | 0
*  |                ok | false
*  --------------------------------------------------------------------------------
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/1.read_no_buf -f Makefiles/stdlib/runtime/chan.mk
*  Test Result: 管道ch1无缓冲区读取阻塞...
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/2.read_no_data -f Makefiles/stdlib/runtime/chan.mk
*  Test Result: 管道ch1缓冲区中无数据读取阻塞...
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/3.read_nil_chan -f Makefiles/stdlib/runtime/chan.mk
*  Test Result: 管道ch1为nil读取阻塞...
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/1.write_no_buf -f Makefiles/stdlib/runtime/chan.mk
*  Test Result: 管道ch1无缓冲区写入阻塞...
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/2.write_full_data -f Makefiles/stdlib/runtime/chan.mk
*  Test Result: 管道ch1缓冲区已满写入阻塞...
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/3.write_nil_chan -f Makefiles/stdlib/runtime/chan.mk
*  Test Result: 管道ch1为nil写入阻塞...
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/1.write_to_a_closed_chan -f Makefiles/stdlib/runtime/chan.mk
*  Test Result: panic: send on closed channel
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/2.close_a_closed_chan -f Makefiles/stdlib/runtime/chan.mk
*  Test Result: panic: close of closed channel
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/read_from_a_close_chan -f Makefiles/stdlib/runtime/chan.mk
*  Test Result:
*  👇 变量初始化
*  --------------------------------------------------------------------------------
*  var (
*          ch1 chan int
*          val int
*          ok  bool
*  )
*  --------------------------------------------------------------------------------
*  👇
*  ch1 = make(chan int, 2);ch1 <- 1;ch1 <- 2;close(ch1)
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 2
*  |          cap(ch1) | 2
*  --------------------------------------------------------------------------------
*  👇
*  val, ok = <-ch1
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 1
*  |          cap(ch1) | 2
*  |               val | 1
*  |                ok | true
*  --------------------------------------------------------------------------------
*  👇
*  val, ok = <-ch1
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 0
*  |          cap(ch1) | 2
*  |               val | 2
*  |                ok | true
*  --------------------------------------------------------------------------------
*  👇
*  val, ok = <-ch1
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |          len(ch1) | 0
*  |          cap(ch1) | 2
*  |               val | 0
*  |                ok | false
*  --------------------------------------------------------------------------------
**************************************************************************************

slice

🌞 ︎阳历 2024年02月19日 星期一 | 🌙农历 龙年(2024)正月初十 | 🕛下午 4点09分05秒 | 更新 🏳️‍🌈

全部知识点

1
2
3
4
5
6
7
--------------------------------------------------------------------------------------
├── 1.slice简介
├── 2.slice特征
├── 3.slice用法
├── 4.slice原理
├── 5.slice使用心得
--------------------------------------------------------------------------------------
slice简介
  1. slice又称动态数组,依托数组实现,可以方便的进行扩容和传递,实际使用中比数组更加灵活。
slice特征
  1. 并发不安全。
  2. panic的情况:①索引超出范围。
  3. 多个slice可能共享同一底层数组。
slice用法
  1. 内置函数make()进行初始化,可指定slice长度和容量(即底层数组的长度)。
  2. slice copy。copy()方法用于将源slice中的数据拷贝到目标slice,拷贝的元素个数取源slice和`目标slice元素个数的最小值,拷贝的过程,从第一个元素开始,直到达到拷贝的元素个数,拷贝的过程中不会发生扩容。
  3. 切片操作。①简单表达式(s1[low:high]),简单表达式切片并未限制新的slice的容量,也就是说通过内置函数append()添加新元素时有覆盖原数组或者原slice元素的风险;②扩展表达式(s1[low:high:max]),扩展表达式切片限制了新的slice的容量,相当于一个被封印了的切片,不会存在覆盖的风险。
  4. 切片操作举例。①简单表达式,对数组[⚪,🟡,⚪,⚪,🟢,⚪,⚪]切片,low=🟡,high=🟢,虽然新slice的len从🟡到🟢,但cap却是从🟡到末尾,append()添加新元素时🟢之后的空间有被覆盖的风险;②扩展表达式,对数组[⚪,🟡,⚪,⚪,⚪,🟢,🟠,⚪,⚪]切片,low=🟡,high=🟢,max=🟠,len从🟡到🟢,cap从🟡到🟠,append()添加新元素时🟠之后的空间没有被覆盖的风险,至于🟢到🟠的这一块是我们自己控制的,默认可覆盖,我们完全可以让🟢=🟠。
slice原理
  1. slice的源码在go的runtime包下,src/runtime/slice.go:slice;从数据结构看slice很清晰,array指针执行底层数组,len表示切片长度,cap表示底层数组容量。
  2. slice扩容。使用append向slice追加元素时,如果slice容量不足,则会触发slice扩容;扩容实际上是分配一块更大的内存,将原slice数据拷贝进新slice,然后返回新slice,扩容后再将数据追加进去。
  3. slice扩容规则。①slice扩容时有几个关键的值需要提前说明一下👉oldCap:扩容前容量、oldLen:扩容前元素个数、cap:扩容所需最小容量、newCap:预估容量②Go1.15扩容规则👉如果oldCap(扩容前的容量)翻倍之后还是小于cap(扩容所需最小容量),那么newCap(预估容量)就等于cap(扩容所需最小容量),如果不满足第一条,而且oldLen(扩容前元素个数)小于1024,那么newCap(预估容量)=oldCap(扩容前的容量)*2,如果不满足第一条,而且oldLen(扩容前元素个数)大于等于1024,那就循环扩容四分之一,直到大于等于所需最小容量③Go1.16扩容规则👉Go1.16中有了些变化,和1024比较的不再是oldLen(扩容前元素个数),而是oldCap(扩容前的容量);Go1.18扩容规则👉到了Go1.18时,又改成不和1024比较了,而是和256比较;并且扩容的增量也有所变化,除了每次扩容1/4,还得加上256*3/4。
  4. 从go1.15到go1.18 slice扩容规则的变化可以看出Go在slice扩容上的一些权衡;当切片较小时,采用较大的扩容倍速,可以避免频繁地扩容,从而减少内存分配次数和数据拷贝的代价;当切片较大时,采用较小的扩容倍速,主要是避免空间浪费。总结一下就是两方面:①slice性能②slice空间使用率;提高slice的综合性能。
  5. 预估容量不一定为最终申请的容量;Go的内存管理,申请内存时都有一定的规格(8,16,32,48…),e.g. int类型slice,预估容量为5,64位操作系统,需要申请40字节,但内存规格中不存在40,Go的内存管理会帮我们匹配到足够大、且最接近的规格48,最终申请的容量是6。
slice使用心得
  1. 创建切片时可根据实际需要预分配容量,尽量避免使用内置函数append()追加过程中出现扩容,有利于提升性能。
  2. 切片拷贝时需要判断实际拷贝的元素个数。
  3. 谨慎使用多个切片操作同一个底层数组,以防读写冲突。

Go1.15~Go.18 slice扩容规则流程图

oldCap:扩容前容量
oldLen:扩容前元素个数
cap:扩容所需最小容量
newCap:预估容量

Go1.15

Go1.15扩容规则流程图

block-beta columns 4 space a["newCap"] space:2 space:4 space b["if (oldCap*2 < cap)"] space:2 space:4 c_1["newCap=cap"] space c_2["if (oldLen<1024)"] space space:4 space d_1["newCap=oldCap*2"] space d_2["while(newCap < cap)"] space:4 space:4 space:3 e["newCap = oldCap*1.25 "] space:4 f["newCap"] a-->b b--"yes"-->c_1 b--"no"-->c_2 c_1-->f c_2--"yes"-->d_1 c_2--"no"-->d_2 d_1-->f d_2--"no"-->f d_2--"yes"-->e e-->d_2

Go1.16

Go1.16扩容规则流程图

block-beta columns 4 space a["newCap"] space:2 space:4 space b["if (oldCap*2 < cap)"] space:2 space:4 c_1["newCap=cap"] space c_2["if (oldCap<1024)"] space space:4 space d_1["newCap=oldCap*2"] space d_2["while(newCap < cap)"] space:4 space:4 space:3 e["newCap = oldCap*1.25 "] space:4 f["newCap"] a-->b b--"yes"-->c_1 b--"no"-->c_2 c_1-->f c_2--"yes"-->d_1 c_2--"no"-->d_2 d_1-->f d_2--"no"-->f d_2--"yes"-->e e-->d_2

Go1.18

Go1.18扩容规则流程图

block-beta columns 4 space a["newCap"] space:2 space:4 space b["if (oldCap*2 < cap)"] space:2 space:4 c_1["newCap=cap"] space c_2["const threshold = 256 "] space space:4 space:2 d_1["if (oldCap<1024)"] space space:4 space e_1["newCap=oldCap*2"] space e_2["while(newCap < cap)"] space:4 space:4 space:3 f["newcap = newcap/4 + 3*threshold/4"] space:4 g["newCap"] a-->b b--"yes"-->c_1 b--"no"-->c_2 c_1-->g d_1--"yes"-->e_1 d_1--"no"-->e_2 e_1-->g e_2--"no"-->g e_2--"yes"-->f f-->e_2

Go1.15~Go.18 slice扩容规则流程图

oldCap:扩容前容量
oldLen:扩容前元素个数
cap:扩容所需最小容量
newCap:预估容量

Go1.15

Go1.15扩容规则流程图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
                       +-------------+                                                 
                       |    newCap   |                                                 
                       +-------------+                                                 
                              |                                                        
                              V                                                        
                 Y /---------------------\ N                                           
         +---------|    oldCap*2 < cap   |---------+                                   
         |         \---------------------/         |                                   
         |                                         |                                   
         V                                         V                                   
+-----------------+                     Y /-----------------\ N                        
|    newCap=cap   |               +-------|   oldLen<1024   |-------+                  
+-----------------+               |       \-----------------/       |                  
         |                        |                                 |                  
         |                        V                                 V                  
         |             +---------------------+           N /-----------------\         
         |             |   newCap=oldCap*2   |    +--------|    newCap<cap   |<-------+
         |             +---------------------+    |        \-----------------/        |
         |                        |               |                 | Y               |
         |                        |               |                 V                 |
         |                        |               |   +---------------------------+   |
         |                        |               |   |   newCap  = oldCap*1.25   |---+
         |                        |               |   +---------------------------+    
         |                        |               |                                    
         |                        |               |                                    
         |                        |               +---------------->O                  
         |                        |                                 |                  
         |                        +--------------->O<---------------+                  
         |                                         |                                   
         |                                         |                                   
         |             +-------------+             |                                   
         +------------>|    newCap   |<------------+                                   
                       +-------------+                                                 

Go1.16

Go1.16扩容规则流程图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
                       +-------------+                                                 
                       |    newCap   |                                                 
                       +-------------+                                                 
                              |                                                        
                              V                                                        
                 Y /---------------------\ N                                           
         +---------|    oldCap*2 < cap   |---------+                                   
         |         \---------------------/         |                                   
         |                                         |                                   
         V                                         V                                   
+-----------------+                     Y /-----------------\ N                        
|    newCap=cap   |               +-------|   oldCap<1024   |-------+                  
+-----------------+               |       \-----------------/       |                  
         |                        |                                 |                  
         |                        V                                 V                  
         |             +---------------------+           N /-----------------\         
         |             |   newCap=oldCap*2   |    +--------|    newCap<cap   |<-------+
         |             +---------------------+    |        \-----------------/        |
         |                        |               |                 | Y               |
         |                        |               |                 V                 |
         |                        |               |   +---------------------------+   |
         |                        |               |   |   newCap  = oldCap*1.25   |---+
         |                        |               |   +---------------------------+    
         |                        |               |                                    
         |                        |               |                                    
         |                        |               +---------------->O                  
         |                        |                                 |                  
         |                        +--------------->O<---------------+                  
         |                                         |                                   
         |                                         |                                   
         |             +-------------+             |                                   
         +------------>|    newCap   |<------------+                                   
                       +-------------+                                                 

Go1.18

Go1.18扩容规则流程图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
                        +-------------+                                                           
                        |    newCap   |                                                           
                        +-------------+                                                           
                               |                                                                  
                               V                                                                  
                  Y /---------------------\ N                                                     
         +----------|    oldCap*2 < cap   |----------+                                            
         |          \---------------------/          |                                            
         |                                           |                                            
         V                                           V                                            
+-----------------+                    +---------------------------+                              
|    newCap=cap   |                    |   const threshold = 256   |                              
+-----------------+                    +---------------------------+                              
         |                                           |                                            
         |                                           V                                            
         |                                Y /-----------------\ N                                 
         |                       +----------|    oldLen<256   |----------+                        
         |                       |          \-----------------/          |                        
         |                       |                                       |                        
         |                       V                                       V                        
         |            +---------------------+                 N /-----------------\               
         |            |   newCap=oldCap*2   |    +--------------|    newCap<cap   |<-------------+
         |            +---------------------+    |              \-----------------/              |
         |                       |               |                       | Y                     |
         |                       |               |                       V                       |
         |                       |               |   +---------------------------------------+   |
         |                       |               |   |   newcap = newcap/4 + 3*threshold/4   |---+
         |                       |               |   +---------------------------------------+    
         |                       |               |                                                
         |                       |               |                                                
         |                       |               +---------------------->O                        
         |                       |                                       |                        
         |                       +------------------>O<------------------+                        
         |                                           |                                            
         |                                           |                                            
         |              +-------------+              |                                            
         +------------->|    newCap   |<-------------+                                            
                        +-------------+                                                           

示例代码

源码链接 stdlib/runtime/slice/features_usages_001_test.go

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package slice000

import (
  "testing"

  commonprint "github.com/gainovel/testcase/tools/common/print"
)

var (
  myfmt = commonprint.MyFmt
)

func TestName_2024_01_09_18_10_28(t *testing.T) {

  // panic的情况:①索引超出范围。
  // go test -v -run TestName_2024_01_09_18_10_28/index_out_of_range github.com/gainovel/testcase/stdlib/runtime/slice
  // Result👉panic: runtime error: index out of range [1] with length 1
  t.Run("index out of range", func(t *testing.T) {
    var (
      s1 []int
    )
    s1 = make([]int, 0, 5)
    s1 = append(s1, 1)
    myfmt.VarInitPrintln(`var (
  s1 []int
)`)
    myfmt.ColorDescPrintln("s1 = make([]int, 0, 5);s1 = append(s1, 1)")
    myfmt.KeyValuePrintln("s1", s1, "s1[0]", s1[0])
    myfmt.KeyValuePrintln("s1[1]", s1[1])
  })

  // 1.多个slice可能共享同一底层数组。
  // 2.简单表达式(s1[low:high]),简单表达式切片并未限制新的slice的容量,
  //  也就是说通过内置函数append()添加新元素时有覆盖原数组或者原slice元素的风险
  // go test -v -run TestName_2024_01_09_18_10_28/share_array github.com/gainovel/testcase/stdlib/runtime/slice
  t.Run("share array", func(t *testing.T) {
    var (
      s1   []int
      s1_1 []int
      s2   []int
      s2_1 []int
    )
    s1 = []int{1, 2, 3, 4, 5}
    s2 = []int{1, 2, 3, 4, 5}
    s1_1 = s1[1:3]
    s2_1 = s2[1:3:3]
    myfmt.VarInitPrintln(`var (
  s1   []int
  s1_1 []int
  s2   []int
  s2_1 []int
)`)
    myfmt.ColorDescPrintln("s1 = []int{1, 2, 3, 4, 5};s1_1 = s1[1:3]", "s2 = []int{1, 2, 3, 4, 5};s2_1 = s2[1:3:3]")
    myfmt.KeyValuePrintln("s1", s1, "len(s1)", len(s1), "cap(s1)", cap(s1),
      "s1_1", s1_1, "len(s1_1)", len(s1_1), "cap(s1_1)", cap(s1_1))
    myfmt.KeyValuePrintln(
      "s2", s2, "len(s2)", len(s2), "cap(s2)", cap(s2),
      "s2_1", s2_1, "len(s2_1)", len(s2_1), "cap(s2_1)", cap(s2_1))
    s1_1 = append(s1_1, 100)
    s2_1 = append(s2_1, 100)
    myfmt.ColorDescPrintln("s1_1 = append(s1_1, 100)", "s2_1 = append(s2_1, 100)")
    myfmt.KeyValuePrintln("s1", s1, "len(s1)", len(s1), "cap(s1)", cap(s1),
      "s1_1", s1_1, "len(s1_1)", len(s1_1), "cap(s1_1)", cap(s1_1))
    myfmt.KeyValuePrintln(
      "s2", s2, "len(s2)", len(s2), "cap(s2)", cap(s2),
      "s2_1", s2_1, "len(s2_1)", len(s2_1), "cap(s2_1)", cap(s2_1))
  })

  // go test -v -run TestName_2024_01_09_18_10_28/slice_copy github.com/gainovel/testcase/stdlib/runtime/slice
  t.Run("slice copy", func(t *testing.T) {
    var (
      s1 []int
      s2 []int
    )
    s1 = []int{1, 2, 3, 4, 5}
    s2 = []int{100, 100, 100}
    myfmt.VarInitPrintln(`var (
  s1 []int
  s2 []int
)`)
    myfmt.ColorDescPrintln("s1 = []int{1, 2, 3, 4, 5}", "s2 = []int{100, 100, 100}")
    myfmt.KeyValuePrintln("s1", s1, "s2", s2)
    copy(s2, s1)
    myfmt.ColorDescPrintln("copy(s2, s1)")
    myfmt.KeyValuePrintln("s1", s1, "s2", s2)
    s1 = []int{1, 2, 3, 4, 5}
    s2 = []int{100, 100, 100}
    myfmt.ColorDescPrintln("s1 = []int{1, 2, 3, 4, 5}", "s2 = []int{100, 100, 100}")
    myfmt.KeyValuePrintln("s1", s1, "s2", s2)
    copy(s1, s2)
    myfmt.ColorDescPrintln("copy(s1, s2)")
    myfmt.KeyValuePrintln("s1", s1, "s2", s2)
  })

  // slice扩容规则:
  //1.slice扩容时有几个关键的值需要提前说明一下:
  //  👉oldCap:扩容前容量、oldLen:扩容前元素个数、cap:扩容所需最小容量、newCap:预估容量
  //2.Go1.15扩容规则👉如果oldCap(扩容前的容量)翻倍之后还是小于cap(扩容所需最小容量),
  //  那么newCap(预估容量)就等于cap(扩容所需最小容量),
  //  如果不满足第一条,而且oldLen(扩容前元素个数)小于1024,那么newCap(预估容量)=oldCap(扩容前的容量)*2,
  //  如果不满足第一条,而且oldLen(扩容前元素个数)大于等于1024,那就循环扩容四分之一,直到大于等于所需最小容量
  //3.Go1.16扩容规则👉Go1.16中有了些变化,和1024比较的不再是oldLen(扩容前元素个数),而是oldCap(扩容前的容量);
  //4.Go1.18扩容规则👉到了Go1.18时,又改成不和1024比较了,而是和256比较;并且扩容的增量也有所变化,除了每次扩容1/4,还得加上256*3/4
  //5.预估容量不一定为最终申请的容量;Go的内存管理,申请内存时都有一定的规格(8,16,32,48…),
  //  e.g. int类型slice,预估容量为5,64位操作系统,需要申请40字节,
  //  但内存规格中不存在40,Go的内存管理会帮我们匹配到足够大、且最接近的规格48,最终申请的容量是6。
  // go test -v -run TestName_2024_01_09_18_10_28/expansion_experiment github.com/gainovel/testcase/stdlib/runtime/slice
  t.Run("expansion experiment", func(t *testing.T) {
    var (
      s1 []int
      s2 []int
    )
    s1 = make([]int, 0, 1)
    s2 = make([]int, 0, 1)
    myfmt.VarInitPrintln(`var (
  s1 []int
  s2 []int
)`)
    myfmt.ColorDescPrintln("s1 = make([]int, 0, 1)", "s2 = make([]int, 0, 1)")
    myfmt.KeyValuePrintln("s1", s1, "len(s1)", len(s1), "cap(s1)", cap(s1))
    myfmt.KeyValuePrintln("s2", s2, "len(s2)", len(s2), "cap(s2)", cap(s2))
    s1 = append(s1, 1, 2, 3, 4)
    s2 = append(s2, 1, 2, 3, 4, 5)
    myfmt.ColorDescPrintln("s1 = append(s1, 1, 2, 3, 4)", "s2 = append(s2, 1, 2, 3, 4, 5)")
    myfmt.KeyValuePrintln("s1", s1, "len(s1)", len(s1), "cap(s1)", cap(s1))
    myfmt.KeyValuePrintln("s2", s2, "len(s2)", len(s2), "cap(s2)", cap(s2))
  })
}

输出结果

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
**************************************************************************************
* 测试用例运行方法
*  0.前置要求安装goversion>=1.20),安装make可选
*  1.git clone git@gitee.com:gainovel/go-test-case.git 👉 cd go-test-case
*    👉 cd docs/tests/stdlib/runtime/slice.md
*    👉 找到对应的命令依次复制执行即可在根目录go-test-case下执行命令
*  2.或者直接打开测试文件 stdlib/runtime/slice/features_usages_001_test.go
*    每个子测试上都有对应的命令直接执行即可在根目录go-test-case下执行命令
*  3. 注意所有命令都在根目录下执行
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/index_out_of_range -f Makefiles/stdlib/runtime/slice.mk
*  Test Result: panic: runtime error: index out of range [1] with length 1
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/share_array -f Makefiles/stdlib/runtime/slice.mk
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  var (
*          s1   []int
*          s1_1 []int
*          s2   []int
*          s2_1 []int
*  )
*  --------------------------------------------------------------------------------
*  👇
*  s1 = []int{1, 2, 3, 4, 5};s1_1 = s1[1:3]
*  s2 = []int{1, 2, 3, 4, 5};s2_1 = s2[1:3:3]
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s1 | [1 2 3 4 5]
*  |           len(s1) | 5
*  |           cap(s1) | 5
*  |              s1_1 | [2 3]
*  |         len(s1_1) | 2
*  |         cap(s1_1) | 4
*  --------------------------------------------------------------------------------
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s2 | [1 2 3 4 5]
*  |           len(s2) | 5
*  |           cap(s2) | 5
*  |              s2_1 | [2 3]
*  |         len(s2_1) | 2
*  |         cap(s2_1) | 2
*  --------------------------------------------------------------------------------
*  👇
*  s1_1 = append(s1_1, 100)
*  s2_1 = append(s2_1, 100)
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s1 | [1 2 3 100 5]
*  |           len(s1) | 5
*  |           cap(s1) | 5
*  |              s1_1 | [2 3 100]
*  |         len(s1_1) | 3
*  |         cap(s1_1) | 4
*  --------------------------------------------------------------------------------
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s2 | [1 2 3 4 5]
*  |           len(s2) | 5
*  |           cap(s2) | 5
*  |              s2_1 | [2 3 100]
*  |         len(s2_1) | 3
*  |         cap(s2_1) | 4
*  --------------------------------------------------------------------------------
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/slice_copy -f Makefiles/stdlib/runtime/slice.mk
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  var (
*          s1 []int
*          s2 []int
*  )
*  --------------------------------------------------------------------------------
*  👇
*  s1 = []int{1, 2, 3, 4, 5}
*  s2 = []int{100, 100, 100}
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s1 | [1 2 3 4 5]
*  |                s2 | [100 100 100]
*  --------------------------------------------------------------------------------
*  👇
*  copy(s2, s1)
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s1 | [1 2 3 4 5]
*  |                s2 | [1 2 3]
*  --------------------------------------------------------------------------------
*  👇
*  s1 = []int{1, 2, 3, 4, 5}
*  s2 = []int{100, 100, 100}
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s1 | [1 2 3 4 5]
*  |                s2 | [100 100 100]
*  --------------------------------------------------------------------------------
*  👇
*  copy(s1, s2)
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s1 | [100 100 100 4 5]
*  |                s2 | [100 100 100]
*  --------------------------------------------------------------------------------
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/expansion_experiment -f Makefiles/stdlib/runtime/slice.mk
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  var (
*          s1 []int
*          s2 []int
*  )
*  --------------------------------------------------------------------------------
*  👇
*  s1 = make([]int, 0, 1)
*  s2 = make([]int, 0, 1)
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s1 | []
*  |           len(s1) | 0
*  |           cap(s1) | 1
*  --------------------------------------------------------------------------------
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s2 | []
*  |           len(s2) | 0
*  |           cap(s2) | 1
*  --------------------------------------------------------------------------------
*  👇
*  s1 = append(s1, 1, 2, 3, 4)
*  s2 = append(s2, 1, 2, 3, 4, 5)
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s1 | [1 2 3 4]
*  |           len(s1) | 4
*  |           cap(s1) | 4
*  --------------------------------------------------------------------------------
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                s2 | [1 2 3 4 5]
*  |           len(s2) | 5
*  |           cap(s2) | 6
*  --------------------------------------------------------------------------------
**************************************************************************************

map

🌞 ︎阳历 2024年02月19日 星期一 | 🌙农历 龙年(2024)正月初十 | 🕛晚上 8点01分07秒 | 更新 🏳️‍🌈

全部知识点

1
2
3
4
5
6
7
8
--------------------------------------------------------------------------------------
├── 1.map简介
├── 2.map特征
├── 3.map用法
├── 4.map原理
├── 5.从map中查找元素的流程
├── 6.map使用心得
--------------------------------------------------------------------------------------
map简介
  1. 字典类型,底层使用Hash表实现。
map特征
  1. 并发不安全,多协程同时同时写会报错concurrent map writes。
  2. panic的情况,给nil map添加key-value会引发panic,但是查询、删除nil map均不报错,相当于空操作。
map用法
  1. 使用内置函数make()进行初始化。
  2. 使用内置函数delete()进行删除元素,key不存在不报错,相当于空操作。
  3. 可以使用逗号模式(val,ok)获取值,key不存在返回val的零值,ok的值表示key是否存在。
map原理
  1. map的源码在go的runtime包下,runtime/map.go:hmap;go语言的map使用Hash表作为底层实现,一个Hash表里可以有多个bucket,而每个bucket保存了map中的一个或一组键值对。
  2. bucket的数据结构。tophash是一个长度为8的整型数组,数组中存储的是key的Hash值的高8位;一个bucket可以存储8个元素,Hash值低位相同的键会存到同一个bucket中,将这些键的高8位存储到tophash中,方便后续查询;data:存储key,value;为了节省字节对齐带来的空间浪费,先存的是key,再存value(key/key/key···value/value/value···);overflow:存储溢出bucket的地址;当当前bucket存满了,使用拉链法解决冲突,新元素存到溢出bucket中。
  3. map负载因子。负载因子=键数量/bucket数量,负载因子小,说明空间利用率不高,负载因子大,说明冲突严重,存取效率低;当负载因子过大或过小时,需要通过扩容来降低负载因子。
  4. 扩容触发条件。负载因子大于6.5,即平均每个bucket存储的键值对达到6.5个以上;overflow(溢出桶)的数量达到一定值;扩容的过程其实是重新创建bucket,重新组织键值对,让键值对均匀分布到新创建的bucket中,这个过程称为rehash。
  5. 增量扩容。负载因子大于6.5时,触发增量扩容,新建一个bucket数组,长度是之前bucket数组的2倍,将之前bucket的元素搬迁过来即可;map结构体中维护了指向新bucket数组和老bucket数组的指针;Go语言采用逐步搬迁的策略,每次访问map时都会触发一次搬迁,每次搬迁2个bucket,直至搬迁完毕,就可以释放老的bucket数组了。
  6. 等量扩容。极端场景下,比如经过大量的元素增删时,键值对刚好集中在一小部分的bucket中,这样会造成溢出的bucket数量增多,触发扩容的第二个条件overflow(溢出桶)的数量达到一定值;此时会触发增量扩容,增量扩容并不扩大容量,但也会走一遍等量扩容的流程;目的在于重新组织键值对,让键值对均匀分布到新创建的bucket中。
从map中查找元素的流程
  1. 根据key值计算Hash值。
  2. 根据Hash值的低位确定要加入的bucket的位置。
  3. 取Hash值的高8位,在tophash数组中查询。
  4. 如果查询到了,则在data区进一步定位到key-value值并返回。
  5. 如果当前bucket没有找到,则依次从溢出的bucket中查找。
  6. 如果当前map处于搬迁过程中,则优先从老的bucket数组中查找,查找不到再从新的bucket数组中查找。
  7. 如果以上都没有找到,则说明key不存在,就返回value类型的零值。
map使用心得
  1. 初始化map时推荐使用内置函数make()并指定预估的容量。
  2. 修改键值对时,需要先查询指定的键是否存在,否则map将创建新的键值对。
  3. 查询键值对时,最好检查键是否存在,避免操作零值。
  4. 避免并发读写map,可能会引起panic,如果需要并发读写,使用锁或改用sync.Map。

示例代码

源码链接 stdlib/runtime/map/features_usages_001_test.go

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package map000

import (
  "fmt"
  "math/rand"
  "sync"
  "testing"
  "time"

  "github.com/gookit/color"

  commonprint "github.com/gainovel/testcase/tools/common/print"
)

var (
  myfmt = commonprint.MyFmt
)

func ConcurrentMapReads() {
  var (
    m1                   map[int]int
    wg                   sync.WaitGroup
    n                    int
    targetKey, targetVal int
  )
  n = 1000
  m1 = make(map[int]int, n)

  // 先随机添加一些元素值
  for i := 0; i < n; i++ {
    key := rand.Intn(i+1000) + 100
    val := rand.Intn(key) + 88
    m1[key] = val
  }

  targetKey = 100
  targetVal = 10000
  m1[targetKey] = targetVal
  // 开启1000个协程并发读
  wg.Add(n)
  for i := 0; i < n; i++ {
    go func(j int) {
      defer wg.Done()
      color.HiMagenta.Printf("goroutine %d read start...\n", j)
      time.Sleep(time.Microsecond * 10)
      val := m1[targetKey]
      fmt.Printf("groutine %s get targetKey(%d), targetVal(%d)\n", color.HiCyan.Sprintf("%d", j), targetKey, val)
    }(i)
  }
  wg.Wait()
}
func ConcurrentMapWrites() {
  var (
    m1                   map[int]int
    wg                   sync.WaitGroup
    n                    int
    targetKey, targetVal int
  )
  n = 1000
  m1 = make(map[int]int, n)

  // 先随机添加一些元素值
  for i := 0; i < n; i++ {
    key := rand.Intn(i+1000) + 100
    val := rand.Intn(key) + 88
    m1[key] = val
  }

  targetKey = 1000
  targetVal = 10000

  // 开启1000个协程并发写
  wg.Add(n)
  for i := 0; i < n; i++ {
    go func(j int) {
      defer wg.Done()
      color.HiMagenta.Printf("goroutine %d write start...\n", j)
      //fmt.Printf("groutine %s write targetKey(%d), targetVal(%d)\n", color.New(color.FgHiCyan).Sprintf("%d", j), targetKey, targetVal)
      time.Sleep(time.Microsecond * 10)
      m1[targetKey] = targetVal
    }(i)
  }
  wg.Wait()
}

func TestName_2024_01_10_11_22_46(t *testing.T) {
  // 多协程同时写会报错concurrent map writes
  // go test -v -run TestName_2024_01_10_11_22_46/concurrent_map_write github.com/gainovel/testcase/stdlib/runtime/map
  t.Run("concurrent map write", func(t *testing.T) {
    // 使用windows terminal 在./cmd/main.go测试ConcurrentMapWrites()
    ConcurrentMapWrites()
  })
  // panic的情况👉给nil map添加key-value
  // go test -v -run TestName_2024_01_10_11_22_46/assignment_to_entry_in_nil_map github.com/gainovel/testcase/stdlib/runtime/map
  t.Run("assignment to entry in nil map", func(t *testing.T) {
    var (
      m1 map[int]int
    )
    m1[1] = 2
  })

  // map 的简单使用
  // 使用内置函数delete()进行删除
  // 查询map时,使用逗号模式(val,ok)获取值,避免操作零值,ok表示key是否存在
  // go test -v -run TestName_2024_01_10_11_22_46/map_crud github.com/gainovel/testcase/stdlib/runtime/map
  t.Run("map crud", func(t *testing.T) {
    var (
      m1  map[int]int
      key int
      ok  bool
    )
    m1 = make(map[int]int)
    m1[1] = 101
    m1[2] = 102
    myfmt.VarInitPrintln(`var (
  m1 map[int]int
)`)
    myfmt.ColorDescPrintln("m1 = make(map[int]int)", "m1[1] = 101", "m1[2] = 102")
    myfmt.KeyValuePrintln("m1", m1, "len(m1)", len(m1))
    delete(m1, 1)
    myfmt.ColorDescPrintln("delete(m1, 1)")
    myfmt.KeyValuePrintln("m1", m1, "len(m1)", len(m1))
    key, ok = m1[1]
    myfmt.ColorDescPrintln("key,ok = m1[1]")
    myfmt.KeyValuePrintln("m1", m1, "len(m1)", len(m1), "key", key, "ok", ok)
    key, ok = m1[2]
    myfmt.ColorDescPrintln("key,ok = m1[2]")
    myfmt.KeyValuePrintln("m1", m1, "len(m1)", len(m1), "key", key, "ok", ok)
  })
}

输出结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
**************************************************************************************
* 测试用例运行方法
*  0.前置要求安装goversion>=1.20),安装make可选
*  1.git clone git@gitee.com:gainovel/go-test-case.git 👉 cd go-test-case
*    👉 cd docs/tests/stdlib/runtime/map.md
*    👉 找到对应的命令依次复制执行即可在根目录go-test-case下执行命令
*  2.或者直接打开测试文件 stdlib/runtime/map/features_usages_001_test.go
*    每个子测试上都有对应的命令直接执行即可在根目录go-test-case下执行命令
*  3.  注意所有命令都在根目录下执行
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/concurrent_map_write -f Makefiles/stdlib/runtime/map.mk
*  Test Result: fatal error: concurrent map writes
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/assignment_to_entry_in_nil_map -f Makefiles/stdlib/runtime/map.mk
*  Test Result: panic: assignment to entry in nil map
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/map_crud -f Makefiles/stdlib/runtime/map.mk
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  var (
*          m1 map[int]int
*  )
*  --------------------------------------------------------------------------------
*  👇
*  m1 = make(map[int]int)
*  m1[1] = 101
*  m1[2] = 102
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                m1 | map[1:101 2:102]
*  |           len(m1) | 2
*  --------------------------------------------------------------------------------
*  👇
*  delete(m1, 1)
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                m1 | map[2:102]
*  |           len(m1) | 1
*  --------------------------------------------------------------------------------
*  👇
*  key,ok = m1[1]
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                m1 | map[2:102]
*  |           len(m1) | 1
*  |               key | 0
*  |                ok | false
*  --------------------------------------------------------------------------------
*  👇
*  key,ok = m1[2]
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                m1 | map[2:102]
*  |           len(m1) | 1
*  |               key | 102
*  |                ok | true
*  --------------------------------------------------------------------------------
**************************************************************************************

struct

🌞 ︎阳历 2024年02月19日 星期一 | 🌙农历 龙年(2024)正月初十 | 🕛晚上 11点35分18秒 | 更新 🏳️‍🌈

全部知识点

1
2
3
4
5
6
7
--------------------------------------------------------------------------------------
├── 1.struct简介
├── 2.struct特征
├── 3.struct用法
├── 4.Tag的本质
├── 5.方法
--------------------------------------------------------------------------------------
struct简介
  1. Go的struct和其它编程语言的class有些类似,可以定义方法和字段,但是不可以继承,通常用内嵌结构体的方式代替继承。
struct特征
  1. 可导出性。无论是结构体还是结构体字段,都是通过首字符是否大写来判断是否可导出。
struct用法
  1. 内嵌字段。Go中没有继承,当要复用其它结构体的字段和方法时,可以通过内嵌的方式;形式上似乎是继承了内嵌结构体的字段和方法,实际上这些字段和方法仍属于内嵌结构体。
  2. 方法受体。可以为结构体和它的指针类型定义方法;值接收者方法无法修改结构体本身字段,指针接收者可以修改。
  3. 字段标签。结构体字段可以定义字段标签(Tag)。
Tag的本质
  1. Tag是struct的一部分。用于标识结构体字段的额外属性,标准库reflect包提供了操作Tag的方法。
  2. Tag的约定。Tag本身是一个字符串,字符串内容应是key:"value"形式组成,key一般表示用途,比如json表示将用于控制结构体类型与JSON格式数据之间的转换;protobuf表示用于控制序列化和反序列化;value一般表示控制指令。
  3. Tag的意义。Go语言的反射特性可以动态的给结构体赋值,正式因为有Tag,在赋值前可以使用Tag来决定赋值的动作;比如,encoding/json包,可以将一个JSON数据Unmarshal进一个结构体,此过程中就使用了Tag;该包定义了一些Tag规则,只要遵循该规则设置Tag就可以将一个JSON数"Unmarshal"进一个结构体。
方法
  1. 方法即method,Go语言支持为自定义类型实现方法,method在具体实现上与普通的函数并无不同,只不过会通过运行时栈多传递一个隐含的参数,这个隐含的参数就是所谓的接收者。
  2. 值接收者和指针接收者语法糖。即使我们只定义了值接收者方法或指针接收者方法,我们都可以使用值或指针访问,这是go的语法糖,go会帮我们进行相应的引用或解引用操作。
  3. 方法表达式和方法变量。我们可以将一个结构体的方法或者一个结构体变量的方法赋给一个变量,这个变量就被称为方法表达式或方法变量;通过方法表达式调用方法要传递接收者和方法的参数;通过方法变量调用函数只需要传递方法的参数即可,无需传递接收者;方法表达式和方法变量和普通的函数在实现上也没什么不同。

示例代码

源码链接 stdlib/struct/features_usages_001_test.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package struct000

import (
  "fmt"
  "testing"
)

type A struct {
  name string
}

func (a A) Name() string {
  a.name = "Hi! " + a.name
  return a.name
}

func TestName_2024_02_20_12_18_40(t *testing.T) {
  // 方法即method,Go语言支持为自定义类型实现方法,method在具体实现上与普通的函数并无不同,只不过会通过运行时栈多传递一个隐含的参数,这个隐含的参数就是所谓的接收者。
  // 以下代码展示了两种不同的写法,都能顺利通过编译并正常运行,实际上这两种写法会生成同样的机器码。
  // 第一种:a.Name(),这是我们惯用的写法,很方便;
  // 第二种:A.Name(a),这种写法更底层也更严谨,要求所有的类型必须严格对应,否则是无法通过编译的。
  // 其实编译器会帮我们把第一种转换为第二种的形式,所以我们惯用的第一种写法只是“语法糖”,方便而已。
  // go test -v -run TestName_2024_02_20_12_18_40/case1 github.com/gainovel/testcase/stdlib/struct
  t.Run("case1", func(t *testing.T) {
    a := A{name: "eggo"}
    // 1)编译器的语法糖,提供面向对象的语法
    fmt.Println(a.Name())
    // 2)更贴近真实实现的写法,和普通函数调用几乎没什么不同
    fmt.Println(A.Name(a))
  })
}

输出结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
**************************************************************************************
* 测试用例运行方法
*  0.前置要求安装goversion>=1.20),安装make可选
*  1.git clone git@gitee.com:gainovel/go-test-case.git 👉 cd go-test-case
*    👉 cd docs/tests/stdlib/struct.md
*    👉 找到对应的命令依次复制执行即可在根目录go-test-case下执行命令
*  2.或者直接打开测试文件 stdlib/struct/features_usages_001_test.go
*    每个子测试上都有对应的命令直接执行即可在根目录go-test-case下执行命令
*  3.  注意所有命令都在根目录下执行
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/case1 -f  Makefiles/stdlib/struct.mk
*  Test Result:
*  Hi! eggo
*  Hi! eggo
**************************************************************************************

iota

🌞 ︎阳历 2024年02月20日 星期二 | 🌙农历 龙年(2024)正月十一 | 🕛下午 1点20分05秒 | 更新 🏳️‍🌈

全部知识点

1
2
3
4
5
--------------------------------------------------------------------------------------
├── 1.iota简介
├── 2.iota疑惑烟消云散
├── 3.iota原理
--------------------------------------------------------------------------------------
iota简介
  1. iota只能用于const声明中。
  2. 值从零开始,const声明中每增加一行,iota值自增1。
  3. 使用iota可以简化常量定义。
iota疑惑烟消云散
  1. 使用iota牢记一句话,所有困惑都会烟消云散。iota代表了const声明块的行索引;不管一行声明多少个iota、不管中途是否更换iota表达式、不管是否从首行开始声明iota ⇨ iota雷打不动代表了const声明块的行索引。
  2. 使用const声明块也牢记一句话。如果为常量指定了一个表达式,但后续常量没有表达式,则继承上面的表达式。
iota原理
  1. 了解iota的原理首先要了解编译器处理常量声明语句的方式。const声明块中,每个常量声明都对应一个ValueSpec结构体,ValueSpec结构记录的是每一行常量声明的元信息,比如变量名称、类型、值、注释等等;const声明块中一般包含多行常量声明,则对应多个ValueSpec结构体,一行对应一个;实际上一个const声明块就是一个[]ValueSpec,iota的本质就是[]ValueSpec的索引值,也就是行索引。

示例代码

源码链接 stdlib/runtime/iota/features_usages_001_test.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package iota000

import (
  "fmt"
  "testing"

  commonprint "github.com/gainovel/testcase/tools/common/print"
)

var (
  myfmt = commonprint.MyFmt
)

func TestName_2024_01_10_16_36_44(t *testing.T) {
  // ‼️ iota 和 const声明块 关键两条原则,所有赋值不疑惑
  // 1.iota代表了const声明块的行索引
  // 2.如果为常量指定了一个表达式,但后续常量没有表达式,则继承上面的表达式
  // go test -v -run TestName_2024_01_10_16_36_44/iota_1 github.com/gainovel/testcase/stdlib/runtime/iota
  t.Run("iota 1", func(t *testing.T) {
    const (
      a, b = 1 << iota, iota * 2 //【iota:0】 【1<<iota:1】 【iota*2:0】
      c, d                       //【iota:1】 【1<<iota:2】 【iota*2:2】
      e, f                       //【iota:2】 【1<<iota:4】 【iota*2:4】
      g, h                       //【iota:3】 【1<<iota:8】 【iota*2:6】
      i, j                       //【iota:4】 【1<<iota:16】 【iota*2:8】
      k, l                       //【iota:5】 【1<<iota:32】 【iota*2:10】
    )
    myfmt.VarInitPrintln(`const (
  a, b = 1 << iota, iota * 2 //【iota:0】 【1<<iota:1】 【iota*2:0】
  c, d                       //【iota:1】 【1<<iota:2】 【iota*2:2】
  e, f                       //【iota:2】 【1<<iota:4】 【iota*2:4】
  g, h                       //【iota:3】 【1<<iota:8】 【iota*2:6】
  i, j                       //【iota:4】 【1<<iota:16】 【iota*2:8】
  k, l                       //【iota:5】 【1<<iota:32】 【iota*2:10】
)`)
    myfmt.KeyValuePrintln("a", a, "b", b, "c", c, "d", d, "e", e, "f", f, "g", g, "h", h, "i", i, "j", j, "k", k, "l", l)

  })
  // go test -v -run TestName_2024_01_10_16_36_44/iota_2 github.com/gainovel/testcase/stdlib/runtime/iota
  t.Run("iota 2", func(t *testing.T) {
    // const声明块即使常量中途重新声明表达式,iota的值始终都是该行的行索引
    const (
      a, b = 1 << iota, iota * 2 //【iota:0】 【1<<iota:1】 【iota*2:0】
      c, d                       //【iota:1】 【1<<iota:2】 【iota*2:2】
      e, f = iota, iota + 2      //【iota:2】 【iota:2】 【iota+2:4】
      g, h                       //【iota:3】 【iota:3】 【iota+2:5】
      i, j = iota * 3, iota + 4  //【iota:4】 【iota*3:12】 【iota+4:8】
      k, l                       //【iota:5】 【iota*3:15】 【iota+4:9】
    )
    myfmt.VarInitPrintln(`const (
  a, b = 1 << iota, iota * 2 //【iota:0】 【1<<iota:1】 【iota*2:0】
  c, d                       //【iota:1】 【1<<iota:2】 【iota*2:2】
  e, f = iota, iota + 2      //【iota:2】 【iota:2】 【iota+2:4】
  g, h                       //【iota:3】 【iota:3】 【iota+2:5】
  i, j = iota * 3, iota + 4  //【iota:4】 【iota*3:12】 【iota+4:8】
  k, l                       //【iota:5】 【iota*3:15】 【iota+4:9】
)`)
    myfmt.KeyValuePrintln("a", a, "b", b, "c", c, "d", d, "e", e, "f", f, "g", g, "h", h, "i", i, "j", j, "k", k, "l", l)

  })
  // go test -v -run TestName_2024_01_10_16_36_44/iota_3 github.com/gainovel/testcase/stdlib/runtime/iota
  t.Run("iota 3", func(t *testing.T) {
    // const声明块即使首行非iota表达式,iota的值始终都是该行的行索引
    const (
      x, y = 100, 100
      a, b = 1 << iota, iota * 2 //【iota:1】 【1<<iota:2】 【iota*2:2】
      c, d                       //【iota:2】 【1<<iota:4】 【iota*2:4】
      e, f = iota, iota + 2      //【iota:3】 【iota:3】 【iota+2:5】
      g, h                       //【iota:4】 【iota:4】 【iota+2:6】
    )
    myfmt.VarInitPrintln(`const (
  x, y = 100, 100
  a, b = 1 << iota, iota * 2 //【iota:1】 【1<<iota:2】 【iota*2:2】
  c, d                       //【iota:2】 【1<<iota:4】 【iota*2:4】
  e, f = iota, iota + 2      //【iota:3】 【iota:3】 【iota+2:5】
  g, h                       //【iota:4】 【iota:4】 【iota+2:6】
)`)
    myfmt.KeyValuePrintln("x", x, "y", y, "a", a, "b", b, "c", c, "d", d, "e", e, "f", f, "g", g, "h", h)

  })
  // go test -v -run TestName_2024_01_10_16_36_44/iota_4 github.com/gainovel/testcase/stdlib/runtime/iota
  t.Run("iota 4", func(t *testing.T) {
    // 可以用在单行const声明中(没有太大意义)
    const a = iota + 3
    fmt.Println(a)
  })
}

输出结果

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
**************************************************************************************
* 测试用例运行方法
*  0.前置要求安装goversion>=1.20),安装make可选
*  1.git clone git@gitee.com:gainovel/go-test-case.git 👉 cd go-test-case
*    👉 cd docs/tests/stdlib/runtime/iota.md
*    👉 找到对应的命令依次复制执行即可在根目录go-test-case下执行命令
*  2.或者直接打开测试文件 stdlib/runtime/iota/features_usages_001_test.go
*    每个子测试上都有对应的命令直接执行即可在根目录go-test-case下执行命令
*  3.  注意所有命令都在根目录下执行
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/iota_1 -f Makefiles/stdlib/runtime/iota.mk
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  const (
*          a, b = 1 << iota, iota * 2 //【iota:0】 【1<<iota:1】 【iota*2:0】
*          c, d                       //【iota:1】 【1<<iota:2】 【iota*2:2】
*          e, f                       //【iota:2】 【1<<iota:4】 【iota*2:4】
*          g, h                       //【iota:3】 【1<<iota:8】 【iota*2:6】
*          i, j                       //【iota:4】 【1<<iota:16】 【iota*2:8】
*          k, l                       //【iota:5】 【1<<iota:32】 【iota*2:10】
*  )
*  --------------------------------------------------------------------------------
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                 a | 1
*  |                 b | 0
*  |                 c | 2
*  |                 d | 2
*  |                 e | 4
*  |                 f | 4
*  |                 g | 8
*  |                 h | 6
*  |                 i | 16
*  |                 j | 8
*  |                 k | 32
*  |                 l | 10
*  --------------------------------------------------------------------------------
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/iota_2 -f Makefiles/stdlib/runtime/iota.mk
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  const (
*          a, b = 1 << iota, iota * 2 //【iota:0】 【1<<iota:1】 【iota*2:0】
*          c, d                       //【iota:1】 【1<<iota:2】 【iota*2:2】
*          e, f = iota, iota + 2      //【iota:2】 【iota:2】 【iota+2:4】
*          g, h                       //【iota:3】 【iota:3】 【iota+2:5】
*          i, j = iota * 3, iota + 4  //【iota:4】 【iota*3:12】 【iota+4:8】
*          k, l                       //【iota:5】 【iota*3:15】 【iota+4:9】
*  )
*  --------------------------------------------------------------------------------
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                 a | 1
*  |                 b | 0
*  |                 c | 2
*  |                 d | 2
*  |                 e | 2
*  |                 f | 4
*  |                 g | 3
*  |                 h | 5
*  |                 i | 12
*  |                 j | 8
*  |                 k | 15
*  |                 l | 9
*  --------------------------------------------------------------------------------
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/iota_3 -f Makefiles/stdlib/runtime/iota.mk
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  const (
*          x, y = 100, 100
*          a, b = 1 << iota, iota * 2 //【iota:1】 【1<<iota:2】 【iota*2:2】
*          c, d                       //【iota:2】 【1<<iota:4】 【iota*2:4】
*          e, f = iota, iota + 2      //【iota:3】 【iota:3】 【iota+2:5】
*          g, h                       //【iota:4】 【iota:4】 【iota+2:6】
*  )
*  --------------------------------------------------------------------------------
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |                 x | 100
*  |                 y | 100
*  |                 a | 2
*  |                 b | 2
*  |                 c | 4
*  |                 d | 4
*  |                 e | 3
*  |                 f | 5
*  |                 g | 4
*  |                 h | 6
*  --------------------------------------------------------------------------------
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/iota_4 -f Makefiles/stdlib/runtime/iota.mk
*  Test Result: 3
**************************************************************************************

string

🌞 ︎阳历 2024年02月20日 星期二 | 🌙农历 龙年(2024)正月十一 | 🕛下午 1点30分42秒 | 更新 🏳️‍🌈

全部知识点

1
2
3
4
5
6
7
8
--------------------------------------------------------------------------------------
├── 1.string简介
├── 2.string特征
├── 3.string用法
├── 4.string原理
├── 5.为什么字符串不允许修改?
├── 6.string和[]byte如何取舍?
--------------------------------------------------------------------------------------
string简介
  1. string是Go的基础类型之一,值不可修改。
string特征
  1. 值不可修改。不能通过取索引的方式修改字符内容或获取字符地址。
  2. 空值。空值为""而不是nil。
string用法
  1. string字面量。双引号("···")和反引号(`···`)两种方式,区别在于`···`是原样输出,不会对特殊字符进行转义。
  2. 内存占用。字符串类型不可修改,因此字符串赋值时会触发内存分配和内存拷贝,单行语句拼接多个字符串只分配一次内存,先计算最终字符串长度再分配内存。
  3. 常用标准库函数。标准库strings包中包含了大量的字符串操作函数。
  4. 字符串和[]byte、[]rune可以互相转换。
string原理
  1. string和slice很相似,有一个底层的[]byte用于存储,Go默认使用UTF8编码,[]byte存储的是字符采用UTF-8得到的字节。
  2. UTF-8是一种可变长度字符编码,通常用1~3个字节表示一个字符。e.g. "A"占一个字节,而"中"占3个字节。
为什么字符串不允许修改?
  1. 像C++中的string,其本身拥有内存空间,是支持修改的。但在Go的实现中,string不包含内存空间,只有一个内存的指针,这样做的好处是string变得非常轻量,可以很方便的进行传递而不用担心内存拷贝。
  2. 因为string通常指向字符串字面量,而字符串字面量的存储位置是只读段,而不是堆或栈上,所有有了string类型不支持修改的约定。
string和[]byte如何取舍?
  1. string和[]byte都可以表示字符串,但因数据结构不同,其衍生出来的方法也不同,要根据实际场景来选择。
  2. string擅长的场景。①需要字符串比较的场景②不需要nil字符串的场景。
  3. []byte擅长的场景。①修改字符串的场景,尤其是修改粒度为一个字节②函数返回值,需要用nil表示含义的场景③需要切片操作的场景。

示例代码

源码链接 stdlib/runtime/string/features_usages_001_test.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package string

import (
  "testing"

  commonprint "github.com/gainovel/testcase/tools/common/print"
)

var (
  myfmt = commonprint.MyFmt
)

func TestName_2024_01_10_17_38_02(t *testing.T) {
  // go test -v -run TestName_2024_01_10_17_38_02/string_to_bytes_or_runes github.com/gainovel/testcase/stdlib/runtime/string
  t.Run("string to bytes or runes", func(t *testing.T) {
    var (
      str1  string
      bytes []byte
      runes []rune
    )
    str1 = "hello 中国!"
    // ['h','e','l','l','o',   ' ',         '中',          '国',           '!'   ]
    // ['h','e','l','l','o',   ' ',         '中',          '国',           '!'   ]
    // [104,101,108,108,111,    32,     228,184,173    229,155,189    239,188,129] []byte len:15
    // [104,101,108,108,111,    32,        20013,        22269,          65281   ] []rune len:9
    myfmt.VarInitPrintln(`var (
  str1  string
  bytes []byte
  runes []rune
)`)
    bytes = []byte(str1)
    runes = []rune(str1)
    myfmt.ColorDescPrintln("bytes = []byte(str1)", "runes = []rune(str1)")
    myfmt.KeyValuePrintln("str1", str1, "bytes", bytes, "runes", runes)
  })
}

输出结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
**************************************************************************************
* 测试用例运行方法
*  0.前置要求安装goversion>=1.20),安装make可选
*  1.git clone git@gitee.com:gainovel/go-test-case.git 👉 cd go-test-case
*    👉 cd docs/tests/stdlib/runtime/string.md
*    👉 找到对应的命令依次复制执行即可在根目录go-test-case下执行命令
*  2.或者直接打开测试文件 stdlib/runtime/string/features_usages_001_test.go
*    每个子测试上都有对应的命令直接执行即可在根目录go-test-case下执行命令
*  3.  注意所有命令都在根目录下执行
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: xxx
*  Test Result:
*  👇
*  变量初始化
*  --------------------------------------------------------------------------------
*  var (
*          str1  string
*          bytes []byte
*          runes []rune
*  )
*  --------------------------------------------------------------------------------
*  👇
*  bytes = []byte(str1)
*  runes = []rune(str1)
*  --------------------------------------------------------------------------------
*  |               key | value
*  --------------------------------------------------------------------------------
*  |              str1 | hello 中国
*  |             bytes | [104 101 108 108 111 32 228 184 173 229 155 189 239 188 129]
*  |             runes | [104 101 108 108 111 32 20013 22269 65281]
*  --------------------------------------------------------------------------------
**************************************************************************************

sync.Map

🌞 ︎阳历 2024年02月20日 星期二 | 🌙农历 龙年(2024)正月十一 | 🕛下午 2点20分59秒 | 更新 🏳️‍🌈

全部知识点

1
2
3
4
5
6
7
--------------------------------------------------------------------------------------
├── 1.sync.Map简介
├── 2.sync.Map特征
├── 3.sync.Map用法
├── 4.sync.Map原理
├── 5.sync.Map使用思考
--------------------------------------------------------------------------------------
sync.Map简介
  1. sync.Map是一种并发安全的map,针对特定场景优化的map,并非可以取代原生map,其实大多数场景还是使用原生map;因为sync.Map和原生map在功能上也并非完全对等。
sync.Map特征
  1. 零值。sync.Map是结构体,零值是结构体的零值,并非nil;因此声明完即可使用。
  2. 遍历方法。sync.Map不能使用for-range遍历;sync.Map提供了range方法支持遍历,并且可以针对每个键值对设置回调方法。
  3. 并发安全。sync.Map并发安全,支持并发读写,因此遍历期间有可能读到其它协程刚写入的数据。
  4. sync.Map可存储任何类型的键值对,取出的元素类型为interface{},使用时需要使用类型断言。
sync.Map用法
  1. 增改:sync.Map.Store(key,value)。
  2. 查:sync.Map.Load(key)。
  3. 删:sync.Map.Delete(key)。
  4. 遍历:sync.Map.Range(f func(key, value any) bool)。
  5. LoadOrStore。如果key存在,则执行Load方法,加载原来的数据;如果key不存在,则执行Store方法,插入数据。
  6. LoadAndDelete。如果key存在,则执行Load和Delete方法,删除键值对并返回删除的值;如果key不存在,则返回标识key不存在 。
sync.Map原理
  1. sync.Map内部维护了两个mapread和dirty,read用于并发读,dirty用于新数据的写入。
  2. sync.Map内部维护了一个互斥锁用于保护dirty表。
  3. dirty用于新数据的写入,访问需要加锁,但dirty表也只是暂存数据,最终都会同步到read表中。
  4. sync.Map内部维护了一个整型值用于记录查找read表时miss的次数。
  5. 上面说的dirty表暂存数据,最终都会同步到read表中;同步的条件就是miss的值累加到一定的值(等于数据总数)。
  6. dirty表向map同步数据的过程中也会加锁,避免多个同步操作并发执行。
  7. 因此,sync.Map在读多写少且键值对不断增长的场景下可以减少对锁的依赖,因为read表只读,可并发读写。
  8. sync.Map缩小了互斥锁的保护范围,获得性能提升。
sync.Map使用思考
  1. 并发安全。sync.Map将互斥锁内置来实现并发读写。
  2. 特定场景下性能能够提升。sync.Map的内部实现采用了两个原生的map(read和dirty)来实现读写分离,同时又将互斥锁仅限定在dirty中,缩小了互斥锁的保护范围,可以提升性能;由于存在dirty向read同步的机制,在大部分场合下,数据可以从read中获取,所以当读多写少是,sync.Map相较原生map会有性能提升,在写多读少的场景下则不适合使用,主要是由于写入时dirty会冗余read的数据,一方面增加了遍历的时间开销,另一方面增加了新对象的创建频次,从而增加垃圾回收的负担;实际场景下如何选择?如果对性能不敏感,则二者都可以,如果对性能敏感,则可以使用go benchmark进行相应的测试,以得到这个场景的性能比较数据,从而做出选择。
  3. 警惕类型安全风险。sync.Map会将数据以interface{}的类型来进行存储,读出的数据都是interface{}类型,因此要进行类型断言,避免触发panic;因此建议不要在同一个sync.Map中存储不同类型的键值对,以避免panic风险,提升代码的可读性。
  4. 不能拷贝和传递。我们已经习惯在函数间传递原生的map,但sync.Map不支持拷贝和函数间传递,因为它内置锁,而锁是不能拷贝的,否则可能引起死锁或触发panic。

示例代码

源码链接 stdlib/sync/map/features_usages_001_test.go

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
package syncmap000

import (
  "fmt"
  commontools "github.com/gainovel/testcase/tools/common"
  "sync"
  "testing"
)

func TestName_2024_01_11_16_18_49(t *testing.T) {
  //  go test -v -run TestName_2024_01_11_16_18_49/sync.Map_1 github.com/gainovel/testcase/stdlib/sync/map
  t.Run("sync.Map 1", func(t *testing.T) {
    var (
      m1   sync.Map
      val  any
      ok   bool
      temp string
    )
    temp = "{}"
    fmt.Println()
    commontools.PrintAll(true, "var m1  sync.Map", "sync.Map m1 status", "", "sync.Map m1", temp)
    m1.Store("Jim", 80)
    m1.Store("Kevin", 90)
    m1.Store("Jane", 100)
    temp = ""
    m1.Range(func(key, value any) bool {
      temp += fmt.Sprintf("{%v:%v} ", key, value)
      return true
    })
    commontools.PrintAll(true, "add "+temp+"to sync.Map m1", "sync.Map m1 status", "", "sync.Map m1", temp)
    val, ok = m1.Load("Jim")
    commontools.PrintAll(true, "m1.Load(\"Jim\")", "Load Result", "", "val", val, "ok", ok)
    m1.Delete("Jim")
    temp = ""
    m1.Range(func(key, value any) bool {
      temp += fmt.Sprintf("{%v:%v} ", key, value)
      return true
    })
    commontools.PrintAll(true, "m1.Delete(\"Jim\")", "sync.Map m1 status", "", "sync.Map m1", temp)
  })
  // go test -v -run TestName_2024_01_11_16_18_49/sync.Map.LoadOrStore github.com/gainovel/testcase/stdlib/sync/map
  t.Run("sync.Map.LoadOrStore", func(t *testing.T) {
    var (
      m1     sync.Map
      actual any
      loaded bool
      temp   string
    )
    temp = "{}"
    fmt.Println()
    commontools.PrintAll(true, "var m1  sync.Map", "sync.Map m1 status", "", "sync.Map m1", temp)
    m1.Store("Jim", 80)
    m1.Store("Kevin", 90)
    m1.Store("Jane", 100)
    temp = ""
    m1.Range(func(key, value any) bool {
      temp += fmt.Sprintf("{%v:%v} ", key, value)
      return true
    })
    commontools.PrintAll(true, "add "+temp+"to sync.Map m1", "sync.Map m1 status", "", "sync.Map m1", temp)
    actual, loaded = m1.LoadOrStore("Jim", 81)
    commontools.PrintAll(true, "m1.LoadOrStore(\"Jim\", 81)", "LoadOrStore Result", "", "actual", actual, "loaded", loaded)
    m1.Delete("Jim")
    temp = ""
    m1.Range(func(key, value any) bool {
      temp += fmt.Sprintf("{%v:%v} ", key, value)
      return true
    })
    commontools.PrintAll(true, "m1.Delete(\"Jim\")", "sync.Map m1 status", "", "sync.Map m1", temp)
    actual, loaded = m1.LoadOrStore("Jim", 81)
    commontools.PrintAll(true, "m1.LoadOrStore(\"Jim\", 81)", "LoadOrStore Result", "", "actual", actual, "loaded", loaded)
  })
  // go test -v -run TestName_2024_01_11_16_18_49/sync.Map.LoadAndDelete github.com/gainovel/testcase/stdlib/sync/map
  t.Run("sync.Map.LoadAndDelete", func(t *testing.T) {
    var (
      m1     sync.Map
      value  any
      loaded bool
      temp   string
    )
    temp = "{}"
    fmt.Println()
    commontools.PrintAll(true, "var m1  sync.Map", "sync.Map m1 status", "", "sync.Map m1", temp)
    m1.Store("Jim", 80)
    m1.Store("Kevin", 90)
    m1.Store("Jane", 100)

    temp = ""
    m1.Range(func(key, value any) bool {
      temp += fmt.Sprintf("{%v:%v} ", key, value)
      return true
    })
    commontools.PrintAll(true, "add "+temp+"to sync.Map m1", "sync.Map m1 status", "", "sync.Map m1", temp)

    value, loaded = m1.LoadAndDelete("Jim")
    commontools.PrintAll(true, "m1.LoadAndDelete(\"Jim\")", "LoadAndDelete Result", "", "value", value, "loaded", loaded)

    temp = ""
    m1.Range(func(key, value any) bool {
      temp += fmt.Sprintf("{%v:%v} ", key, value)
      return true
    })
    commontools.PrintAll(true, "m1.Range", "sync.Map m1 status", "", "sync.Map m1", temp)

    value, loaded = m1.LoadAndDelete("Jim")
    commontools.PrintAll(true, "m1.LoadAndDelete(\"Jim\")", "LoadAndDelete Result", "", "value", value, "loaded", loaded)
  })
}

输出结果

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
**************************************************************************************
* 测试用例运行方法
*  0.前置要求安装goversion>=1.20),安装make可选
*  1.git clone git@gitee.com:gainovel/go-test-case.git 👉 cd go-test-case
*    👉 cd docs/tests/stdlib/sync/map.md
*    👉 找到对应的命令依次复制执行即可在根目录go-test-case下执行命令
*  2.或者直接打开测试文件 stdlib/sync/map/features_usages_001_test.go
*    每个子测试上都有对应的命令直接执行即可在根目录go-test-case下执行命令
*  3.  注意所有命令都在根目录下执行
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/sync.Map_1 -f Makefiles/stdlib/sync/map.mk
*  Test Result:
*   (️2024年03月11日🕛下午 2点37分18秒)👇(var m1  sync.Map)
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {}
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点37分18秒)👇(add {Jim:80} {Kevin:90} {Jane:100} to sync.Map m1)
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {Jim:80} {Kevin:90} {Jane:100}
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点37分18秒)👇(m1.Load("Jim"))
*  ----------------------------------------------------------------------------------------
*  | Load Result         |
*  ----------------------------------------------------------------------------------------
*  | val                 |  80
*  | ok                  |  true
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点37分18秒)👇(m1.Delete("Jim"))
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {Kevin:90} {Jane:100}
*  ----------------------------------------------------------------------------------------
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/sync.Map.LoadOrStore -f Makefiles/stdlib/sync/map.mk
*  Test Result:
*   (️2024年03月11日🕛下午 2点44分40秒)👇(var m1  sync.Map)
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {}
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点44分40秒)👇(add {Jane:100} {Jim:80} {Kevin:90} to sync.Map m1)
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {Jane:100} {Jim:80} {Kevin:90}
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点44分40秒)👇(m1.LoadOrStore("Jim", 81))
*  ----------------------------------------------------------------------------------------
*  | LoadOrStore Result  |
*  ----------------------------------------------------------------------------------------
*  | actual              |  80
*  | loaded              |  true
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点44分40秒)👇(m1.Delete("Jim"))
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {Kevin:90} {Jane:100}
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点44分40秒)👇(m1.LoadOrStore("Jim", 81))
*  ----------------------------------------------------------------------------------------
*  | LoadOrStore Result  |
*  ----------------------------------------------------------------------------------------
*  | actual              |  81
*  | loaded              |  false
*  ----------------------------------------------------------------------------------------
**************************************************************************************

🏳️‍🌈
**************************************************************************************
*  Test Command: make 001/sync.Map.LoadAndDelete -f Makefiles/stdlib/sync/map.mk
*  Test Result:
*   (️2024年03月11日🕛下午 2点45分08秒)👇(var m1  sync.Map)
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {}
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点45分08秒)👇(add {Jim:80} {Kevin:90} {Jane:100} to sync.Map m1)
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {Jim:80} {Kevin:90} {Jane:100}
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点45分08秒)👇(m1.LoadAndDelete("Jim"))
*  ----------------------------------------------------------------------------------------
*  | LoadAndDelete Result|
*  ----------------------------------------------------------------------------------------
*  | value               |  80
*  | loaded              |  true
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点45分08秒)👇(m1.Range)
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1 status  |
*  ----------------------------------------------------------------------------------------
*  | sync.Map m1         |  {Jane:100} {Kevin:90}
*  ----------------------------------------------------------------------------------------
*     (️2024年03月11日🕛下午 2点45分08秒)👇(m1.LoadAndDelete("Jim"))
*  ----------------------------------------------------------------------------------------
*  | LoadAndDelete Result|
*  ----------------------------------------------------------------------------------------
*  | value               |  <nil>
*  | loaded              |  false
*  ----------------------------------------------------------------------------------------
**************************************************************************************

系列文章

同系列其它文章

参考

Built with Hugo
主题 StackJimmy 设计