Part1: array & slice
// define a slice
s := make([]int, 10)
// define an array
a := [10]int{}
- 定义 slice 第一种方式
var s []int
- 定义 slice 第二种方式
s := make([]int, len, cap)
- array
var a [length]type // var a [10]int
Part2: 类型
package main
import "fmt"
func main() {
var a [8]int
printArray(a)
}
func printArray(a [10]int) {
fmt.Println(len(a))
fmt.Println(cap(a))
}
以上代码中,printArray(a)
是否可以正常执行?答案是否定的,编译期就会提示错误
cannot use a (type [8]int) as type [10]int in argument to printArray
可以看到提示中变量 a
的类型是 type [8]int
,而函数 printArray
要求的入参类型是 type [10]int
。可见 array 的长度也是其类型的一部分!
Part3: slice grow
package main
import "fmt"
func main() {
var s []int
for i := 0; i < 1025; i++ {
s = append(s, i)
}
fmt.Println(len(s)) // 1025
fmt.Println(cap(s)) // 1280 = 1024*1.25
}
slice 扩容的方式是:source code
- cap < 1024 –> cap * 2
- cap > 1024 –> cap * 1.25
append
多个参数的对于 slice
容量的影响是不同的,特殊case:
package main
import "fmt"
func main() {
var s1, s2, s3, s4, s5 []int
s1 = append(s1, 0) // len 1, cap 1
printSlice(s1)
s2 = append(s2, 0, 1) // len 2, cap 2
printSlice(s2)
s3 = append(s3, 0, 1, 2) // len 3, cap 3
printSlice(s3)
s4 = append(s4, 0, 1, 2, 3) // len 4, cap 4
printSlice(s4)
s5 = append(s5, 0, 1, 2, 3, 4) // len 5, cap 6
printSlice(s5)
}
func printSlice(s []int) {
fmt.Println(len(s))
fmt.Println(cap(s))
}
Part4: slice append
如何快速完成一次 slice
数据填充?
- 声明一个
slice
直接开始append
- 声明固定长度
slice
后开始append
- 声明固定长度
slice
后,使用index
进行数据填充
可以看到方法三是最快的。
对于大数组的赋值,常用的优化方式还有一种 BCE,可以参考这篇文章中的用法,不再赘述。golang 边界检查优化
package main
import "testing"
func BenchmarkAppend(b *testing.B) {
var s []int
for i := 0; i < b.N; i++ {
s = append(s, i)
}
}
func BenchmarkMakeSliceAppend(b *testing.B) {
s := make([]int, 0, b.N)
for i := 0; i < b.N; i++ {
s = append(s, i)
}
}
func BenchmarkIndex(b *testing.B) {
s := make([]int, b.N)
for i := 0; i < b.N; i++ {
s[i] = i
}
}
// Output
// goos: darwin
// goarch: amd64
// cpu: Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
// BenchmarkAppend-8 457261122 14.93 ns/op
// BenchmarkMakeSliceAppend-8 1000000000 1.407 ns/op
// BenchmarkIndex-8 1000000000 1.246 ns/op
Part5: slice 扩容带来的”意外”
当使用 index 修改 slice 时,可能会出现”时而生效时而不生效的情况”,究其原因是 slice 在 grow 的过程中重新分配了内存地址。
下面这个情况展示接收 slice 的函数 sliceAppend
修改了 index 但是在函数外不生效的情况。
package main
import "fmt"
func main() {
var s []int
s = append(s, 1)
printSlice(s)
sliceAppend(s)
printSlice(s)
}
func sliceAppend(s []int) {
s = append(s, 1) // 此处发生了扩容操作,导致 s 的内存地址改变
s[0] = 0
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len: %v, cap: %v, val: %v\n", len(s), cap(s), s)
}
// Output:
// len: 1, cap: 1, val: [1]
// len: 2, cap: 2, val: [0 1]
// len: 1, cap: 1, val: [1]
其实,扩容的发生导致函数内外的可见性也不一样了。和上个例子差不多的一个案例。
可以看到,此处两个 slice s 打印出来,结果是不一样的。看起来就是函数内的 s 比函数外的 s 数据更多了!换句话说,在实际场景中,很有可能因为如下这样的误操作,导致看似操作过 s,但是数据缺丢失了。
package main
import "fmt"
func main() {
var s []int
s = append(s, 1)
printSlice(s)
sliceAppend(s)
printSlice(s)
}
func sliceAppend(s []int) {
s = append(s, 1) // 此处发生了扩容操作,导致 s 的内存地址改变
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len: %v, cap: %v, val: %v\n", len(s), cap(s), s)
}
// Output:
// len: 1, cap: 1, val: [1]
// len: 2, cap: 2, val: [1 1]
// len: 1, cap: 1, val: [1]
!!如果发生了扩容,修改会在新的内存中!!
所以针对 slice 的操作,务必使用 append 函数返回的 slice 对象进行后续操作,避免出现奇怪的数据异常!
Part6: slice 序列化
如下案例所示,slice 的 zero value 经过默认的 json 库序列化结果是 null
,但是初始化的 slice 经过默认的 json 库序列化结果就是 []
。
package main
import (
"encoding/json"
"fmt"
)
func main() {
var s []int
b, _ := json.Marshal(s)
fmt.Println(string(b))
}
// Output:
// null
package main
import (
"encoding/json"
"fmt"
)
func main() {
s := []int{}
b, _ := json.Marshal(s)
fmt.Println(string(b))
}
// Output:
// []
package main
import (
"encoding/json"
"fmt"
)
func main() {
s := make([]int, 0, 1)
b, _ := json.Marshal(s)
fmt.Println(string(b))
}
// Output:
// []
package main
import (
"encoding/json"
"fmt"
)
func main() {
s := make([]int, 1)
b, _ := json.Marshal(s)
fmt.Println(string(b))
}
// Output:
// [0]
TODO 如果有更多再补充
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 [email protected]