Go面试题

Go面试题

一、Go语言基础

1. GOROOT,GOPATH,GOBIN?

  1. GOROOT: Go语言安装路径
  2. GOPATH: 我们自己定义的工作空间,多个工作区目录的路径
  3. GOBIN:Go程序生成可执行文件的路径

2. 命令源码文件(main.go), 库源码文件?

  1. 命令源码文件:命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的
  2. 库源码文件:Go标准库和项目依赖包文件

3. 包函数权限?

  1. 相同包:函数可以小写(私有函数)
  2. 不同包:函数必须大写才能调用(公用函数)
  3. internal:包级权限

4. 声明变量有几种方式?

  1. 直接声明(var name string)
  2. 赋值声明(var name = “hello”)
  3. 类型推断声明(name := “hello”)

5. Go 语言的类型推断可以带来哪些好处?

  1. 通过类型推断体验动态类型语言一部分优势,提升程序灵活性
  2. Python是牺牲代码维护性和运行效率换来的
  3. Go 语言是静态类型的,初始化变量就确定类型,避免程序维护问题
  4. 类型确定在编译期完成,不会影响运行效率

6. 怎样判断一个变量的类型?

  1. 使用类型断言表达式(name.(string))
  2. 使用反射(reflect.TypeOf(name))

7. 基本数据结构?

  1. 值类型:数字(int,float),字符串(string, byte, rune), 布尔类型(true, false), array, 结构体(struct)
  2. 引用类型 :slice, map(map[string]bool),interface,channel,函数,指针

8. 数组,切片(array, slice)

  1. 数组长度固定,切片是可变长的
  2. 怎样正确估算切片的长度和容量?
    • var i; i++, cap = 2 * len
    • i := 1024; i++, cap = 1.25 * len
    • var i; 2 * i, cap > 2 * len

9. 字典(map)

  1. Go语言字典的键类型不可以是函数类型、字典类型和切片类型(无法hash类型)
  2. 用var声明的值为nil的map,读,删除都不报错,新增报错,需要初始化

10. 结构体(struct)

  1. 嵌入字段,没有类型(结构体嵌入结构体)
  2. 结构体字段大写才能暴露

11. 通道(channel)(**)

  1. 对通道的发送和接收操作都有哪些基本的特性?
    • 对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的
    • 发送操作和接收操作中对元素值的处理都是不可分割的
    • 发送操作在完全完成之前会被阻塞。接收操作也是如此
  2. 单向通道有什么应用价值?
    • 单向通道最主要的用途就是约束其他代码的行为
  3. channel 的主要组成?
    • 一个环形数组实现的队列,用于存储消息元素
    • 两个链表实现的 goroutine 等待队列,用于存储阻塞在 recv 和 send 操作上的 goroutine
    • 一个互斥锁,用于各个属性变动的同步
  4. channel 的关闭注意事项:
    • 关闭一个未初始化(nil) 的 channel 会产生 panic
    • 重复关闭同一个 channel 会产生 panic
    • 向一个已关闭的 channel 中发送消息会产生 panic
    • 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭
    • 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
  5. 无缓冲channel和有缓冲channel的区别?
    a. 无缓存的 channel
    从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。
    b. 有缓存的 channel
    • 有缓存的 channel 的声明方式为指定 make 函数的第二个参数,该参数为 channel 缓存的容量。
    • 有缓存的 channel 类似一个阻塞队列(采用环形数组实现)。当缓存未满时,向 channel 中发送消息时不会阻塞,当缓存满时,发送操作将被阻塞,直到有其他 goroutine 从中读取消息;相应的,当 channel 中消息不为空时,读取消息不会出现阻塞,当 channel 为空时,读取操作会造成阻塞,直到有 goroutine 向 channel 中写入消息。

12. 函数(func)

  1. 高阶函数
    1. 接受其他的函数作为参数传
    2. 把其他的函数作为结果返回
  2. Go函数参数是值复制,引用类型拷贝本身

13. 接口(interface)

  1. 只声明而不初始化,或者显式地赋给它nil,否则接口变量的值就不会为nil
  2. 变量类型不是nil,值是nil,与nil不等

14. 指针

  1. Go语言中的哪些值是不可寻址的吗?
    • 不可变的
    • 临时结果(例外: 切片字面量的索引结果值是可寻址的)
    • 不安全的

15. go程(goroutine)

  1. 主go程起子goroutine中for打印,无任何结果,当前go程不会打印数据
  2. 应用场景
    • IO耗时操作,读写日志
    • 需要异步执行的任务,函数(发送邮件,短信)

16. 错误处理(error)

  1. 对于具体错误的判断,Go 语言中都有哪些惯用法?
    1. 已知范围错误,类型断言表达式或switch判断
    2. 已有相同错误变量,判等
    3. 未知错误,使用错误信息字符串判断

17. panic, recover, defer函数

  1. panic报错过程?
    • panic函数报错调用,栈结构,先进后出
  2. defer执行顺序?
    • defer函数调用,栈结构,先进后出

18. Go语言测试(go test)

  1. Go 语言对测试函数的名称和签名都有哪些规定
    • 对于功能测试函数来说,其名称必须以Test为前缀,并且参数列表中只应有一个*testing.T类型的参数声明
    • 对于性能测试函数来说,其名称必须以Benchmark为前缀,并且唯一参数的类型必须是*testing.B类型的
    • 对于示例测试函数来说,其名称必须以Example为前缀,但对函数的参数列表没有强制规定

二、Go语言标准包(standard package)

1. archive

2. bufio(2*)$$

  1. bufio.Reader类型值中的缓冲区起着怎样的作用?
    • bufio.Reader类型的值(以下简称Reader值)内的缓冲区,其实就是一个数据存储中介,它介于底层读取器与读取方法及其调用方之间
    • 所谓的底层读取器,就是在初始化此类值的时候传入的io.Reader类型的参数值
    • Reader值的读取方法一般都会先从其所属值的缓冲区中读取数据
    • 同时,在必要的时候,它们还会预先从底层读取器那里读出一部分数据,并暂存于缓冲区之中以备后用

3. builtin

4. bytes(2*)$$

  • Index
    1. type Buffer
  1. bytes.Buffer类型的值记录的已读计数,在其中起到了怎样的作用?
    • 读取内容时,相应方法会依据已读计数找到未读部分,并在读取后更新计数
    • 写入内容时,如需扩容,相应方法会根据已读计数实现扩容策略
    • 截断内容时,相应方法截掉的是已读计数代表索引之后的未读部分
    • 读回退时,相应方法需要用已读计数记录回退点
    • 重置内容时,相应方法会把已读计数置为0
    • 导出内容时,相应方法只会导出已读计数代表的索引之后的未读部分
    • 获取长度时,相应方法会依据已读计数和内容容器的长度,计算未读部分的长度并返回

5. compress

6. container(2*)$$

  • heap(最小堆)
  • list(双向链表)
  • ring(环)
  1. 可以把自己生成的Element类型值传给链表吗?
    • 不会接受,这些方法将不会对链表做出任何改动
    • 因为我们自己生成的Element值并不在链表中,所以也就谈不上“在链表中移动元素”
    • 更何况链表不允许我们把自己生成的Element值插入其中

7. context(***)

  • Index
    1. func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
    2. func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
    3. func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
    4. type CancelFunc
    5. type Context
  1. 怎样使用context包中的程序实体,实现一对多的 goroutine 协作流程?
    • 在这个函数体中,我先后调用了context.Background函数和context.WithCancel函数
    • 并得到了一个可撤销的context.Context类型的值(由变量cxt代表)
    • 以及一个context.CancelFunc类型的撤销函数(由变量cancelFunc代表)
  2. context用途?
    • RPC调用,WithCancel取消通知其他go程,sync.errorGroup
    • PipeLine,使用switch处理了case <-ctx.Done()
    • 超时请求,timerCtx。具体实例化的方法是 WithDeadline 和 WithTimeout。
    • HTTP服务器的request互相传递数据,valueCtx,存储中间件函数,数据

8. crypto

9. database(*)

10. debug

11. embed

12. encoding(*)

13. errors(2*)$$

14. expvar

15. flag(2*)

16. fmt(2*)$$

  • Index
    1. func Errorf(format string, a …interface{}) error
    2. func Fprintf(w io.Writer, format string, a …interface{}) (n int, err error)
    3. func Fscanf(r io.Reader, format string, a …interface{}) (n int, err error)
    4. func Printf(format string, a …interface{}) (n int, err error)
    5. func Sprintf(format string, a …interface{}) string
    6. func Scanf(format string, a …interface{}) (n int, err error) (stdin)

17. go

18. hash

19. html

20. image

21. index

22. io(2*)$$

  • fs
  • ioutil
  1. 在io包中,io.Reader的扩展接口和实现类型都有哪些?它们分别都有什么功用?
    1. 扩展接口
      • io.ReadWriter
      • io.ReadCloser
      • io.ReadWriteCloser
      • io.ReadSeeker
      • io.ReadWriteSeeker
    2. 实现类型
      • *io.LimitedReader
      • *io.SectionReader
      • *io.teeReader
      • io.multiReader
      • io.pipe
      • io.PipeReader

23. log(*)

24. math

25. mime

26. net(2*)

  1. net.Dial函数的第一个参数network有哪些可选值?
    • net.Dial函数会接受两个参数,分别名为network和address,都是string类型的
    • 参数network常用的可选值一共有 9 个。这些值分别代表了程序底层创建的 socket 实例可使用的不同通信协议
    • tcp, tcp4, tcp6, udp, udp4, udp6, unix, unixgram, unixpacket
  2. http.Client类型中的Transport字段代表着什么?
    • 向网络服务发送 HTTP 请求,并从网络服务接收 HTTP 响应的操作过程
    • 也就是说,该字段的方法RoundTrip应该实现单次HTTP 事务(或者说基于 HTTP 协议的单次交互)需要的所有步骤

27. os(2*)$$

  • exec
  • signal
  • user
  1. os.File类型都实现了哪些io包中的接口?
    • os.File类型拥有的都是指针方法,所以除了空接口之外,它本身没有实现任何接口
    • 而它的指针类型则实现了很多io代码包中的接口
    • 对于io包中最核心的 3 个简单接口io.Reader、io.Writer和io.Closer,*os.File类型都实现了它们
    • 该类型还实现了另外的 3 个简单接口,即:io.ReaderAt、io.Seeker和io.WriterAt

28. path

29. plugin

30. reflect(*)

31. regexp

32. runtime(2*)

  1. 怎样让程序对 CPU 概要信息进行采样?
    • 需要用到runtime/pprof包中的 API
    • 在我们想让程序开始对 CPU 概要信息进行采样的时候,需要调用这个代码包中的StartCPUProfile函数
    • 而在停止采样的时候则需要调用该包中的StopCPUProfile函数

33. sort(*)

34. strconv(*)

35. strings(2*) $$

  • Index
    1. type Builder
  1. 与string值相比,strings.Builder类型的值有哪些优势?
    • 已存在的内容不可变,但可以拼接更多的内容
    • 减少了内存分配和内容拷贝的次数
    • 可将内容重置,可重用值

36. sync(2*)$$

  • Index
    1. type Cond(变量)
    2. type Loker (锁,解锁接口)
    3. type Map(并发安全字典)
    4. type Mutex(互斥锁)
    5. type Once(只做一次)
    6. type Pool
    7. type RWMutex(读写互斥锁)
    8. type WaitGroup(等待多个go程完成)
    9. errgroup(同步,错误传递,上下文取消)
    10. semaphore
    11. singleflight
    12. syncmap
  • atomic
  1. 我们使用互斥锁时有哪些注意事项?(sync.Mutex与sync.RWMutex)
    • 不要重复锁定互斥锁
    • 不要忘记解锁互斥锁,必要时使用defer语句
    • 不要对尚未锁定或者已解锁的互斥锁解锁
    • 不要在多个函数之间直接传递互斥锁
  2. 条件变量(sync.Cond)怎样与互斥锁配合使用?
    • 条件变量的初始化离不开互斥锁,并且它的方法有的也是基于互斥锁的
  3. sync/atomic包中提供了几种原子操作?可操作的数据类型又有哪些?
    • 加法(add)、比较并交换(compareand swap,简称 CAS)、加载(load)、存储(store)和交换(swap)
    • int32、int64、uint32、uint64、uintptr,以及unsafe包中的Pointer
    • 针对unsafe.Pointer类型,该包并未提供进行原子加法操作的函数
  4. sync.WaitGroup类型值中计数器的值可以小于0吗?
    • 不可以,这样会引发一个 panic
    • 先统一Add,再并发Done,最后Wait
  5. 为什么说临时对象池(sync.Pool)中的值会被及时地清理掉?
    Go 语言运行时系统中的垃圾回收器,所以在每次开始执行之前,都会对所有已创建的临时对象池中的值进行全面地清除
  6. 并发安全字典(sync.Map)对键的类型有要求吗?
    键的实际类型不能是函数类型、字典类型和切片类型
  7. Go如何保证gorountine执行完毕后继续执行(一个goroutine执行完后另一个开始执行)?
    a. 使用sync.WaitGroup
    b. 使用channel(无缓冲channel,取消息阻塞)
    c. 使用time.sleep

37. syscall

38. testing(2*)

39. text

40. time(2*)

  1. Timer的常见使用场景和接口
    • time.NewTimer(d)创建一个Timer
    • timer.Stop()停掉当前Timer
    • timer.Reset(d)重置当前Timer
    • time.After, time.AfterFunc等待指定时间

41. unicode(2*) $$

  • utf16
  • utf8

42. unsafe(*)

三、Go语言相关原理

1. Don’t communicate by sharing memory; share memory by communicating.

* 不要通过共享内存来通信,而应该通过通信来共享内存

2. GPM(goroutine)(***)

* Goroutine:就是咱们常用的用go关键字创建的执行体,它对应一个结构体g,结构体里保存了goroutine的堆栈信息
* Machine:表示操作系统的线程
* Processor:表示处理器,有了它才能建立G、M的联系
* 三者的关系
	1. 默认最多启动GOMAXPROCS线程和处理器,相互绑定,线程和处理器数量一致,节省切换线程开销
	2. 创建一个goroutine,进行函数体地址、参数起始地址、参数长度等信息以及调度相关属性更新,放到一个空闲处理器P的私有队列中
	3. 再创建一个goroutine,轮流放在处理器P私有队列中(优先空闲)【窗口】
	4. 当所有处理器P的私有队列满了,再创建一个goroutine,放到全局队列中【候车大厅】
	5. 线程M疯狂取goroutine,先取绑定处理器P私有队列goroutine,再取全局队列goroutine,再从其他处理器P取goroutine
	6. 如果没有要执行的goroutine,线程M与处理器P断开连接,进入休眠
	7. 如果执行goroutine发生IO阻塞住了,线程M不会等待,找其他可执行goroutine
	8. 系统调用
	9. sysmon:监控线程M,回收垃圾、回收长时间系统调度阻塞的P、向长时间运行的G发出抢占调度

3. CSP(channel)

* 全称Communicating Sequential Processes,意为通讯顺序进程,它是七大并发模型中的一种,它的核心观念是将两个并发执行的实体通过通道channel连接起来,所有的消息都通过channel传输

四、Go语言框架

1. Gin

  1. 支持多种HTTP请求方式
  • GET
    1. 路径参数:/user/:name
    2. 查询参数:
      • 默认参数:firstname := c.DefaultQuery(“firstname”, “Guest”) // /v1/user?firstname=“Smith”
      • 动态参数:lastname := c.Query(“lastname”) // /v1/user?lastname=“jack”
  • POST
    1. x-www-form-urlencoded:
      • 默认: message := c.DefaultPostForm(“message”)
      • 动态:message := c.PostForm(“message”)
      • 字典:names := c.PostFormMap(“names”)
    2. 上传文件(form-data):
      • 单文件:file, _ := c.FormFile(“file”)
      • 多文件:files := form.File[“files”]
      • 保存文件:c.SaveUploadedFile(file, dst)
  1. 分组路由:v1 := router.Group("/v1")
  2. HTTP Server和中间件:
    • 无中间件Server:r := gin.New()
    • 默认中间件Server:r := gin.Default(":8080") // 默认包含,gin.Logger,gin.Recovery中间件
    • 自定义HTTP Server
    • Gin运行多个HTTP Server:g.Go()
    • 中间件使用:r.Use(gin.Recovery())
    • 中间件使用go程,需要复制*gin.Context:cCp := c.Copy()
    • 自定义中间件:c.Next()
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()
		// Set example variable
		c.Set("example", "12345")
		// before request
		c.Next()
		// after request
		latency := time.Since(t)
		log.Print(latency)
		// access the status we are sending
		status := c.Writer.Status()
		log.Println(status)
	}
}
  1. 日志
    • 自定义日志格式
    • 第三方日志库:zap
  2. 模型绑定和验证
  • Must bind:强制绑定,无法很好的自定义控制(请求码)
  • Should bind:应该绑定,开发者负责HTTP请求错误处理,自定义校验函数
type PostParams struct {
	Name string `json:"name" uri:"name" form:"name"`
	Age  int    `json:"age" uri:"age" form:"age" binding:"required,mustBig"`
	Sex  bool   `json:"sex" uri:"sex" form:"sex"`
}

var p PostParams
// ShouldBind 通过Content-Type判断
err := c.ShouldBindJSON(&p)
  1. 请求返回和数据渲染
id := c.Param("id")
user := c.DefaultPostForm("user", "ft")
pwd := c.PostForm("pwd")

// gin.H is a shortcut for map[string]interface{}
c.JSON(http.StatusOK, gin.H{
	"id":     id,
	"user":   user,
	"pwd":    pwd,
	"method": c.Request.Method,
})
  1. 正常关机或重启

2. GORM v2

  1. 连接数据库
import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  // MySQL
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
  1. CRUD 接口
  • 创建
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
db.Select("Name", "Age", "CreatedAt").Create(&user) // 选择字段
db.Omit("Name", "Age", "CreatedAt").Create(&user) // 忽略字段

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users) // 批量插入
db.CreateInBatches(users, 100) // 批量插入100条
  • 查询
var user User

// 获取第一条记录(主键升序)
db.First(&user)
// 选定字段
db.Select("name", "age").Find(&users)
// WHERE,struct不能查询空值数据(0, '', false),请使用map[string]interface{}
db.Where("name = ?", "jinzhu").First(&user)
)
// Order
db.Order("age desc, name").Find(&users)
// Limit & Offset
db.Limit(10).Offset(5).Find(&users)
// Group & Having
db.Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&user)
// Joins
type result struct {
  Name  string
  Email string
}

db.Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
  • 更新
// 更新单个列(User是用户表)
db.Where("id = ?", 1).First(&User{}).Update("name", "jack")
// 更新多列,struct不能更新非0字段(0, '', false),请使用map[string]interface{}
db.Where("id = ?", 1).First(&User{}).Updates(User{
	Name: "jack",
	Age: 26,
})
  • 删除
// 假删
db.Where("id in (?)", []int{1, 2}).Delete(&User{})
// 真删
db.Where("id in (?)", []int{1, 2}).Unscoped().Delete(&User{})
  • 原生SQL
type Result struct {
  ID   int
  Name string
  Age  int
}

var result Result
// 查询
db.Raw("SELECT id, name, age FROM users WHERE id = ?", 3).Scan(&result)
// 增删改
db.Exec("DROP TABLE users")
  1. 关联
  • Belongs To
// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {
  gorm.Model
  Name      string
  CompanyID int
  Company   Company
}

type Company struct {
  ID   int
  Name string
}```
* Has One
```go
// User 只有一张 CreditCard,CreditCardID 是外键
type User struct {
  gorm.Model
  CreditCard CreditCard
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}
  • Has Many
// User 有多张 CreditCard,UserID 是外键
type User struct {
  gorm.Model
  CreditCards []CreditCard
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}
  • Many To Many
// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name string
}
  • 关联模式(自动创建,更新)
  • 预加载(Preload)
  1. 教程
  • Context
  • 错误处理
  • 链式操作
  • Session
  • 钩子:在新增,修改之前,之后调用方法
  • 事务:db.Transaction,在事务中执行一些 db 操作(从这里开始,您应该使用 ‘tx’ 而不是 ‘db’)
  • 迁移:db.AutoMigrate(&User{}) // User用户表
  • 日志
  • 通用数据库接口:*gorm.DB返回一个通用的数据库接口 *sql.DB
  • 性能
    1. 禁用默认事务(&gorm.Config)
    2. 缓存预编译语句
    3. 带 PreparedStmt 的 SQL 生成器(原生SQL)
    4. 查询选择字段
    5. 数据量大进行批量新增,更新
    6. 使用索引优化加速查询
    7. 读写分离
  • 自定义数据类型:Scanner / Valuer
  • 约定
  • 设置
  1. 高级主题
  • Dataase Resolver
  • Prometheus
  • 提示
  • 索引
  • 约束
  • 复合主键
  • 安全
  • GORM配置
  • 编写插件
  • 编写驱动

3. Redis

  1. Getting started
  2. Redis Cluster
  3. Redis Sentinel
  4. Redis Ring
  5. Tracing

4. gRPC

1. RPC(remote procedure call 远程过程调用)

框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。

2. gRPC

高性能,开源的通用RPC框架

3. ProtoBuf

  • 概念
    ProtoBuf实际是一套类似JSON或者XML的数据传输格式和规范,用于不同应用或进程之间进行通信时使用。通信时所传递的信息是通过Protobuf定义的message数据结构进行打包,然后编译成二进制的码流再进行传输或者存储。
  • 优点
    a. 足够简单
    b. 序列化后体积很小:消息大小只需要XML的1/10 ~ 1/3
    c. 解析速度快:解析速度比XML快20 ~ 100倍
    d. 多语言支持
    c. 更好的兼容性,Protobuf设计的一个原则就是要能够很好的支持向下或向上兼容
  • ProtoBuf与JSON的区别?
    a. ProtoBuf编码解码性能比JSON高,ProtoBuf编码成二进制,传输数据量驾校(高性能)
    b. gRPC可以通过ProtoBuf来定义接口,从而可以有更加严格的接口约束条件(安全性)
    c. JSON数据格式对用户更加友好,方便理解

5. Cron

6. kit

你可能感兴趣的:(Go,面经,go,面试,golang)