51.常见坑1~10
# 01.nil slice & empty slice
# 1.1 nil切片与空切片底层
nil切片:
var nilSlice []string
- nil slice 的
长度len和容量cap都是0
nil slice == nil
- nil slice的pointer 是nil,
- nil slice 的
空切片:
emptySlice0 := make([]int, 0)
- empty slice的长度是0, 容量是由指向底层数组决定
empty slice != nil
- empty slice的pointer是底层数组的地址
nil切片和空切片最大的区别在于指向的数组引用地址是不一样的
nil空切片引用数组指针地址为0(无指向任何实际地址)
空切片的引用数组指针地址是有的,且固定为一个值
,所有的空切片指向的数组引用地址都是一样的
# 1.2 创建nil slice和empty slice
package main
import "fmt"
func main() {
var nilSlice []string // 创建一个 nil 切片
emptySlice0 := make([]int, 0) // 方法1:创建一个空切片(零切片)
var emptySlice1 = []string{} // 方法2:创建一个空切片
fmt.Printf("\nnilSlice---> Nil:%v Len:%d Capacity:%d", nilSlice == nil, len(nilSlice), cap(nilSlice))
fmt.Printf("\nemptySlice0---> nil:%v Len:%d Capacity:%d", emptySlice0 == nil, len(emptySlice0), cap(emptySlice0))
fmt.Printf("\nemptySlice1---> nil:%v Len:%d Capacity:%d", emptySlice1 == nil, len(emptySlice1), cap(emptySlice1))
// nil切片和空切片都可以正常 append数据
nilSlice = append(nilSlice, "sss")
}
/*
Nil:true Len:0 Capacity:0
nil:false Len:0 Capacity:0
nil:false Len:0 Capacity:0[sss]
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 02.类型强转产生内存拷贝
# 1.1 字符串转数组发送内存拷贝
字符串转成byte数组,会发生内存拷贝吗?
字符串转成切片,会产生拷贝
严格来说,只要是发生类型强转都会发生内存拷贝
那么问题来了,频繁的内存拷贝操作听起来对性能不大友好
有没有什么办法可以在字符串转成切片的时候不用发生拷贝呢?
# 1.2 字符串转数组不内存拷贝方法
那么如果想要在底层转换二者,只需要把
StringHeader
的地址强转成SliceHeader
就行那么go有个很强的包叫unsafe
- 1.
unsafe.Pointer(&a)
方法可以得到变量a
的地址- 2.
(*reflect.StringHeader)(unsafe.Pointer(&a))
可以把字符串a转成底层结构的形式 - 3.
(*[]byte)(unsafe.Pointer(&ssh))
可以把ssh底层结构体转成byte的切片的指针 - 4.再通过
*
转为指针指向的实际内容
- 2.
- 1.
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
a :="aaa"
ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a))
b := *(*[]byte)(unsafe.Pointer(&ssh))
fmt.Printf("%v---%T",b,b) // [97 97 97]---[]uint8
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.3 解释
StringHeader
是字符串
在go的底层结构
type StringHeader struct {
Data uintptr
Len int
}
2
3
4
SliceHeader
是切片
在go的底层结构
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
2
3
4
5
那么如果想要在底层转换二者,只需要把
StringHeader
的地址强转成SliceHeader
就行那么go有个很强的包叫
unsafe
- 1.
unsafe.Pointer(&a)
方法可以得到变量a
的地址- 2.
(*reflect.StringHeader)(unsafe.Pointer(&a))
可以把字符串a转成底层结构的形式 - 3.
(*[]byte)(unsafe.Pointer(&ssh))
可以把ssh底层结构体转成byte的切片的指针 - 4.再通过
*
转为指针指向的实际内容
- 2.
- 1.
# 03.拷贝大切片一定代价大吗
拷贝大切片一定比小切片代价大吗?
SliceHeader
是切片
在go的底层结构- 第一个字是指向切片
底层数组的指针
,这是切片的存储空间 - 第二个字段是
切片的长度
- 第三个字段是
容量
- 第一个字是指向切片
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
2
3
4
5
- 大切片跟小切片的区别无非就是
Len
和Cap
的值比小切片的这两个值大一些,如果发生拷贝,本质上就是拷贝上面的三个字段 - 所以 拷贝大切片跟小切片的代价应该是一样的
# 04.map不初始化使用会怎么样
- 空map和nil map结果是一样的,都为map[]
- 所以,这个时候别断定map是空还是nil,而应该通过map == nil来判断
package main
func main() {
var m1 map[string]string // 创建一个 nil map
println("m1为nil: ", m1==nil)
// 报错 => panic: assignment to entry in nil map
//m1["name"] = "tom"
var m2 = make(map[string]string) // 创建一个空map
m2["name"] = "jack" // 空map可以正常
println("m2为nil: ", m2==nil)
}
2
3
4
5
6
7
8
9
10
11
12
# 05.map遍历删除安全吗
map 并不是一个线程安全的数据结构
同时读写一个 map 是未定义的行为,如果被检测到,会直接 panic
上面说的是发生在多个协程同时读写同一个 map 的情况下
如果在同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以这样做的
sync.Map可以解决多线程读写map问题
一般而言,这可以通过读写锁来解决:
sync.RWMutex
读之前调用
RLock()
函数,读完之后调用RUnlock()
函数解锁;写之前调用
Lock()
函数,写完之后,调用Unlock()
解锁另外,
sync.Map
是线程安全的 map,也可以使用
# 06.for循环append坑
# 6.1 坑1:添加元素变覆盖
- 不会死循环,
for range
其实是golang
的语法糖
,在循环开始前会获取切片的长度len(切片)
,然后再执行len(切片)
次数的循环
package main
import "fmt"
func main() {
s := []int{1,2,3,4,5}
for _, v:=range s {
s =append(s, v)
fmt.Printf("len(s)=%v\n",len(s))
}
}
/*
len(s)=6
len(s)=7
len(s)=8
len(s)=9
len(s)=10
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 6.2 坑2:值全部一样
每次循转中num的值是正常的,但是由append构造的res中,全是nums的最后一个值
最终总结出原因是在for range语句中,创建了变量num且只被创建了一次
即num有自己的空间内存且地址在for循环过程中不变
循环过程中每次将nums中对应的值和num进行值传递
package main
import "fmt"
func main() {
var nums = []int{1, 2, 3, 4, 5}
var res []*int
for _, num := range nums {
res = append(res, &num)
//fmt.Println("num:", num)
}
for _, r := range res {
fmt.Println("res:", *r)
}
}
/*
res: 5
res: 5
res: 5
res: 5
res: 5
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 6.3 解决方法
方法1
- 不使用for range的形式,直接用索引来对nums取值
package main
import "fmt"
func main() {
var nums = []int{1, 2, 3, 4, 5}
var res []*int
for i := 0; i < len(nums); i++ {
res = append(res, &nums[i])
}
fmt.Println("res:", res)
for _, r := range res {
fmt.Println("res:", *r)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
方法2
- 在for循环中每次再定义一个新的变量num_temp,将num的值传给num_temp,之后append该变量即可
package main
import "fmt"
func main() {
var nums = []int{1, 2, 3, 4, 5}
var res []*int
for _, num := range nums {
numTemp := num // 创建一个新的临时变量
res = append(res, &numTemp)
}
for _, r := range res {
fmt.Println("res:", *r)
}
}
/*
res: 1
res: 2
res: 3
res: 4
res: 5
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24