常见坑1~10
1.常见坑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
02.类型强转产生内存拷贝
1.1 字符串转数组发送内存拷贝
- 字符串转成byte数组,会发生内存拷贝吗?
- 字符串转成切片,会产生拷贝。
- 严格来说,只要是发生类型强转都会发生内存拷贝。
- 那么问题来了,频繁的内存拷贝操作听起来对性能不大友好。
- 有没有什么办法可以在字符串转成切片的时候不用发生拷贝呢?
1.2 字符串转数组不内存拷贝方法
那么如果想要在底层转换二者,只需要把
StringHeader
的地址强转成SliceHeader
就行。那么go有个很强的包叫unsafe
。方法可以得到变量
的地址。
- 2.
(*reflect.StringHeader)(unsafe.Pointer(&a))
可以把字符串a转成底层结构的形式。 - 3.
(*[]byte)(unsafe.Pointer(&ssh))
可以把ssh底层结构体转成byte的切片的指针。 - 4.再通过
*
转为指针指向的实际内容。
- 2.
1.3 解释
StringHeader
是字符串
在go的底层结构。
SliceHeader
是切片
在go的底层结构。
那么如果想要在底层转换二者,只需要把
StringHeader
的地址强转成SliceHeader
就行。那么go有个很强的包叫
unsafe
。方法可以得到变量
的地址。
- 2.
(*reflect.StringHeader)(unsafe.Pointer(&a))
可以把字符串a转成底层结构的形式。 - 3.
(*[]byte)(unsafe.Pointer(&ssh))
可以把ssh底层结构体转成byte的切片的指针。 - 4.再通过
*
转为指针指向的实际内容。
- 2.
03.拷贝大切片一定代价大吗
- 切片
大切片跟小切片的区别无非就是
Len
和Cap
的值比小切片的这两个值大一些,如果发生拷贝,本质上就是拷贝上面的三个字段。所以 拷贝大切片跟小切片的代价应该是一样的。
04.map不初始化使用会怎么样
- 空map和nil map结果是一样的,都为map[]。
- 所以,这个时候别断定map是空还是nil,而应该通过map == nil来判断。
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(切片)
次数的循环。
6.2 坑2:值全部一样
- 每次循转中num的值是正常的,但是由append构造的res中,全是nums的最后一个值。
- 最终总结出原因是在for range语句中,创建了变量num且只被创建了一次。
- 即num有自己的空间内存且地址在for循环过程中不变
- 循环过程中每次将nums中对应的值和num进行值传递。
6.3 解决方法
常见坑1~10
http://coderedeng.github.io/2022/04/21/Go进阶 - 常见坑1~10/