今天继续分享热乎乎的面经,来自一家中小厂的Go后端开发岗,薪资是10k左右,因为面试问到的问题我觉得都挺有代表性的,所以就想分享出来给大家学习学习:
面经详解
数组和切片的区别
回答:
数组和切片虽然都是存储同类型元素的集合,但核心区别主要体现在以下几个方面:
定义与类型
- 数组是固定长度的数据结构,声明时必须指定长度,例如
var arr [5]int
,其类型包含长度信息(如[3]int
和[5]int
是不同类型)。
• 切片是动态长度的,基于底层数组封装,类型仅包含元素类型(如[]int
),无需指定长度。切片的结构包含指针(指向底层数组)、长度和容量三个字段。
- 数组是固定长度的数据结构,声明时必须指定长度,例如
内存分配与传递
- 数组是值类型,赋值或传参时会发生完整复制,适合需要强隔离的场景(如加密密钥存储)。
- 切片是引用类型,传递时复制的是切片头(浅拷贝),共享底层数组。修改切片元素会直接影响底层数组。
扩容与性能
- 数组长度固定,无法扩容;切片支持自动扩容(通过
append
),当容量不足时会创建新的底层数组,并将旧数据复制过去,可能触发性能开销。 - 切片由于动态特性,在高频修改或变长数据场景更灵活,但部分操作(如随机访问)可能略慢于数组。
- 数组长度固定,无法扩容;切片支持自动扩容(通过
初始化与使用
- 数组初始化需指定长度和元素值(如
arr := [3]int{1,2,3}
);切片可通过字面量(slice := []int{1,2,3}
)或从数组/现有切片创建(如slice := arr[1:4]
)。
- 数组初始化需指定长度和元素值(如
Go的GMP模型
回答:
GMP是Go语言并发调度的核心模型,由三个核心组件构成:
G(Goroutine)
- 轻量级协程,初始栈仅2KB(可动态扩展),由
go
关键字创建。每个G包含执行上下文(程序计数器、栈、寄存器状态)。
- 轻量级协程,初始栈仅2KB(可动态扩展),由
M(Machine)
- 对应操作系统线程,负责执行CPU指令。M必须绑定一个P才能运行G,默认上限为10,000个(可通过
debug.SetMaxThreads
调整)。
- 对应操作系统线程,负责执行CPU指令。M必须绑定一个P才能运行G,默认上限为10,000个(可通过
P(Processor)
- 虚拟处理器,管理本地G队列(Local Queue)和调度上下文。数量由
GOMAXPROCS
控制(通常等于CPU核心数)。
- 虚拟处理器,管理本地G队列(Local Queue)和调度上下文。数量由
调度策略与优化:
- Work-Stealing(工作窃取):当P的本地队列为空时,会从全局队列或其他P窃取G,避免资源闲置。
- Hand Off机制:若G阻塞(如系统调用),M会释放P,由其他M接管,减少线程阻塞对整体性能的影响。
- 协作式调度:通过
runtime.Gosched()
或通道操作主动让出CPU,结合基于信号的抢占式调度(Go 1.14+),减少长耗时Goroutine的“饥饿”问题。
优势:GMP通过细粒度调度和并发执行,实现高吞吐、低延迟的并发处理,特别适合I/O密集型和高并发场景(如Web服务器)。
defer关键字的作用以及执行顺序
回答:defer
关键字用于延迟执行函数,常用于资源释放(如文件关闭、锁解锁)和错误处理,其核心特性如下:
- 执行顺序
- 多个
defer
按后进先出(LIFO)顺序执行。例如:
- 多个
defer fmt.Println("First")
defer fmt.Println("Second")
// 输出顺序为 Second → First
这种设计确保依赖资源按逆序释放(如先打开的文件后关闭)。
- 参数预计算
defer
的函数参数在声明时立即求值,而非执行时。例如:
x := 10
defer fmt.Println(x) // 输出10
x = 20
此时x
的值在defer
声明时已确定为10。
- 常见用途
= 资源管理:确保文件、网络连接等资源在函数退出时释放:
file, _ := os.Open("file.txt")
defer file.Close()
错误恢复:结合recover
在defer
中捕获panic:
defer func() {
if r := recover(); r != nil {
log.Println("Recovered:", r)
}
}()
日志跟踪:记录函数进入和退出时间,用于性能分析。
- 性能注意事项
defer
会引入微小性能开销(纳秒级),但在大多数场景可忽略。避免在循环中滥用defer
(可改用显式函数调用)。
有接触过什么Golang中的设计模式吗
回答:
- 单例模式(Singleton)
通过sync.Once
确保全局唯一实例:
var instance *Singleton
var once sync.Once
func GetInstance() *Singleton {
once.Do(func() { instance = &Singleton{} })
return instance
}
适用于配置管理、日志器等场景。
- 工厂模式(Factory)
根据输入类型返回不同对象:
type Vehicle interface { Drive() }
func CreateVehicle(t string) Vehicle {
switch t {
case "car": return &Car{}
case "bus": return &Bus{}
}
}
隐藏对象创建细节,便于扩展新类型。
- 策略模式(Strategy)
定义算法族并使其可互换:
type PaymentStrategy interface { Pay(amount float64) }
type WeChatPay struct{}
type AliPay struct{}
// 上下文根据策略执行支付
适用于支付方式、排序算法等灵活切换的场景。
- 观察者模式(Observer)
通过事件通知实现松耦合:
type Subject struct { observers []Observer }
func (s *Subject) Notify(msg string) {
for _, o := range s.observers { o.Update(msg) }
}
用于消息推送、状态变更通知。
- 装饰器模式(Decorator)
动态扩展对象功能:
type Component interface { Operation() }
type Decorator struct { component Component }
func (d *Decorator) Operation() {
d.component.Operation()
d.AddBehavior()
}
适用于中间件、日志增强等场景。
Go的垃圾回收机制
回答:
Go的垃圾回收(GC)采用并发三色标记清除算法,主要流程如下:
标记阶段:
- 初始标记(STW):短暂暂停程序,扫描GC Roots(全局变量、Goroutine栈等)。
- 并发标记:与程序并发执行,遍历对象图,标记存活对象为黑色,引用未扫描完的为灰色,未访问的为白色。
清除阶段:
- 回收白色对象内存,未标记对象被加入空闲链表供后续分配。
优化技术:
- 混合写屏障:结合插入屏障(标记新引用对象)和删除屏障(保留旧引用对象),允许并发标记期间减少STW时间。
- 分代假设优化:虽然没有显式分代,但通过局部性原理优化扫描顺序,优先处理年轻对象。
触发条件:
- 堆内存增长达到阈值(默认100%,由
GOGC
环境变量控制)。 - 手动调用
runtime.GC()
或内存不足时强制触发。
性能特点:
- 低延迟:Go 1.14后STW时间通常控制在1ms以内,适合实时系统。
- 吞吐量:相比Java,Go的GC更轻量,但堆内存可能更大(无分代)。
Go垃圾回收跟其他语言的有什么区别,STW跟其他相比做了哪些优化,减少暂停时间
回答:
对比其他语言GC机制:
特性 | Go | Java | Python |
---|---|---|---|
算法 | 并发三色标记清除 | 分代(年轻代+老年代) | 引用计数+分代标记清除 |
STW时间 | <1ms(混合写屏障优化) | 可达几十ms(CMS/G1) | 依赖全局解释器锁(GIL) |
内存模型 | 无分代,整体扫描 | 分代优化年轻代回收频率 | 分代但受GIL限制 |
适用场景 | 高并发、低延迟 | 大型企业应用 | 短期任务、脚本 |
Go的STW优化:
- 并发标记:标记阶段与用户代码并发执行,仅初始标记和重标记需要极短STW。
- 混合写屏障:允许在并发标记期间修改对象引用,无需完全暂停程序。
- 增量回收:将GC工作分摊到多次小规模操作,避免单次长时间停顿。
垃圾回收机制主要针对的是栈区的还是堆区的
回答:
垃圾回收主要针对堆内存:
• 堆区:动态分配的对象(如new
、make
创建)由GC管理,通过标记清除回收不可达对象。
• 栈区:栈内存由编译器自动管理(局部变量),函数返回时立即释放,不依赖GC。
例外情况:若栈上的指针引用了堆对象(如闭包),GC会通过可达性分析确保堆对象不被错误回收。
使用内存池的优点
回答:
内存池(Memory Pool)通过预分配和复用内存块优化性能,核心优点包括:
- 减少内存碎片:固定大小块分配避免频繁动态分配导致的内存碎片。
- 提升分配速度:直接从池中获取内存块,避免系统调用(如
malloc
)的开销。 - 降低GC压力:复用对象减少垃圾产生,尤其在高频创建/销毁场景(如网络请求)。
- 实时性保障:适用于嵌入式或实时系统,确保内存分配时间可控。
适用场景:Web服务器连接池、游戏对象池、数据库连接管理等。
程序打开之后加载到内存,他有多少个分区
回答:
程序内存通常分为以下区域:
- 代码区(Text):存储可执行指令,只读。
- 全局/静态区(BSS+Data):
- BSS段:未初始化的全局/静态变量(默认为零值)。
- Data段:已初始化的全局/静态变量。
- 堆区(Heap):动态分配内存(如
malloc
、new
),由GC或手动管理。 - 栈区(Stack):存储局部变量、函数参数,自动分配释放。
- 常量区:存储字符串常量等只读数据(可能合并到Text或Data段)。
TCP跟UDP的区别
回答:
特性 | TCP | UDP |
---|---|---|
连接性 | 面向连接(三次握手) | 无连接 |
可靠性 | 可靠传输(重传、拥塞控制) | 尽力交付,可能丢包 |
数据顺序 | 保证数据顺序 | 不保证顺序 |
头部开销 | 大(20字节) | 小(8字节) |
适用场景 | 文件传输、Web请求 | 视频流、实时游戏 |
用TCP设计文件上传功能,能够知道实时进度,你会怎么设计?
回答:
设计方案:
分片传输:
- 将文件拆分为固定大小块(如1MB),每块编号后通过TCP发送。
- 接收方按编号重组文件,确保顺序。
进度反馈:
- 接收方每收到一个分片,返回ACK包含已接收的分片编号和总大小。
- 发送方根据ACK计算进度(已发送分片数/总分片数)。
断点续传:
- 记录已传输的分片到数据库或文件,中断后从断点继续发送。
流量控制:
- 滑动窗口机制动态调整发送速率,避免网络拥塞。
优化点:
- 多线程分片上传提升速度。
- 压缩分片减少传输量。
- 客户端本地计算分片哈希,服务端校验完整性。
如何设计缓存,解决缓存和数据库一致性问题
回答:
核心策略:
缓存更新策略:
• 写穿透(Write Through):先更新数据库,再更新缓存。
• 延迟双删:更新数据库后,先删缓存,延迟再删一次(防旧数据回填)。失效机制:
• 设置合理TTL,结合惰性删除(访问时检查过期)。
• 主动刷新热点数据,避免批量失效(缓存雪崩)。并发控制:
• 使用分布式锁(如Redis锁),防止缓存击穿(大量请求同时查DB)。最终一致性:
• 通过消息队列异步更新缓存(如数据库变更后发送MQ)。
容错设计:
- 降级策略:缓存失效时直接返回默认值或限流。
- 监控告警:缓存命中率、延迟异常时触发告警。
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。
有疑问加站长微信联系(非本文作者))

你觉得这个难度怎么样?