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怎么实现
更多精彩文章请访问: 百考汇