组成
指针、长度和容量
大小
slice占用24个字节
array:执向底层数组的指针,占用8个字节
len:切片的长度,占用8个字节
cap:切片的容量,占用8个字节
四种初始化方式
var slice1 []int
slice2 := []int{1, 2, 3, 4}
slice3 := make([]int,3,5)
// 从其他array中街区
slice4 := arr[1:3]
slice和array的区别
数组的长度固定,数组是值类型(值类型有基本数据类型,结构体类型),slice的值长度可变,属于引用类型(引用类型:字典类型,通道类型,函数类型)切片是引用传递,所以它们不需要使用额外的内存并且比使用数组更有效率
扩容策略
- 首先判断,如果新申请的容量(cap)大于2倍的旧容量(old.cap),最终容量就是新申请的容量
- 否则判断,如果旧切片的长度(len)小于1024,则最终容量就是旧容量的两倍
- 否则判断,如果旧切片长度(len)大于等于1024,则最终容量从旧容量开始循环增加原来的1/4,直到最终容量大于等于新申请的容量
- 如果最终容量计算值溢出,则最终容量就是新申请容量
slice深拷贝和浅拷贝
浅拷贝
a := []int{1, 2, 3}
b := a //地址的拷贝,浅拷贝
fmt.Println(a, b)
a[0] = 1000
fmt.Println(a, b)
b[2] = 4000
fmt.Println(a, b)
/*
执行结果
[1 2 3] [1 2 3]
[1000 2 3] [1000 2 3]
[1000 2 4000] [1000 2 4000]
*/
深拷贝
a := []int{1, 2, 3}
b := make([]int, 0) //创建新切片
b = append(b, a[:]...)
fmt.Println(a, b)
a[1] = 1000
fmt.Println(a, b)
fmt.Printf("%p,%p", a, b)
/*
执行结果
[1 2 3] [1 2 3]
[1 1000 3] [1 2 3]
0xc0000b4018,0xc0000b4030%
*/
线程安全性
slice是非线程安全的,因为底层数组是通过指针来引用的,该指针是非线程安全的(索引位覆写)
slice底层结构并没有使用加锁等方式,不支持并发读写,所以并不是线程安全的,使用多个 goroutine 对类型为 slice 的变量进行操作,每次输出的值大概率都不会一样,与预期值不一致; slice在并发执行中不会报错,但是数据会丢失
func main() {
var s []string
for i := 0; i < 9999; i++ {
go func() {
s = append(s, "脑子进煎鱼了")
}()
}
fmt.Printf("进了 %d 只煎鱼", len(s))
}
执行结果肯定对不上9999,这就是索引位覆写
解决办法:
- 加互斥锁
package main
import (
"fmt"
"sync"
"time"
)
var s []int
var lock sync.Mutex
func appendSlice(i int) {
lock.Lock()
s = append(s, i)
lock.Unlock()
}
func main() {
for i := 0; i < 10000; i++ {
go appendSlice(i)
}
time.Sleep(time.Second)
for i, v := range s {
fmt.Println(i, ":", v)
}
}
- 使用协程来维护slice
for {
data := <- chanList
list = append(list, data)
}