头图

很多朋友在找工作的时候都会遇到各种外包岗,今天分享的面经就是出自组织内部的朋友在百度外包的面试;

看到这个岗位我就有点好奇,如果同时拿到了大厂外包的offer和小公司的offer,你们会怎么选?欢迎大家留言讨论。

面经详解

1. 自我介绍

一定要提前准备好,时间控制在2~3分钟,不要说太多也不要说太少。

2. make的作用

make 是 Go 语言中用于初始化切片(slice)、映射(map)和通道(channel)的内置函数。具体作用如下:

  • 切片:分配底层数组并初始化切片结构(长度、容量、指针)。
  • 映射:分配哈希表并初始化。
  • 通道:创建用于协程间通信的通道。
    make 的参数根据类型不同而变化:
  • 切片:make([]T, length, capacity)(容量可选)。
  • 映射:make(map[K]V, initialCapacity)(初始容量可选)。
  • 通道:make(chan T, bufferSize)(缓冲区大小可选)。

3. make的容量

容量的含义根据类型不同而不同:

  • 切片:容量表示底层数组的总大小,必须 ≥ 长度。例如,make([]int, 5, 10) 创建一个长度为 5、容量为 10 的切片。
  • 通道:容量表示缓冲区大小,即通道在阻塞前可存储的元素数。例如,make(chan int, 5) 创建一个缓冲区为 5 的通道。
  • 映射的容量(make(map[K]V, capacity))表示初始哈希表的空间预分配,但映射的实际容量会动态调整。

4. channel有几种类型

Channel 的类型分为以下三种:

  1. 双向通道:默认类型,可发送和接收数据,例如 chan int
  2. 只读通道<-chan T):仅能接收数据。
  3. 只写通道chan<- T):仅能发送数据。
    通过类型转换可实现只读或只写的限制,例如:

    ch := make(chan int)
    var readOnly <-chan int = ch   // 只读
    var writeOnly chan<- int = ch  // 只写

5. 实际应用最常用哪种

实际应用中,带缓冲区的双向通道最常见。例如:

  • 任务队列:通过缓冲通道实现异步处理,提升吞吐量(如 make(chan Task, 100))。
  • 流量控制:结合 select 和缓冲区限制并发数。
    无缓冲通道多用于同步场景(如信号通知),但带缓冲的通道更灵活且性能更优。

6. 并发编程常用指令

  1. 协程创建go func() { ... }()
  2. 通道操作

    • 发送数据:ch <- data
    • 接收数据:data := <-ch
  3. 同步工具

    • sync.WaitGroup:等待多个协程完成(Add(), Done(), Wait())。
    • sync.Mutex/sync.RWMutex:保护共享资源。
    • sync.Map:线程安全的映射。
  4. 通道选择select 处理多通道操作。
  5. 原子操作sync/atomic 包提供原子读写(如 atomic.AddInt32())。

7. 代码题:并发请求下游并控制 QPS

需求:读取文件中的请求信息,并发请求下游服务,控制每秒请求数(QPS)。
实现示例

func main() {
    limiter := rate.NewLimiter(rate.Every(time.Second), 10) // QPS=10

    file, _ := os.Open("requests.txt")
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        req := scanner.Text()
        go func(r string) {
            ctx := context.Background()
            limiter.Wait(ctx) // 等待令牌
            callDownstream(r)
        }(req)
    }
}

func callDownstream(request string) {
    // 发送请求逻辑
}

关键点

  • 使用 rate.Limiter 控制 QPS。
  • 协程池或带缓冲通道可进一步优化资源管理。

8. 数据库常用用法

8.1 表设计

  1. 业务拆分:通过逻辑外键关联(如用户表与订单表)。
  2. 字段设计

    • NOT NULL 约束避免空值。
    • 合理选择类型(如 INT vs VARCHAR)。
  3. 索引优化:高频查询字段(如用户 ID)添加索引。
  4. 分库分表:数据量大时按哈希或范围分片。

8.2 并发控制

  1. 读写分离:主库写,从库读。
  2. 缓存热点数据:Redis 缓存高频查询结果。
  3. 流量削峰:Kafka 异步处理请求。

9. 慢查询优化

9.1 SQL 优化

  1. 精简查询字段:避免 SELECT *
  2. 优化 JOIN:避免多层子查询。
  3. 分页优化:用 WHERE id > ? 替代 OFFSET

9.2 索引优化

  1. 添加索引:覆盖查询字段。
  2. 避免失效场景

    • 函数计算(如 WHERE YEAR(create_time) = 2023)。
    • 模糊查询(如 LIKE '%keyword%')。
    • 隐式类型转换(如字符串字段用数字查询)。

9.3 架构优化

分库分表、读写分离分散压力。


10. Redis 数据类型

  1. String:字符串、整数。
  2. List:有序列表,支持双向操作。
  3. Hash:键值对集合,适合存储对象。
  4. Set:无序唯一集合。
  5. ZSet:有序集合,按分数排序。

11. Hash 的典型使用场景

  • 存储对象:如用户信息(字段为 name, age)。
  • 配置管理:多个键值对的高效存取。
  • 缓存组合数据:避免序列化整个对象。

12. 代码题:有序数组查找(二分法)

func binarySearch(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right-left)/2
        if nums[mid] == target {
            return mid
        } else if nums[mid] < target {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return -1
}

要点

  • 循环条件 left <= right
  • 中间值计算防溢出(mid := left + (right-left)/2)。

早日上岸!

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:sf面试群。


王中阳讲编程
836 声望326 粉丝