1.array和slice差别:
- 长度可变
- 函数传参
- 计算数据长度的方式
array
- 长度不可变,初始化必须指定长度
- 是值类型的,将一个数组赋值给另一个数组时,传递的是一份深拷贝,会占用额外的内存,函数内对数组元素值的修改,不会修改原数组内容
- 数组需要遍历计算长度,时间复杂度是O(n)
slice
- 长度不固定,通过append追加元素,cap不够容纳元素时会进行扩容
- 是引用类型的,将一个切片赋值给另一个切片,传递的是浅拷贝,函数传参操作不会拷贝整个切片,只会赋值len和cap,底层共用是同一个数组,不会占用额外的内存,函数内对数组元素值的修改,会修改数组内容
- 通过len直接获取,时间复杂度是O(n)
2.slice底层实现
占用24个字节
type slice struct{
array unsafe.Pointer//一个指向数组的指针 //占用8个字节
len int //占用8个字节
cap int //占用8个字节
}
初始化方式:
1.直接声明
var slice1 []int
2.使用字面量
slice2 := []int{1,2,3,4}
3.使用make
slice3 := make([]int,3,5)
4.从切片/数组截取
slice4 := arr[1:3]
追加
slice1 := append(slice1,1)
获取长度
len(slice1)
获取容量
cap(slice1)
扩容机制
扩容时机:发生在slice append时,当slice的cap不足以容纳新元素,就会进行扩容
append() 底层逻辑
- 计算追加后slice的总长度n
- 如果总长度n大于原cap,则调用growslice计算容量大小,并申请内存(cap最小为n,具体扩容规则见growslice)
- 对扩容后的slice进行切片,长度为n,获取slice s,用以存储所有的数据
- 根据不同的数据类型,调用对应的复制方法,将原slice及追加的slice的数据复制到新的slice
growslice 计算容量大小的逻辑
- 如果新申请容量比2倍原有容量大,扩容大小为新申请容量
- 如果原slice长度小于1024,则扩容为原来2倍
- 否则在原slice长度大于等于1024,每次扩容为原来1.25倍
package main import "fmt" func main() { s1 := []int{} fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 1) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 2) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 3) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 4) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 5) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 6) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 7) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 8) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) s1 = append(s1, 9) fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1)) } /* len=0,cap=0 len=1,cap=1 len=2,cap=2 len=3,cap=4 len=4,cap=4 len=5,cap=8 len=6,cap=8 len=7,cap=8 len=8,cap=8 len=9,cap=16 */
- 注意:用range拿到的切片元素是值引用的,因此修改无效,只能通过arr[i]的形式修改切片的值
3.slice深拷贝
深拷贝和浅拷贝(+)
深拷贝:
拷贝的是数据本身
在内存中开辟一个新的内存地址,复制值过去,新对象和旧对象不共享内存
修改时互不影响
浅拷贝:
拷贝的是数据地址
只复制指向对象的指针,此时新对象和老对象指向的内存地址一样
引用类型变量默认赋值操作为浅拷贝方式
修改时一个变另一个也变
slice浅拷贝实现方式:
slice1:=slice2
slice深拷贝实现方式:
- copy(slice1,slice2)
- 遍历append赋值
值传递和引用传递
值传递
将实参的值传给形参,形参是实参的一份拷贝,实参和形参的内存地址不同,函数内对形参值内容的修改,是否会影响实参的值内容,取决于参数是否是引用类型
引用传递
将实参的地址传给形参,函数内对于形参值内容的修改,将会影响实参的值内容
Go语言没有引用传递,C++有
即使参数类型是指针(p *Pointer)或其他引用类型,函数内指针地址也会改变,但是指针指向的地址不变,所以可以通过指针修改原数据内容
4.slice为什么不是线程安全的(+)
线程安全:
多线程同时对一个对象进行并发读写,调用这个对象的行为都可以获得正确的结果
若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全
go实现线程安全常用的几种方式:
1.互斥锁
2.读写锁
3.原子操作
4.sync.once
5.sync.atomic
6.channel
slice底层没有使用加锁等方式,不支持并发读写
在并发读写时不会报错,但是会数据丢失