#
#
* 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. 协程是用户级线程,轻量级,通信通过用户代码显式调度实现,适用于大量任务的处理。
***
#
#
* 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的键
#
* 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 等
#
* 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. 注意为了避免日志的消费远远慢于生产,可以异步产生和消费队列
#
* 互斥锁的底层实现
* 读和写锁之间的关系?比如读锁会堵塞写锁?
> 1. 只有读锁与读锁之间不互斥,其它锁都是互斥的
> 2. 读锁会堵塞写锁,保证数据的一致性
* 互斥锁有几种模式,可以分别讲讲为什么需要它们?
> 1. 独占锁和共享锁
> 2. 独占锁,例如写锁,确保任一时刻只有一个进程操作共享资源,确保数据的一致性,原子性
> 3. 共享锁,例如读锁,任一时刻允许多个线程读取,提供并发操作
* 原子操作和互斥锁有什么区别?分别用在什么场景下?
> 1. 原子操作是不可中断的单一操作,要么完全执行,要么完全不执行,不会被其他线程或进程中断
> 2. 互斥锁是一种高级的同步机制,用于确保在同一时刻只有一个线程或进程能够访问共享资源
> 3. 简单的操作用原子操作,复杂的同步场景用互斥锁
#
* 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 发生逃逸,因为它被闭包捕获并在函数外被引用
}
}
```