开发者

Golang基础常识性面试中常见的六大陷阱及应对技巧总结

开发者 https://www.devze.com 2024-08-15 11:57 出处:网络 作者: 少林码僧
目录一、nil slice & empty slice1、nil切片与空切片底层2、创建nil slice 和empty slice二、类型强转产生内存拷贝1、字符串转数组发送内存拷贝2、字符串转数组不内存拷贝方法三、拷贝大切片一定代价大吗?四、m
目录
  • 一、nil slice & empty slice
    • 1、nil切片与空切片底层
    • 2、创建nil slice 和empty slice
  • 二、类型强转产生内存拷贝
    • 1、字符串转数组发送内存拷贝
    • 2、字符串转数组不内存拷贝方法
  • 三、拷贝大切片一定代价大吗?
    • 四、map不初始化使用会怎么样
      • 五、map会遍历删除安全吗?
        • 六、for循环append坑
          • 1、坑1:添加元素变覆盖
          • 2、坑2:值全部一样
          • 3、解决方法
        • 总结

          一、nil slice & empty slice

          1、nil切片与空切片底层

          • nil切片:var nilSlice [] string

            • nil slice的长度len和容量hKYFiwcap都是0

            • nil slice==nil

            • nil slice的pointer是nil

          • 空切片:emptySlice0 := make([]int,0)

            • empty slice的长度是0,容量是由指向底层数组决定

            • empty slice != nil

            • empty slice的pointer是底层数组的地址

          • nil切片和空切片最大的区别在指向的数组引用地址是不一样的

          • nil空切片引用数组指针地址为0(无指向任何实际地址)

          Golang基础常识性面试中常见的六大陷阱及应对技巧总结

          • 空切片的引用数组指针地址是有的,且固定为一个值,所有的空切片指向的数组引用地址都是一样的

          Golang基础常识性面试中常见的六大陷阱及应对技巧总结

          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(nijslSlice, "sss")
          }
          /*
          Nil:true Len:0 Capacity:0
          nil:false Len:0 Capacity:0
          nil:false Len:0 Capacity:0[sss]
           */

          二、类型强转产生内存拷贝js

          1、字符串转数组发送内存拷贝

          • 字符串转成byte数组,会发生内存拷贝吗?

          • 字符串转出切片,会产生拷贝

          • 严格来说,只要是发送类型强转都会发送内存拷贝

          • 那么问题来了,频繁的内存拷贝操作听起来对性能不大友好

          • 有没有什么办法可以在字符串转出切片的时候不用发生拷贝呢?

          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
          }

          三、拷贝大切片一定代价大吗?

          • SliceHeader 是切片在go的底层结构。

            • 第一个字是指向切片底层数组的指针,这是切片的存储空间

            • 第二个字段是切片的长度

            • 第三个字段是容量

          type SliceHeader struct {
            Data uintptr
            Len  int
            Cap  int
          }
          • 大切片跟小切片的区别无非就是 Len 和 Cap的值比小切片的这两个值大一些,如果发生拷贝,本质上就是拷贝上面的三个字段。

          • 所以 拷贝大切片跟小切片的代价应该是一样的

          四、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)
          }

          五、map会遍历删除安全吗?

          • map 并不是一个线程安全的数据结构。

          • 同时读写一个 map 是未定义的行为,如果被检测到,会直接 panic。

          • 上面说的是发生在多个协程同时读写同一个 map 的情况下。

          • 如果在同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以这样做的。

          • sync.Map可以解决多线程读写map问题

            • 一般而言,这可以通过读写锁来解决:sync.RWMutex

            • 读之前调用 RLock() 函数,读完之后调用 RUnlock() 函数解锁;

            • 写之前调用 Lock() 函数,写完之后,调用&nbwww.devze.comsp;Unlock() 解锁。

            • 另外,sync.Map 是线程安全的 map,也可以使用

          六、for循环append坑

          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、坑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
           */

          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

            • 在for循环中每次再定义javascript一个新的变量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
           */

          总结

          到此这篇关于Golang基础常识性面试中常见的六大陷阱及应对技巧总结的文章就介绍到这了,更多相关Golang面试常见陷阱及应对内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

          0

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          关注公众号