Golang面试合集(三)

Golang面试合集(三)
最新回答
长野初见

2022-12-01 22:56:12

Golang相关
  • 接口原理、用法及作用

    原理:Go接口是隐式实现的,通过定义方法集合(方法签名)描述行为,类型通过实现接口所有方法自动满足接口。接口变量底层是iface结构体,包含指向类型信息的指针和指向数据指针的指针。

    用法:定义接口类型(如type Reader interface { Read([]byte) (int, error)}),类型实现接口方法后可直接赋值给接口变量。

    作用:解耦代码、实现多态、支持依赖注入,例如标准库io.Reader/Writer抽象I/O操作。

  • 多线程、进程、协程

    进程:资源分配单位,拥有独立内存空间,进程间通信(IPC)需通过管道、共享内存等。

    线程:CPU调度单位,共享进程内存,但切换开销大(涉及寄存器、栈保存)。

    协程(Goroutine):用户态轻量级线程,由Go运行时调度,栈初始小(2KB),动态扩容,切换成本低(仅保存PC/SP)。

  • 数据库索引:哈希索引 vs B+树索引

    哈希索引

    适合等值查询(如=、IN),不支持范围查询。

    底层为哈希表,冲突时用链表解决,O(1)时间复杂度。

    示例:MySQL的MEMORY引擎支持哈希索引。

    B+树索引

    适合范围查询(如>、BETWEEN),数据有序存储。

    非叶子节点存键值指针,叶子节点存数据或主键,通过指针连接形成有序链表。

    示例:InnoDB默认使用B+树索引。

    适合加索引的字段:高频查询条件、排序字段、连接字段;低选择性字段(如性别)不适合。

  • new vs make

    new:分配内存并零值初始化,返回指针(如p := new(int),*p为0)。

    make:仅用于内置类型(slice、map、channel),初始化底层数据结构并返回引用(如s := make([]int, 5))。

  • 值传递 vs 引用传递

    值传递:基本类型(int、float等)、数组(非切片)、结构体(非指针)传递副本,修改不影响原值。

    引用传递:指针、切片、map、channel、函数、接口传递引用,修改会影响原值。

    场景:大结构体用指针避免拷贝开销;需修改原值时用指针。

  • 堆与栈分配

    栈分配:局部变量、函数参数等生命周期明确的变量,由编译器自动分配/释放,速度快。

    堆分配:逃逸到函数外的变量(如返回指针、闭包引用)、大变量(超过栈大小限制),由GC回收。

    逃逸分析:编译器通过分析变量作用域决定分配位置,可通过go build -gcflags="-m"查看。

  • Channel读写流程

    发送:若接收方存在且队列未满,直接拷贝数据到接收方栈;否则阻塞或触发调度。

    接收:若发送方存在且队列有数据,直接拷贝数据;否则阻塞或触发调度。

    底层结构:hchan结构体包含缓冲区、发送/接收队列、互斥锁等。

网络相关
  • 浏览器寻址URL过程

    解析URL,提取协议、域名、路径等。

    检查本地DNS缓存,未命中则向DNS服务器发起递归查询。

    建立TCP连接(三次握手),若使用HTTPS则进行TLS握手。

    发送HTTP请求(GET/POST等),服务器返回响应。

    浏览器渲染页面(解析HTML/CSS/JS)。

  • ARP表作用与分组格式

    作用:维护IP到MAC地址的映射,用于局域网内数据帧转发。

    分组格式:包含硬件类型(以太网为1)、协议类型(IPv4为0x0800)、硬件地址长度(6字节)、协议地址长度(4字节)、操作码(1请求/2响应)、发送方/目标方MAC/IP地址。

  • ARP请求处理

    若目标IP不存在,主机丢弃请求且不响应;发送方超时后重试或报错。

  • DNS作用与解析流程

    作用:将域名解析为IP地址。

    流程

    检查本地hosts文件和DNS缓存。

    向配置的DNS服务器(如8.8.8.8)发起查询。

    递归查询:DNS服务器逐级向根域名服务器、顶级域名服务器、权威域名服务器请求,最终返回结果。

  • 下一跳路由转发

    路由器根据目标IP查找路由表,匹配最长前缀的条目确定下一跳IP和出接口。

    若无匹配条目,丢弃数据包或发送到默认网关。

Go GMP模型
  • GMP模型

    G(Goroutine):协程,用户态线程,包含栈、程序计数器等。

    M(Machine):操作系统线程,绑定P执行G。

    P(Processor):逻辑处理器,管理G队列,调度G到M执行。

  • 线程与协程区别

    线程由OS调度,协程由用户态调度器(如Go的GMP)调度。

    线程切换开销大(需保存寄存器、内核态切换),协程切换轻量(仅保存PC/SP)。

  • 协程调度过程

    P从本地队列或全局队列获取G。

    将G绑定到M执行,若M阻塞(如系统调用),P解绑M并从线程池获取新M。

    G执行完毕或主动让出CPU,P回收G并调度下一个。

  • P/M数量问题

    P数量默认等于CPU核心数(可通过GOMAXPROCS调整),M数量动态伸缩(上限约10000)。

  • 协程切换时机

    G主动调用runtime.Gosched()让出CPU。

    G执行时间片耗尽(GO 1.14后引入协作式调度)。

    G进行系统调用或通道操作阻塞时。

Gin框架
  • 为什么使用Gin

    高性能(基于httprouter,路径匹配快)。

    中间件支持(如日志、认证)。

    简洁的API设计(链式调用)。

  • 路由树实现

    使用压缩前缀树(Radix Tree)存储路由路径,支持静态路由、参数路由(:name)、通配路由(*filepath)。

    匹配时从根节点开始,按路径分段查找,优先匹配最长前缀。

算法与编程
  • 跳表实现:type SkipListNode struct { val int next []*SkipListNode}type SkipList struct { head *SkipListNode maxLevel int p float64 // 晋升概率}func NewSkipList(maxLevel int, p float64) *SkipList { return &SkipList{ head: &SkipListNode{next: make([]*SkipListNode, maxLevel)}, maxLevel: maxLevel, p: p, }}func (sl *SkipList) Insert(val int) { update := make([]*SkipListNode, sl.maxLevel) curr := sl.head for i := sl.maxLevel - 1; i >= 0; i-- { for curr.next[i] != nil && curr.next[i].val < val { curr = curr.next[i] } update[i] = curr } level := randomLevel(sl.p, sl.maxLevel) newNode := &SkipListNode{ val: val, next: make([]*SkipListNode, level), } for i := 0; i < level; i++ { newNode.next[i] = update[i].next[i] update[i].next[i] = newNode }}func randomLevel(p float64, maxLevel int) int { level := 1 for rand.Float64() < p && level < maxLevel { level++ } return level}
  • Unit Test示例:func TestSkipList_Insert(t *testing.T) { tests := []struct { name string sl *SkipList val int want bool }{ {"Insert to empty list", NewSkipList(4, 0.5), 1, true}, {"Insert duplicate", NewSkipList(4, 0.5), 1, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.sl.Insert(tt.val); got != tt.want { t.Errorf("Insert() = %v, want %v", got, tt.want) } }) }}
  • 洗牌算法(Fisher-Yates):func Shuffle(arr []int) { rand.Seed(time.Now().UnixNano()) for i := len(arr) - 1; i > 0; i-- { j := rand.Intn(i + 1) arr[i], arr[j] = arr[j], arr[i] }}
技术栈
  • RPC vs HTTP

    RPC:面向服务,支持多种协议(如gRPC的HTTP/2),性能高(二进制编码),适合内部服务调用。

    HTTP:通用性强,支持RESTful API,适合跨语言/跨平台调用。

  • PB协议快的原因

    二进制编码(JSON为文本编码,体积大)。

    预定义Schema,无需解析键名。

    支持高效序列化(如proto.Marshal)。

  • HTTP/2 vs HTTP/1.1

    HTTP/2:多路复用(单连接并行请求)、头部压缩、服务器推送。

  • HTTPS vs HTTP

    HTTPS:HTTP + TLS/SSL,加密传输,防篡改/窃听。

  • gRPC底层协议:基于HTTP/2,使用PB编码。

  • MySQL存储引擎

    InnoDB:支持事务、行级锁、外键,默认引擎。

    MyISAM:不支持事务,表级锁,适合读多写少场景。

    Memory:数据存内存,速度快,重启丢失。

  • 事务特性与隔离级别

    ACID:原子性、一致性、隔离性、持久性。

    隔离级别

    读未提交(可能脏读)。

    读已提交(解决脏读,可能不可重复读)。

    可重复读(MySQL默认,解决不可重复读,可能幻读)。

    串行化(最高隔离,性能最低)。

  • Redis快的原因

    内存存储,单线程处理命令(避免锁竞争)。

    IO多路复用(epoll/kqueue)。

    数据结构高效(如跳表、压缩列表)。

  • Redis常用数据结构

    ZSET:跳表+哈希表实现,支持范围查询和排序。

  • Redis网络模型:单线程Reactor模式,基于ae事件库处理连接和命令。

项目相关
  • 分布式WebSocket服务器

    使用Redis Pub/Sub或RabbitMQ实现消息广播。

    负载均衡(Nginx)分配连接至不同节点。

  • RabbitMQ vs Kafka

    RabbitMQ:轻量级,支持多种协议(AMQP),适合低延迟场景。

    Kafka:高吞吐,磁盘持久化,适合日志处理,但内存占用高(因缓存和索引)。

  • Linux常用命令

    CPU:top、htop、mpstat -P ALL 1。

    内存:free -h、vmstat 1。

  • 中间件机制

    基于net/http的Handler链,通过context.Context传递数据。

    内存占用取决于中间件数量和请求上下文大小。

  • ORM组件设计

    支持链式查询(如Where().Order().Limit())。

    事务管理(Begin()/Commit()/Rollback())。

  • 缓存组件设计

    多级缓存(本地缓存+Redis)。

    缓存击穿/雪崩防护(互斥锁、随机过期时间)。

  • 热编译实现

    监听文件变化(fsnotify),重新编译并重启服务(如air工具)。

  • 数据库设计

    主键选择(自增ID/UUID)、外键约束、索引优化。

  • 分布式协议

    Raft:强一致性,用于分布式共识(如etcd)。

    Gossip:最终一致性,用于服务发现(如Consul)。