1、对于slice的截取
s := make([]int, 10000)
fmt.Println("s len:", len(s), " cap:", cap(s))
a := s[:3]
fmt.Println("a len:", len(a), " cap:", cap(a))
b := s[3:]
fmt.Println("b len:", len(b), " cap:", cap(b))
输出:
s len: 10000 cap: 10000
a len: 3 cap: 10000
b len: 9997 cap: 9997
2、channel的有无缓冲、关闭与否
// 无缓冲
c1 := make(chan int)
// 有缓冲
c2 := make(chan int, 1)
// 情况1
c1 = nil
// action ① 输出什么
c1 <- 1
// action ② 输出什么
rlt := <- c1
// 情况2
close(c1)
// action ① 输出什么
c1 <- 1
// action ② 输出什么
rlt = <- c1
// 情况3
c2 <- 1
close(c2)
// action ① 输出什么
c2 <- 2
// action ② 输出什么
rlt = <- c2
答案:
给⼀个 nil channel 发送数据,造成永远阻塞
从⼀个 nil channel 接收数据,造成永远阻塞
给⼀个已经关闭的 channel 发送数据,引起 panic
const a = 1
func main(){
fmt.Println(&a)
}
s := "123"
sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
b := []byte{}
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len
defer func(){
e := recover()
fmt.Println(e)
}()
b[0] = '4'
b[0] = '4'这行会报错,并且recover不了。因为string是不可修改的,你变相修改。
6、字节长度和字符长度
import "fmt"
import "unicode/utf8"
func main(){
s := "今天"
fmt.Println("length bytes",len(s))
fmt.Println("length chars", utf8.RuneCountInString(s))
}
输出:
length bytes 6
length chars 2
7、unsafe包的用途
绕过Go语言的类型系统,直接操作内存,例如:我们一般不能操作结构体的未导出成员,但是通过unsafe包就能做到,unsafe包让我们可以直接读写内存,不需要管导出与未导出
8、slice的扩容策略
切片的扩容都是调用 growSlice 方法;
如果期望的大小比2倍的当前容量还大,那么直接用期望的大小;否则,如果期望的大小小于1024,则用2倍的当前容量,如果大于1024,则for循环,每次给当前容量增长1.25倍,直到满足需求。
9、内存逃逸
go内存管理器会给函数分配一个栈帧,用来存放栈上分配的变量,比如形参,局部变量,返回值。编译器会在函数执行完成后自动释放栈帧,但是有的变量由于一些需求,我们不希望被释放,这时候这种变量(一般是指针对象)就会逃逸到堆空间,这就是内存逃逸。
go提供了一个在编译期间的工具,用来分析代码中的逃逸情况:
go builds -gcflags '-m -m -l'
常见的逃逸情况:
① 返回值是指针对象
② interface{}类型的变量,动态变量,需要在运行中才能确定
③ 超大的变量
④ 闭包函数,闭包会使用函数域内的变量,当闭包结束的时候,函数内部还在引用这个变量,并没有随着闭包而释放
10、GC的原理
go语言会在运行时有通过垃圾回收机制(GC)来对堆上的内存空间进行自动回收,减轻开发人员的负担,以确保内存空间的连续性和有效性。但是GC带来的开销随着空间的浪费和滥用而增加,GC需要定时遍历哪些堆上的对象是不可达的,将其清除掉。目前GC用的是标记清除法,对堆上对象通过三色标记和写屏障来清除不可达的对象。