1.切片 VS 数组
列表 | 数组 | 切片 |
---|---|---|
类型 | 值类型 | 引用类型 |
长度 | 初始化后长度是固定的 | 长度可以变化 |
初始化方法① | [5] int {1,2} | s :=make([]int,len,cap) //内置函数make()初始化 .初始化时len=cap,在追加元素时如果容量cap不足时将按len的2倍扩容 |
初始化方法② | [...] int {1,2,3,4,5};//有....自动计算 | s :=[] int {1,2,3 } //直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3 |
初始化方法③ | [5] int { 2:1,3:2,4:3}; | s := arr[startIndex:endIndex] //将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片 |
初始化方法④ | [...] int {2:1,4:3}//长度为5的数组,起元素值依次为:0,0,1,0,3。由于指定了最大索引4对应的值3,根据初始化的元素个数确定其长度为5赋值与使用 | xx |
访问方法① | 通过下标访问元素,可修改其元素值:arr[1] | 同左 |
访问方法② | 通过for遍历数组元素:for index, value := range arr | 同左 |
其他 | 你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份(匿名拷贝)改变原值,则新值不会变 | 添加:①s :=append(s,1,2,3,4);②s :=append(s,s1...) |
切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一个只读对象,其工作机制类似数组指针的一种封装。
切片三要素:
指针:指向地址
长度:len
容量:cap
切片初始化,len=cap
切片是引用类型(新地址,指向原地址上的值),最大的特点是,引用的地址上值改变,切片也会改变
slice := []int{1, 2, 3} //刚开始len=cap
newSlice := append(slice, 4, 5) //扩容,触发了拷贝(新建地址)
newnewSlice := slice[:] //切片的引用
slice[0] = 88
fmt.Printf("slice:%+v, newSlice:%+v, newnewSlice:%+v\n", slice, newSlice, newnewSlice)
//slice:[88 2 3], newSlice:[1 2 3 4 5], newnewSlice:[88 2 3]
另外,数组作为函数的参数,那么实际传递的参数是一份数组的拷贝,而不是数组的指针。
所以函数中对数组改变,返回后数组不会改变
A(arr)
func A(arr []int){
arr[0]=9
}
1.切片vs数组
2.切片是引用传递,所以它们不需要使用额外的内存并且比使用数组更有效率。
3.空切片 vs nil切片
4.切片扩容:
4.1 扩容策略
如果切片的容量小于 1024 个元素,于是扩容的时候就翻倍增加容量。上面那个例子也验证了这一情况,总容量从原来的4个翻倍到现在的8个。
一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25 ,即每次增加原来容量的四分之一。(××××××××××错,发现了1,2,4,6,8规律)
注意:扩容扩大的容量都是针对原来的容量而言的,而不是针对原来数组的长度而言的。
4.2扩容是生成全新的内存地址还是在原来的地址后追加?
4.2.1 slice创建方法一:【分扩容情况,是否扩容】
slice := []int{10, 20, 30, 40}
newSlice := append(slice, 50)
Go 默认会先开一片内存区域,把原来的值拷贝过来,然后再执行 append() 操作。这种情况丝毫不影响原数组。
4.2.2 切片字面量创建【新地址】
由于原数组还有容量可以扩容,所以执行 append() 操作以后,会在原数组上直接操作,所以这种情况下,扩容以后的数组还是指向原来的数组。
array := [4]int{10, 20, 30, 40}
slice := array[0:2]
newSlice := append(slice, 50)
func main() {
arrayA := [2]int{100, 200}
testArrayPoint(&arrayA) // 1.传数组指针
arrayB := arrayA[:]
testArrayPoint(&arrayB) // 2.传切片 ****
fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA)
}
func testArrayPoint(x *[]int) {
fmt.Printf("func Array : %p , %v\n", x, *x)
(*x)[1] += 100
}
打印结果:
func Array : 0xc4200b0140 , [100 200]
func Array : 0xc4200b0180 , [100 300]
arrayA : 0xc4200b0140 , [100 400]
并非所有时候都适合用切片代替数组,因为切片底层数组可能会在堆上分配内存,而且小数组在栈上拷贝的消耗也比make 消耗小
2. map
sync/atomic的使用
Go之 unsafe.Pointer
- context
学习链接
总结:
- 关于cancel
withCance 会传递 cancel信号到子context中。
如果某层context cancel了,则会向它的所有子值(或者说子节点)传达撤销信号。这些子值会如法炮制,把撤销信号继续传播下去。最后,这个Context值会断开它与其父值之间的关联。
- 关于cancel
-
2.关于context 中的value
设置传递时,会传递到所有子context中。
取值key时,回去查找本context,如果没找到key,会向上去查找所有父的context查找 - 关于grpc的timeout
客户端调用时,加了timeout参数,会在http2的header中添加grpc-timeout参数
服务端获取参数,判断相关逻辑。
- 关于grpc的timeout
网友评论