golang面试题汇总

192 阅读10分钟
 ### golang面试题汇总

#### 一、基础题

* make和new的区别?
> 1. new()内置函数用于分配内存,第一个参数接收一个类型而不是一个值,函数返回一个指向该类型内存地址的指针,同时把分配的内存置为该类型的零值  
> 2. new的语法是:func new(Type) *Type;  
> 3. make的语法为:func make(t Type, size …IntegerType) Type  

> 两者异同:  
> 1. new 和 make 都是用于内存的分配。  
> 2. make 只用于 chan,map,slice 的初始化。  
> 3. new 用于给类型分配内存空间,并且置零。  
> 4. make 返回类型本身,new 返回指向类型的指针。

* 数组和切片的区别?  
> 1. 固定大小和动态大小,数组固定,切片动态调整
> 2. 值传递和引用传递,数组是值传递,切片是引用传递
> 3. 创建方式:数组使用`[...]`或者指定大小的语法进行创建,切片使用make函数或者切片字面量进行创建
> 4. 切片的底层是一个数据结构,包含一个len,cap和指向第一个元素的地址指针

* golang的参数传递是值传递还是引用传递?
> 值传递,传递到函数内部的值的修改,不影响外部变量的值

* for range循环的变量地址会发生改变么?
> 不会,每次迭代会创建新的变量

* defer的执行顺序?defer什么时候会修改返回值?
> 1. 函数运行完后,defer才执行  
> 2. 具名返回值,可以在 defer 中直接修改返回值

* slice的底层结构?len和cap分别代表什么含义?函数传参在函数里对该slice添加或者扩容会发生什么?
> 1. 数组结构:数组指针,底层数组的len,cap ;其中len是切片中数组的实际长度,cap是底层数组的容量,即底层数组的长度  
> 2. 当添加切片的元素,切片的长度比cap大时,切片会动态扩容cap的大小  
> 3. slice使用的是引用传递,不会函数外面slice的结果  

* 单引号、双引号、反引号的区别?
> 1. 单引号是字符常量 'a'  
> 2. 双引号是字符串常量 "hello"  
> 3. 反引号是代表原始字符串的常量 ,不解析换行,空格等符号``你好 \n``

* 怎么样优化golang程序性能?
> 1. 使用正确的数据结构和算法  
> 2. 避免不必要的内存分配  
> 3. 利用go的特性使用并发,提供程序的吞吐量和响应性

* go与其他语言的一个比较?
> 1. Go: 适用于构建高性能的后端服务、网络应用、分布式系统等。 
> 2. PHP: 主要用于Web开发,特别是服务器端的Web应用程序。
 
* 进程、线程、协程有什么区别?
> 1. 进程是独立的执行单位,拥有独立的内存空间,通信通过 IPC 实现。  
> 2. 线程是进程内的执行单元,共享进程的资源,通信通过共享内存空间实现。  
> 3. 协程是用户级线程,轻量级,通信通过用户代码显式调度实现,适用于大量任务的处理。

***
#### 二、专栏篇

##### 2.1 map

* map是否有有序的?怎样实现有序性?
> 1. map是无序的,每次迭代打印出来的数据顺序都不一定一样  
> 2.   使用slice和struct的组合,或者使用第三方提供的有序映射来实现

* map是并发安全的?怎么样实现对map并发安全访问,有什么方案?
> 1. map不是并发安全的,多个协程同时读写一个map,可能会发生竞态  
> 2. 方案:  
> 1. 使用互斥锁:sync.Mutex  
> 2. 使用读写锁:sync.RWMutex  
> 2. 1.9引入了sync.map,并发安全

* map删除key之后内存会释放么?
> 1. 不会,不确定什么时候触发垃圾回收机制  
> 2. Go 的垃圾回收机制是自动管理内存的,它负责在运行时识别和回收不再使用的内存   
> 3. php是引用计数为0时就会被回收
* map的底层实现,扩容机制?
> 1. map 的底层实现是哈希表(hash table)。哈希表是一种数据结构,用于实现关联数组或字典,它通过哈希函数将键映射到存储桶(bucket),然后在每个桶中存储键值对。  
> 2. 底层实现:go的哈希表由一个数组和一组链表组成,数组的每个元素是一个桶,每个桶存储一个链表,链表中存储了具有相同哈希值的键值对  
> 3. 哈希函数:哈希函数将键值转换为哈希值,该哈希值用来确定键值放在哪个桶   
> 4. 碰撞处理:相同哈希值的桶中使用链表来存储键值对    
> 5. 扩容机制:   
当 map 中的键值对数量达到一定阈值时,Go 会触发扩容操作。
扩容的过程包括创建一个新的桶数组,将旧桶数组中的键值对重新分布到新的桶数组中。
扩容时,Go 会选择新的桶数组的大小,通常是当前大小的两倍。这个选择保证了哈希表的负载因子(键值对数量与桶数量的比值)较小,提高了性能。
* 哪些数据结构不可以作为map的key?
> 1. 具有可比较性:键的类型必须支持相等性比较  
> 2. 可哈希性:键的类型必须支持哈希计算  
> 3. 通常使用整数、字符串、浮点数、指针等基本类型作为 map 的键  
> 4. 切片和函数不支持比较,不能用作map的键

##### 2.2 context

* context的用途以及使用场景
>  用于在函数调用链中传递截止日期、取消信号、请求范围的值等信息:  
  > 1. 通过 context 提供的 WithCancel 函数创建带有取消信号的上下文,用于优雅地取消一组相关的操作  
  > 2. 通过 context 提供的 WithDeadline 函数设置截止日期,确保操作在指定的时间内完成。
  > 3. 通过 context 提供的 WithValue 函数在处理器链中传递请求范围的值,例如用户身份验证信息、语言偏好等  
  > 4. 通过 context 提供的 WithTimeout 函数设置超时,确保程序不会无限等待。  
  > 5. 通过 context 提供的 WithCancel、WithDeadline、WithTimeout 函数,可以将多个上下文合并,形成新的上下文
* context和gin框架的context有什么区别?
> 1. context.Context: 适用于任何需要在函数调用链中传递上下文信息的场景,不局限于 Web 开发  
> 2. gin.Context: 适用于使用 gin 框架进行 Web 开发的场景,提供了方便的方法处理 HTTP 请求和响应  
> 3. context.Context: 提供基础的上下文传递功能,如 context.WithCancel、context.WithTimeout、context.Value 等  
> 4. gin.Context: 提供了与 Web 请求和响应相关的一系列方法,例如 ShouldBind、Param、JSON、HTML 等,以及用于设置响应的方法如 JSON、HTML、String 等


##### 2.3channel

* channel的底层实现?
> 1. hchan的结构体,包含发送队列,接收队列,缓存区,锁,关闭状态
> 2. channel 包含两个队列,一个发送队列一个接受队列
> 3. 包含一个互斥锁,用于保护对队列的访问,保证对channel读写操作的原子性    
> 4. channel 的关闭是通过原子操作来实现的。关闭一个 channel 会将一个关闭标志设置为 true,这样任何进一步的发送操作都会立即返回  
> 5. channel避免不必要的内存拷贝,在发送数据时传递指向数据的指针(缓存区存的就是指针)  

* channel的一些特殊场景,往一个chan传nil、关闭的chan传数据、从关闭的chan里取数据?
> 1. 在 Go 中,nil 可以被发送到一个 channel,但接收方在接收时会得到 nil 值。这是因为 nil 在 Go 中也是一个合法的值,可以被传递  
> 2. 如果尝试向一个已经关闭的 channel 中发送数据,会导致 panic。关闭的 channel 不再允许发送数据  
> 3. 从已经关闭的 channel 中接收数据是安全的,并且会在没有数据可接收时立即返回。如果 channel 是空的,并且已经关闭,接收操作会得到零值  

* channel是线程安全的么?
> 1. 是的,通道中有并发锁控制,多个协程可同时操作同一个通道

* 怎么通过channel实现一个日志收集系统?
> 1. 通过缓冲channel来接收日志,迭代channel来消费日志  
> 2. 注意为了避免日志的消费远远慢于生产,可以异步产生和消费队列

##### 2.4 锁相关

* 互斥锁的底层实现

* 读和写锁之间的关系?比如读锁会堵塞写锁?
> 1. 只有读锁与读锁之间不互斥,其它锁都是互斥的
> 2. 读锁会堵塞写锁,保证数据的一致性

* 互斥锁有几种模式,可以分别讲讲为什么需要它们?
> 1. 独占锁和共享锁
> 2. 独占锁,例如写锁,确保任一时刻只有一个进程操作共享资源,确保数据的一致性,原子性
> 3. 共享锁,例如读锁,任一时刻允许多个线程读取,提供并发操作

* 原子操作和互斥锁有什么区别?分别用在什么场景下?
> 1. 原子操作是不可中断的单一操作,要么完全执行,要么完全不执行,不会被其他线程或进程中断 
> 2. 互斥锁是一种高级的同步机制,用于确保在同一时刻只有一个线程或进程能够访问共享资源
> 3. 简单的操作用原子操作,复杂的同步场景用互斥锁


##### 2.5 垃圾回收

* GC底层实现机制?
> 1. 三色标记法
> 2. 白色对象:表示这些对象是可回收的。初始时,所有的对象都是白色
> 3. 灰色对象:表示这些对象是可达的,需要进一步遍历它们的引用关系来确定它们是否是活动对象。
> 4. 黑色对象:表示这些对象是活动对象,它们被直接或间接地引用着。

* GC的触发场景?
> 1. 内存分配触发
> 2. 定时触发
> 3. 系统资源触发
> 4. 显式触发(手动调用)runtime.GC()

* golang的1.3、1.5、1.8分别对GC做了哪些优化啊?
> 1. 返回指针: 如果一个函数返回一个局部变量的指针,而且这个指针在函数返回后仍然被引用,那么该局部变量就会发生内存逃逸
```go
  func escapeExample() *int {
      x := 42
      return &x // x 发生逃逸,因为它的地址在函数外被引用
  }
```
> 2. 在堆上分配并返回变量: 如果一个函数在堆上分配了内存,并返回对该内存的引用,这个变量也会发生内存逃逸。
```go
  func escapeExample() *int {
      x := new(int)
      return x // x 发生逃逸,因为它在堆上分配
  }

```
> 3. 传递指针给外部函数: 如果一个函数接受一个指针参数,并且该指针在函数外被引用,那么被传递的指针指向的变量可能发生内存逃逸
```go
  func escapeExample(y *int) {
    x := y
    externalFunction(x) // x 发生逃逸,因为它的引用被传递给外部函数
  }
```

> 4. 在闭包中捕获局部变量: 如果一个闭包捕获了一个局部变量,并且该闭包在函数外被引用,那么被捕获的局部变量可能发生内存逃逸。
```go
  func escapeExample() func() int {
    x := 42
      return func() int {
      return x // x 发生逃逸,因为它被闭包捕获并在函数外被引用
    }
  }

 ```