GO 必知必会面试题汇总

互联网的就业环境越来越差了,应小伙伴们的强烈建议,你们要的 Go 面试题汇总他来了。
我们平常在工作中除了撸好代码,跑通项目之外,还要注意内外兼修。内功和招式都得练👌,才能应对突如其来的变故,顺利的拿到新的 offer。

值类型和引用类型
值类型有哪些?
基本数据类型都是值类型,包括:int 系列、float 系列、bool、字符串、数组、结构体 struct。
引用类型有哪些?
指针、切片 slice、接口 interface、管道 channel 以及 map
值类型和引用类型的区别?
值类型在内存中存储的是值本身,而引用类型在内存中存储的是值的内存地址。
值类型内存通常在栈中分配,引用类型内存通常在堆中分配。
垃圾回收
引用类型的内存在堆中分配,当没有任何变量引用堆中的内存地址时,该内存地址对应的数据存储空间就变成了垃圾,就会被 GO 语言的 GC 回收。
一图胜千言


堆和栈
栈
在 Go 中,栈的内存是由编译器自动进行分配和释放,栈区往往存储着函数参数、局部变量和调用函数帧,它们随着函数的创建而分配,函数的退出而销毁。
一个 goroutine 对应一个栈,栈是调用栈(call stack)的简称。一个栈通常又包含了许多栈帧(stack frame),它描述的是函数之间的调用关系,每一帧对应一次尚未返回的函数调用,它本身也是以栈形式存放数据。
堆
与栈不同的是,应用程序在运行时只会存在一个堆。狭隘地说,内存管理只是针对堆内存而言的。程序在运行期间可以主动从堆上申请内存,这些内存通过 Go 的内存分配器分配,并由垃圾收集器回收。
切片
比较
切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。
切片唯一合法的比较操作是和 nil 比较。
比较的详解
要检查切片是否为空,应该使用
来判断,而不应该使用
来判断。
原因是:一个 nil 值的切片并没有底层数组,一个 nil 值的切片的长度和容量都是 0。但是我们不能说一个长度和容量都是 0 的切片一定是 nil。
我们通过下面的示例就很好理解了:
所以要判断一个切片是否是空的,要是用 len(s) == 0 来判断,不应该使用 s == nil 来判断。
其根本原因在于后面两种初始化方式已经给切片分配了空间,所以就算切片为空,也不等于 nil。但是 len(s) == 0 成立,则切片一定为空。
注意:在 go 中 var 是声明关键字,不会开辟内存空间;使用 := 或者 make 关键字进行初始化时才会开辟内存空间。
深拷贝和浅拷贝
操作对象
深拷贝和浅拷贝操作的对象都是 Go 语言中的引用类型
区别如下:
引用类型的特点是在内存中存储的是其他值的内存地址;而值类型在内存中存储的是真实的值。
我们在 go 语言中通过 := 赋值引用类型就是 浅拷贝,即拷贝的是内存地址,两个变量对应的是同一个内存地址对应的同一个值。
如果我们通过 copy()函数进行赋值,就是深拷贝,赋值的是真实的值,而非内存地址,会在内存中开启新的内存空间。
举例如下:
new 和 make
new
new 是 GO 语言一个内置的函数,它的函数签名如下:
特点
Type 表示类型,new 函数只接受一个参数,这个参数是一个类型
*Type 表示类型指针,new 函数返回一个指向该类型内存地址的指针。
new 函数不太常用,使用 new 函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。
举个例子:
使用技巧
var a *int 只是声明了一个指针变量 a 但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。
应该按照如下方式使用内置的 new 函数对 a 进行初始化之后就可以正常对其赋值了:
make
make 也是用于内存分配的,区别于 new,它只用于 slice、map 以及 channel 的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型(指针类型),所以就没有必要返回他们的指针了。
make 函数的函数签名
特点
make 函数是无可替代的,我们在使用 slice、map 以及 channel 的时候,都需要使用 make 进行初始化,然后才可以对它们进行操作。
使用技巧
var b map[string]int
这段代码,只是声明变量 b 是一个 map 类型的变量,需要像下面的示例代码一样使用 make 函数进行初始化操作之后,才能对其进行键值对赋值:
总结:new 与 make 的区别
二者都是用来做内存分配的。
make 只用于 slice、map 以及 channel 的初始化,返回的是类型本身(类型本身就是引用类型(指针类型));
而 new 用于内存分配时,在内存中存储的是对应类型的型零值(比如 0,false),返回的是该类型的指针类型。
Go 的 map 如何实现排序
我们知道 Go 语言的 map 类型底层是由 hash 实现的,是无序的,不支持排序。
如果我们的数据使用 map 类型存储,如何实现排序呢?
解决思路
排序 map 的 key,再根据排序后的 key 遍历输出 map 即可。
代码实现:
运行结果

搞定,非常顺滑!
并发编程
Goroutine 和线程的区别?
内存占用:Goroutine 初始栈约 2KB 且可动态扩展,线程栈通常固定 2MB
调度方式:Goroutine 由 Go 运行时调度,线程由 OS 内核调度
切换成本:Goroutine 切换只需 120ns,线程切换需 1-2μs
通信机制:Goroutine 通过 channel 通信,线程通过共享内存需要同步原语
Channel 的缓冲机制?
无缓冲通道:发送接收操作会阻塞直到配对操作就绪
有缓冲通道:缓冲区满时发送阻塞,缓冲区空时接收阻塞
sync.Map 的实现原理?
空间换时间:通过两个 map(read 和 dirty)实现读写分离
原子操作:使用 atomic.Value 保证 read map 的原子访问
延迟删除:删除操作先标记再物理删除
适用场景:多读少写、键值对变化少的场景
接口与反射
空接口与类型断言
接口的底层实现?
Go 接口采用双指针结构:
类型指针:指向接口动态类型信息
数据指针:指向实际数据值
错误处理
panic/recover 机制要点
recover 必须在 defer 函数中调用才有效
panic 会逆序执行当前 goroutine 的 defer 链
未捕获的 panic 会导致程序崩溃
典型应用模式:
高级特性
Context 包的核心作用
链路控制:传递请求上下文信息(如 traceID)
超时控制:
取消传播:通过 Done()通道实现级联取消
defer 的执行顺序
遵循 LIFO 原则(后进先出):
注意:defer 后的函数参数会立即求值
性能优化
内存对齐原则
通过调整结构体字段顺序减少内存占用:
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:infoq 面试群。
版权声明: 本文为 InfoQ 作者【王中阳Go】的原创文章。
原文链接:【http://xie.infoq.cn/article/0b8245b5adc2c25370af963b1】。文章转载请联系作者。
评论