golang之基础面试题

1、for select时,如果通道已经关闭会怎么样?如果只有一个case呢?

  • for循环select时,如果其中一个case通道已经关闭,则每次都会执行到这个case。

  • 如果select里边只有一个case,而这个case被关闭了,则会出现死循环
package main

import (
   "fmt"
   "time"
)

const (
   fmat = "2006-01-02 15:04:05"
)
func main()  {
   c := make(chan int)
   go func() {
      time.Sleep(1 * time.Second)
      c <- 10
      close(c)
   }()
   for {
      select {
         case x,ok := <-c:
            fmt.Printf("%v,通道读取到:x=%v,ok=%v\n", time.Now().Format(fmat),x ,ok)
            time.Sleep(500 * time.Millisecond)
         default:
            fmt.Printf("%v,没读到信息进入default\n", time.Now().Format(fmat))
            time.Sleep(500 * time.Millisecond)

      }
   }
}

2、nil切片和空切片一不一样呢?

   首先来看下切片底层数据结构:

   

type SliceHeader struct {
    Data uintptr  //指向的引用数组地址
    Len  int     //切片长度
    Cap  int     //切片容量
}

  不难理解: nil 切片和空切片是 Len 和 Cap 都是 0。
  关键看 nil 切片和空切片的 Data (即指向的引用数组地址) 的区别,不妨将他们的值打印出来,看到底有什么区别?

  

func main(){
var slice1 []int   //nil 切片
var slice2 []int   //nil 切片
slice3 := []int{}  //空切片
slice4 := make([]int,0) //空切片
fmt.Printf("slice1 Data:%+v, slice2 Data:%+v, slice3 Data:%+v,  slice4 Data:%+v,\n", *(*reflect.SliceHeader)(unsafe.Pointer(&slice1)),*(*reflect.SliceHeader)(unsafe.Pointer(&slice2)),*(*reflect.SliceHeader)(unsafe.Pointer(&slice3)),*(*reflect.SliceHeader)(unsafe.Pointer(&slice4)))
}

输出结果:

slice1 Data:{Data:0 Len:0 Cap:0}, slice2 Data:{Data:0 Len:0 Cap:0}, slice3 Data:{Data:824634158760 Len:0 Cap:0},  slice4 Data:{Data:824634158760 Len:0 Cap:0}

1). nil 切片 Data 值为 0(指向的引用数组地址为 0,可以理解是一个无效地址)
2). 空切片 Data 值为 824634158760,指向的引用数组地址是一个固定值。

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

    简单转换:

     

package main

import (
	"fmt"
)

func main()  {
	var str string
	str = "abd"
	arr := []byte(str)
	fmt.Println(arr)
}

 字符串转成切片,会产生拷贝。严格来说,只要是发生类型强转都会发生内存拷贝。那么问题来了。(初级工程师只回答这个就行了)
频繁的内存拷贝操作听起来对性能不大友好。有没有什么办法可以在字符串转成切片的时候不用发生拷贝呢?

代码实现

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    a :="aaa"
    ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a))
    b := *(*[]byte)(unsafe.Pointer(&ssh))  
    fmt.Printf("%v",b)
}

解释

  • StringHeader字符串在go的底层结构。
type StringHeader struct {
	Data uintptr
	Len  int
}
  • SliceHeader切片在go的底层结构。
type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

1).unsafe.Pointer(&a)方法可以得到变量a的地址。

2).(*reflect.StringHeader)(unsafe.Pointer(&a))可以把字符串a转成底层结构的形式。

3).(*[]byte)(unsafe.Pointer(&ssh))可以把ssh底层结构体转成byte的切片的指针。

4).再通过 *转为指针指向的实际内容。

4、翻转含有中文、数字、英文的字符串

      

package main

import (
	"fmt"
)

func main()  {
	src := "你好abc啊"
	dst := reverse([]rune(src))
	fmt.Printf("翻转之后的字符串是:%v\n", string(dst))

}

func reverse(s []rune) []rune {
	for i, j := 0, len(s)-1; i < j; i,j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
	return s
}

解释:
rune关键字,从golang源码中看出,它是int32的别名(-2^31 ~ 2^31-1),比起byte(-128~127),可表示更多的字符。

由于rune可表示的范围更大,所以能处理一切字符,当然也包括中文字符。在平时计算中文字符,可用rune。

因此将字符串转为rune的切片,再进行翻转,完美解决。
 

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

并不是,所有切片的大小相同;三个字段(一个 uintptr,两个int)。切片中的第一个字是指向切片底层数组的指针,这是切片的存储空间,第二个字段是切片的长度,第三个字段是容量。将一个 slice 变量分配给另一个变量只会复制三个机器字。所以 拷贝大切片跟小切片的代价应该是一样的

解释

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

6、对未初始化的chan进行读写,会怎样?为什么?

     读写未初始化chan都会阻塞。  

   多问一句

     关于chan的面试题非常多,这个是比较常见的其中一个。但多问一句:为什么对未初始化的chan就会阻塞呢?

1).对于写的情况

    

//在 src/runtime/chan.go中
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
	if c == nil {
      // 不能阻塞,直接返回 false,表示未发送成功
      if !block {
        return false
      }
      gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
      throw("unreachable")
	}
  // 省略其他逻辑
}
  • 未初始化的chan此时是等于nil,当它不能阻塞的情况下,直接返回 false,表示写 chan 失败
  • chan能阻塞的情况下,则直接阻塞 gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2), 然后调用throw(s string)抛出错误,其中waitReasonChanSendNilChan就是刚刚提到的报错"chan send (nil chan)"

2). 对于读的情况

//在 src/runtime/chan.go中
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
    //省略逻辑...
    if c == nil {
        if !block {
          return
        }
        gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
        throw("unreachable")
    }
    //省略逻辑...
} 
  • 未初始化的chan此时是等于nil,当它不能阻塞的情况下,直接返回 false,表示读 chan 失败
  • chan能阻塞的情况下,则直接阻塞 gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2), 然后调用throw(s string)抛出错误,其中waitReasonChanReceiveNilChan就是刚刚提到的报错"chan receive (nil chan)"

7、map不初始化使用会怎么样?

     不会报错,会有默认值;int会默认为0,字符串默认为空串

8、map不初始化长度和初始化长度的区别

     

9、map承载有多大,大了怎么办

10、map的iterator是否安全?能不能一般delete一边便利

11、字符串不能改,那转成数组能改吗,怎么改

12、怎么判断一个数组是否已经排序

13、普通map如何不用锁解决协程安全问题

14、array和slice的区别

15、json包变量不加tag会怎么样

16、零切片、空切片、nil切片是什么

17、slice深拷贝和浅拷贝

18、map触发扩容的时机,满足什么条件时扩容?

19、map扩容策略是什么

20、自定义类型切片转字节切片和字节切片转回自定义类型切片

21、make和new什么区别

22、slice,map,chanel创建的时候的几个参数什么含义

        这几个类型的函数使用时必须先make一下。

23、线程安全的map怎么实现

更多精彩文章请访问: 百考汇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值