不做大哥好多年 不做大哥好多年
首页
  • MySQL
  • Redis
  • Elasticsearch
  • Kafka
  • Etcd
  • MongoDB
  • TiDB
  • RabbitMQ
  • 01.GO基础
  • 02.面向对象
  • 03.并发编程
  • 04.常用库
  • 05.数据库操作
  • 06.Beego框架
  • 07.Beego商城
  • 08.GIN框架
  • 09.GIN论坛
  • 10.微服务
  • 01.Python基础
  • 02.Python模块
  • 03.Django
  • 04.Flask
  • 05.SYL
  • 06.Celery
  • 10.微服务
  • 01.Java基础
  • 02.面向对象
  • 03.Java进阶
  • 04.Web基础
  • 05.Spring框架
  • 100.微服务
  • Docker
  • K8S
  • 容器原理
  • Istio
  • 数据结构
  • 算法基础
  • 算法题分类
  • 前置知识
  • PyTorch
  • 01.Python
  • 02.GO
  • 03.Java
  • 04.业务问题
  • 05.关键技术
  • 06.项目常识
  • 10.计算机基础
  • Linux基础
  • Linux高级
  • Nginx
  • KeepAlive
  • ansible
  • zabbix
  • Shell
  • Linux内核

逍遥子

不做大哥好多年
首页
  • MySQL
  • Redis
  • Elasticsearch
  • Kafka
  • Etcd
  • MongoDB
  • TiDB
  • RabbitMQ
  • 01.GO基础
  • 02.面向对象
  • 03.并发编程
  • 04.常用库
  • 05.数据库操作
  • 06.Beego框架
  • 07.Beego商城
  • 08.GIN框架
  • 09.GIN论坛
  • 10.微服务
  • 01.Python基础
  • 02.Python模块
  • 03.Django
  • 04.Flask
  • 05.SYL
  • 06.Celery
  • 10.微服务
  • 01.Java基础
  • 02.面向对象
  • 03.Java进阶
  • 04.Web基础
  • 05.Spring框架
  • 100.微服务
  • Docker
  • K8S
  • 容器原理
  • Istio
  • 数据结构
  • 算法基础
  • 算法题分类
  • 前置知识
  • PyTorch
  • 01.Python
  • 02.GO
  • 03.Java
  • 04.业务问题
  • 05.关键技术
  • 06.项目常识
  • 10.计算机基础
  • Linux基础
  • Linux高级
  • Nginx
  • KeepAlive
  • ansible
  • zabbix
  • Shell
  • Linux内核
  • Python

  • GO

    • 01.GMP模型 ✅
    • 02.Map原理 ✅
    • 03.sync_map ✅
    • 04.sync.Pool
    • 05.垃圾回收 ✅
    • 06.channel
    • 07.内存逃逸
    • 08.内存泄漏
    • 09.mutex锁原理
    • 10.Go常识
    • 50.Golang常犯错误
    • 51.常见坑1~10
      • 01.nil slice & empty slice
        • 1.1 nil切片与空切片底层
        • 1.2 创建nil slice和empty slice
      • 02.类型强转产生内存拷贝
        • 1.1 字符串转数组发送内存拷贝
        • 1.2 字符串转数组不内存拷贝方法
        • 1.3 解释
      • 03.拷贝大切片一定代价大吗
      • 04.map不初始化使用会怎么样
      • 05.map遍历删除安全吗
      • 06.for循环append坑
        • 6.1 坑1:添加元素变覆盖
        • 6.2 坑2:值全部一样
        • 6.3 解决方法
  • Java

  • 业务问题

  • 关键技术

  • 项目常识

  • 计算机基础

  • 常识
  • GO
xiaonaiqiang
2021-06-22
目录

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,
  • 空切片: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]
 */
1
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.再通过 *转为指针指向的实际内容
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1.3 解释

  • StringHeader 是字符串在go的底层结构
type StringHeader struct {
	Data uintptr
	Len  int
}
1
2
3
4
  • SliceHeader 是切片在go的底层结构
type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}
1
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.再通过 *转为指针指向的实际内容

# 03.拷贝大切片一定代价大吗

拷贝大切片一定比小切片代价大吗?

  • SliceHeader 是切片在go的底层结构
    • 第一个字是指向切片底层数组的指针,这是切片的存储空间
    • 第二个字段是切片的长度
    • 第三个字段是容量
type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}
1
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)
}
1
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
 */
1
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
 */
1
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)
	}
}
1
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
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
上次更新: 2024/10/15 16:27:13
50.Golang常犯错误
01.Java基础

← 50.Golang常犯错误 01.Java基础→

最近更新
01
04.数组双指针排序_子数组
03-25
02
08.动态规划
03-25
03
06.回溯算法
03-25
更多文章>
Theme by Vdoing | Copyright © 2019-2025 逍遥子 技术博客 京ICP备2021005373号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式