golang 面试题

12

1、内存逃逸的现象是什么?

1.1 什么是内存逃逸?

逃逸是指在函数内部创建的对象或者变量,在函数结束后仍然被其他部分引用或者持有;

原本可以分配在“栈”上的变量,被 Go 编译器判断为“可能在函数外被使用”,因此被强制分配到了“堆”上,这个现象就叫内存逃逸。

1.2 堆栈的区别

栈:内存分配和释放由编译器自动管理,速度快但空间有限;

堆:内存分配由运行时系统的参与,相对较慢但是空间较大,由GC 回收

1.3 判断的方式

内存逃逸最直观的现象就是:GC 变频繁、内存回收变慢、延迟开始抖动,而在代码上却看不到明显的 new 或大对象分配。

2、make 和 new 的区别

new 负责“分配内存并返回指针”,不初始化内部结构;
make 负责“初始化引用类型并返回可用对象”,不返回指针。

new:只做 零值初始化

分配一块内存 → 赋零值 → 返回指针
不会初始化任何内部数据结构

make:会 初始化底层数据结构

3、请介绍一下 map 的数据结构

Go 的 map 是一个基于“哈希表 + 桶(bucket)+ 渐进式扩容”的高性能字典结构。

它不是红黑树、也不是链表数组,而是 Go runtime 自己实现的一套 cache 友好型哈希表结构

4、map 是如何解决 hash 冲突的

Go 的 map 通过“定长 bucket(8 个槽位)+ overflow 链表”来解决 hash 冲突,写入时优先放入主桶,满了再挂溢出桶;查找时通过 hash 的高八位(tophash)做快速过滤;当冲突或装载因子过高时,触发渐进式扩容重新分散元素,从根本上降低冲突概率。

5、map 什么情况下会创建overflow

当且仅当:

  1. 通过 hash 计算,新 key 被映射到某个 bucket

  2. 该 bucket 的 8 个槽位已全部被占用

  3. 且该 key 不属于这 8 个已有 key(不是覆盖写)

runtime 就会创建一个 overflow bucket

6、为什么 map 的遍历是无序的

Go map 的遍历是无序的,原因在于它底层是基于哈希桶和溢出链表的存储结构,元素顺序只由 hash 分布和扩容过程决定;同时每个 map 在创建时都会引入随机 hash 种子以防止冲突攻击,加之渐进式扩容会动态重排元素,因此 Go 从语言规范层面明确保证 map 的遍历顺序是不确定、不可依赖的。

7、那如果一个 map 被初始化并且赋值之后,key 的元素已经固定不会发生变化了,这个时候多次去 range 遍历,每次遍历得到的 key 顺序是否是一致的

即使一个 Go map 在初始化并完成赋值后不再发生任何修改,多次使用 range 遍历时得到的 key 顺序在语言规范层面仍然是不保证一致的。

这是因为 map 底层是基于哈希桶的无序结构,每次遍历的起始位置可能被 runtime 随机化,同时每个 map 还引入了随机 hash 种子用于防止冲突攻击;因此遍历顺序只能视为不确定结果,任何依赖其稳定性的逻辑都是潜在 Bug。