2024-02-17
隐藏
00
请注意,本文编写于 481 天前,最后修改于 196 天前,其中某些信息可能已经过时。

目录

关于channel的特性,下面说法正确的是?
下列代码有什么问题?
下列代码输出什么?
关于无缓冲和有冲突的channel,下面说法正确的是?
下列代码输出什么?
关于select机制,下面说法正确的是?
Go 程序中的包是什么?
关于字符串拼接,下列正确的是?
go struct能不能比较
Go 支持什么形式的类型转换?将整数转换为浮点数。
Log包线程安全吗?
Goroutine和线程的区别?
Go 语言中导入包时 . 和 _ 的区别是什么?
Go语言中的select case机制
详细解释--Mutex包的功能和用法。(从Mutex的4种状态,正常模式和饥饿模式,自旋角度)
详细解释--char和varchar区别。
1. 存储方式
2. 存储空间
3. 性能
4. 适用场景
5. 查询性能
6. 索引性能
7. 最大长度
总结
何时使用 CHAR 或 VARCHAR

Go 入门面试题汇总

🖊路边有野花 📅2024年5月19日⌛大约 14 分钟

下列代码是否会触发异常?

func Test59(t *testing.T) { runtime.GOMAXPROCS(1) intChan := make(chan int, 1) stringChan := make(chan string, 1) intChan <- 1 stringChan <- "hello" select { case value := <-intChan: fmt.Println(value) case value := <-stringChan: panic(value) } }

答案

不一定,当两个chan同时有值时,select 会随机选择一个可用通道做收发操作

## 关于channel的特性,下面说法正确的是?

A. 给一个 nil channel 发送数据,造成永远阻塞

B. 从一个 nil channel 接收数据,造成永远阻塞

C. 给一个已经关闭的 channel 发送数据,引起 panic

D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值

答案

A B C D

## 下列代码有什么问题?

const i = 100 var j = 123

func main() { fmt.Println(&j, j) fmt.Println(&i, i) }

答案

Go语言中,常量无法寻址, 是不能进行取指针操作的

## 下列代码输出什么?

func Test62(t *testing.T) { x := []string{"a", "b", "c"} for v := range x { fmt.Print(v) } }

答案

012

range 一个返回值时,这个值是下标,两个值时,第一个是下标,第二个是值,当 x 为 map时,第一个是key,第二个是value

## 关于无缓冲和有冲突的channel,下面说法正确的是?

A. 无缓冲的channel是默认的缓冲为1的channel;

B. 无缓冲的channel和有缓冲的channel都是同步的;

C. 无缓冲的channel和有缓冲的channel都是非同步的;

D. 无缓冲的channel是同步的,而有缓冲的channel是非同步的;

答案

D

## 下列代码输出什么?

func Foo(x interface{}) { if x == nil { fmt.Println("empty interface") return } fmt.Println("non-empty interface") } func Test64(t *testing.T) { var x *int = nil Foo(x) }

答案

non-empty interface

接口除了有静态类型,还有动态类型和动态值,
当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。
这里的 x 的动态类型是 *int,所以 x 不为 nil

## 关于select机制,下面说法正确的是?

A. select机制用来处理异步IO问题;

B. select机制最大的一条限制就是每个case语句里必须是一个IO操作;

C. golang在语言级别支持select关键字;

D. select关键字的用法与switch语句非常类似,后面要带判断条件;

答案

A B C

## Go 程序中的包是什么?

包(pkg)是 Go 工作区中包含 Go 源文件或其他包的目录。源文件中的每个函数、变量和类型都存储在链接包中。每个 Go 源文件都属于一个包,该包在文件顶部使用以下命令声明:

package <packagename>

您可以使用以下方法导入和导出包以重用导出的函数或类型:

import <packagename>

Golang 的标准包是 fmt,其中包含格式化和打印功能,如 Println().

## 关于字符串拼接,下列正确的是?

A. str := 'abc' + '123'

B. str := "abc" + "123"

C. str := '123' + "abc"

D. fmt.Sprintf("abc%d", 123)

答案

B D 双引号用来表示字符串 string,其实质是一个 byte 类型的数组,单引号表示 rune 类型。

open in new window

## go struct能不能比较

  • 相同struct类型的可以比较
  • 不同struct类型的不可以比较,编译都不过,类型不匹配
package main import "fmt" func main() { type A struct { a int } type B struct { a int } a := A{1} //b := A{1} b := B{1} if a == b { fmt.Println("a == b") }else{ fmt.Println("a != b") } } // output // command-line-arguments [command-line-arguments.test] // ./.go:14:7: invalid operation: a == b (mismatched types A and B)

## Go 支持什么形式的类型转换?将整数转换为浮点数。

Go 支持显式类型转换以满足其严格的类型要求。

i := 55 //int

j := 67.8 //float64

sum := i + int(j)//j is converted to int

## Log包线程安全吗?

Golang的标准库提供了log的机制,但是该模块的功能较为简单(看似简单,其实他有他的设计思路)。在输出的位置做了线程安全的保护。

## Goroutine和线程的区别?

从调度上看,goroutine的调度开销远远小于线程调度开销。

OS的线程由OS内核调度,每隔几毫秒,一个硬件时钟中断发到CPU,CPU调用一个调度器内核函数。这个函数暂停当前正在运行的线程,把他的寄存器信息保存到内存中,查看线程列表并决定接下来运行哪一个线程,再从内存中恢复线程的注册表信息,最后继续执行选中的线程。这种线程切换需要一个完整的上下文切换:即保存一个线程的状态到内存,再恢复另外一个线程的状态,最后更新调度器的数据结构。某种意义上,这种操作还是很慢的。

Go运行的时候包涵一个自己的调度器,这个调度器使用一个称为一个M:N调度技术,m个goroutine到n个os线程(可以用GOMAXPROCS来控制n的数量),Go的调度器不是由硬件时钟来定期触发的,而是由特定的go语言结构来触发的,他不需要切换到内核语境,所以调度一个goroutine比调度一个线程的成本低很多。

从栈空间上,goroutine的栈空间更加动态灵活。

每个OS的线程都有一个固定大小的栈内存,通常是2MB,栈内存用于保存在其他函数调用期间哪些正在执行或者临时暂停的函数的局部变量。这个固定的栈大小,如果对于goroutine来说,可能是一种巨大的浪费。作为对比goroutine在生命周期开始只有一个很小的栈,典型情况是2KB, 在go程序中,一次创建十万左右的goroutine也不罕见(2KB*100,000=200MB)。而且goroutine的栈不是固定大小,它可以按需增大和缩小,最大限制可以到1GB。

goroutine没有一个特定的标识。

在大部分支持多线程的操作系统和编程语言中,线程有一个独特的标识,通常是一个整数或者指针,这个特性可以让我们构建一个线程的局部存储,本质是一个全局的map,以线程的标识作为键,这样每个线程可以独立使用这个map存储和获取值,不受其他线程干扰。

goroutine中没有可供程序员访问的标识,原因是一种纯函数的理念,不希望滥用线程局部存储导致一个不健康的超距作用,即函数的行为不仅取决于它的参数,还取决于运行它的线程标识。

## Go 语言中导入包时 . 和 _ 的区别是什么?

比如:

import ( "log" . "github.com/alimy/mir/v4/core" "github.com/gin-gonic/gin" _ "github.com/rocboss/paopao-ce/mirc/admin/v1" _ "github.com/rocboss/paopao-ce/mirc/bot/v1"

)

在 Go 语言中,. 和 _ 都是用于导入包的特殊符号。

. 导入包后可以直接使用其中的符号,无需包名前缀。
. 的作用是将导入的包中的符号(函数、类型、常量等)直接在当前文件中可见,而不需要通过包名来访问。这意味着你可以直接使用导入包中的符号,而无需在代码中添加包名前缀。例如,使用 gin 包时,可以直接使用 gin 中的函数和类型,而不需要写成 gin.XXXopen in new window 的形式。

_ 导入包但不直接使用其中的符号,通常用于执行包的初始化或注册操作。
_ 的作用是导入包,但不直接使用该包中的任何符号。这通常用于执行包的初始化操作或注册操作。
比如:当你导入一个包但不使用其中的任何符号时,Go 编译器会产生一个编译错误。为了避免这种错误,可以使用 _ 来导入包,告诉编译器你只是希望执行包的初始化或注册操作,并不需要使用该包中的符号。
又比如:你服务启动时依赖某个包的init()方法里的逻辑,通过_ 导入时,会去执行对应包的init()方法。

## Go语言中的select case机制

在 Go 语言中,select 语句用于处理多个通道(channel)的操作。它类似于 switch 语句,但用于处理通道通信。

select 语句会阻塞直到其中的一个 case 分支可以执行,如果有多个 case 分支都可以执行,那么 Go 语言会随机选择一个执行。 以下是 select 语句的一些常见特性和用法:

基本语法:

go
select { case <-chan1: // 当从 chan1 成功接收值时执行 case chan2 <- value: // 当成功向 chan2 发送值时执行 default: // 当所有的 case 都无法立即执行时执行 }

多个可执行的 case 分支:如果有多个 case 分支同时满足执行条件(即通道操作就绪),那么 Go 语言会随机选择其中一个执行。

超时处理:可以使用 time 包设置超时,避免 select 永远阻塞。

go
timeout := time.After(5 * time.Second) select { case <-chan1: // 从 chan1 接收 case <-timeout: // 超时 }

非阻塞操作:如果 select 语句中没有任何一个 case 分支就绪,并且没有 default 分支,那么 select 语句会阻塞,直到至少有一个 case 分支可以执行。如果存在 default 分支,那么当所有的通道操作都没有准备好时,会立即执行 default 分支。

select 机制在处理多个并发的通道操作时非常有用,可以方便地实现并发任务之间的协调和通信。

## 详细解释--Mutex包的功能和用法。(从Mutex的4种状态,正常模式和饥饿模式,自旋角度)

在 Go 语言中,sync.Mutex是用于提供互斥锁的同步原语,它可以确保在同一时刻只有一个 goroutine 能够访问被保护的资源。

一、功能

sync.Mutex的主要功能是对共享资源进行同步访问控制,防止多个 goroutine 同时读写共享资源导致数据不一致或出现竞态条件。

二、用法

创建互斥锁:

go
var mu sync.Mutex

加锁:

go
mu.Lock() // 访问共享资源

解锁:

go
mu.Unlock()

三、Mutex 的四种状态

sync.Mutex实际上并没有严格意义上的明确四种状态,但可以从不同角度来理解其状态:

  1. 未锁定状态:初始状态下,互斥锁未被任何 goroutine 持有,可以被某个 goroutine 通过调用Lock方法锁定。
  2. 锁定状态:当一个 goroutine 调用Lock方法成功后,互斥锁处于锁定状态,其他 goroutine 尝试获取锁时会被阻塞,直到持有锁的 goroutine 调用Unlock方法释放锁。
  3. 正在竞争状态:当多个 goroutine 同时尝试获取锁时,互斥锁处于竞争状态。
  4. 饥饿状态(与饥饿模式相关):当一个 goroutine 等待锁的时间过长,进入饥饿模式后,互斥锁可以认为处于饥饿状态。

四、正常模式和饥饿模式

  1. 正常模式:
  • 在正常模式下,等待锁的 goroutine 以先进先出(FIFO)的顺序排队等待锁的释放。新唤醒的 goroutine 和已经在等待队列中的 goroutine 竞争锁的机会是平等的,但是刚被唤醒的 goroutine 有一定的优势,因为它可能已经在 CPU 上运行,而已经在等待队列中的 goroutine 可能需要重新被调度。

  • 正常模式的目的是为了在没有严重竞争的情况下提供较好的性能,因为它允许新唤醒的 goroutine 快速获取锁,减少了上下文切换的开销。

  1. 饥饿模式:
  • 当一个 goroutine 等待锁的时间超过 1 毫秒时,互斥锁进入饥饿模式。在饥饿模式下,互斥锁会直接把锁交给等待队列最前面的 goroutine,新来的 goroutine 不会尝试获取锁,而是直接进入等待队列的尾部。
  • 饥饿模式的目的是为了避免某些 goroutine 长时间等待锁而导致的不公平。在高竞争的情况下,饥饿模式可以确保每个等待锁的 goroutine 都有机会获取锁。

五、自旋

在 Go 语言中,sync.Mutex在某些情况下会使用自旋(spinning)策略。自旋是指当一个 goroutine 尝试获取锁时,如果发现锁已经被其他 goroutine 持有,它不会立即进入阻塞状态,而是在一个循环中不断地尝试获取锁,直到一定次数后才进入阻塞状态。

自旋的目的是为了在锁被短暂持有的情况下,减少上下文切换的开销。如果锁很快被释放,那么自旋的 goroutine 可以快速获取锁,而不需要进行上下文切换进入阻塞状态,然后再被唤醒。但是,如果锁被长时间持有,自旋会浪费 CPU 资源,因此自旋只会进行一定次数的尝试。

总的来说,sync.Mutex通过正常模式和饥饿模式以及自旋策略来平衡性能和公平性,以适应不同的竞争情况。

## 详细解释--char和varchar区别。

CHARVARCHAR 是 MySQL 中两种常见的字符数据类型,它们主要用于存储文本数据。尽管它们的功能相似,但在存储和使用上有一些关键的区别。以下是它们的主要区别:

1. 存储方式

  • CHAR:
    CHAR 是定长字符类型,表示存储固定长度的字符串。如果存储的字符串长度小于定义的长度,MySQL会自动在字符串后面填充空格,直到达到指定的长度。例如,定义 CHAR(10),即使存储的是 "abc"(长度为3),它会将 "abc" 填充为 "abc "(共10个字符,末尾有7个空格)。

  • VARCHAR:
    VARCHAR 是变长字符类型,表示存储可变长度的字符串。VARCHAR 只会存储实际字符的长度,并且会在存储时记录字符串的实际长度。因此,VARCHAR 不会像 CHAR 一样填充空格。如果存储的字符串长度是 3 个字符,VARCHAR(10) 只存储 3 个字符。

2. 存储空间

  • CHAR:
    CHAR 总是占用固定的空间,即使存储的字符串长度小于定义的长度,也会占用完整的空间。这可能导致存储空间浪费。例如,如果定义 CHAR(100),即使只存储了一个字符,它也会占用 100 字节(如果字符集是 UTF-8)。

  • VARCHAR:
    VARCHAR 只会占用实际存储的字符空间,再加上一个字节用于记录字符串的长度(如果最大长度小于 255,则占用 1 字节;如果最大长度大于等于 255,则占用 2 字节)。因此,VARCHAR 更加节省空间,尤其在存储长度较短的字符串时。

3. 性能

  • CHAR:
    由于 CHAR 存储的是固定长度的字符串,它在存取时的操作较为简单且速度较快,尤其适用于存储长度固定的字段(如身份证号码、邮政编码等)。因为每次读取时,MySQL 已经知道每个字符串的长度,不需要再计算实际的存储长度。

  • VARCHAR:
    由于 VARCHAR 存储的是变长字符串,MySQL 需要存储和读取额外的长度信息,因此在某些情况下,访问 VARCHAR 的速度可能比 CHAR 慢。不过,实际性能差异通常非常小,只有在大量数据需要频繁查询时,才可能有显著影响。

4. 适用场景

  • CHAR:
    适用于存储长度固定的字符串,比如国家代码、性别、邮政编码、电话号码等。这些字段通常具有固定的长度,因此使用 CHAR 可以提高性能。

  • VARCHAR:
    适用于存储长度不固定的字符串,如用户名、地址、描述等。这些字段的长度经常变化,因此使用 VARCHAR 可以节省存储空间。

5. 查询性能

  • CHAR:
    由于是定长字段,查询时不需要额外计算字段的实际长度,因此通常可以提供较快的查询速度,尤其是对于长度固定的字段。

  • VARCHAR:
    对于变长字段,查询时需要额外考虑存储长度信息,因此在某些情况下可能稍微影响性能,尤其是在涉及大量变长字段的查询时。

6. 索引性能

  • CHAR:
    因为是定长的,CHAR 的索引在性能上通常比较稳定,尤其是在排序和查找时。

  • VARCHAR:
    由于变长,VARCHAR 的索引可能会稍微影响性能,尤其是对于较长的字符串列。然而,如果该列的数据非常多样化,VARCHAR 的空间利用效率会更高。

7. 最大长度

  • CHAR:
    CHAR 的最大长度为 255 字符。

  • VARCHAR:
    VARCHAR 的最大长度为 65,535 字符,但实际长度受到行的大小限制。在 MySQL 中,一行的最大大小为 65,535 字节,这意味着存储一个 VARCHAR 字段的最大长度将受到字符集(如 UTF-8)每个字符所占字节数的影响。

总结

特性CHARVARCHAR
存储方式固定长度(填充空格)变动长度(不填充空格)
存储空间总是占用固定空间根据实际长度占用空间
性能查询速度较快(适用于定长字段)查询时略慢(适用于变长字段)
适用场景长度固定的字符串(如邮政编码、身份证)长度不固定的字符串(如用户名、地址)
最大长度最大255字符最大65,535字符

何时使用 CHARVARCHAR

  • 如果你知道字段的长度是固定的(例如,固定长度的编码或标识符),使用 CHAR 可以提高性能并节省存储空间。
  • 如果字段的长度不确定,且可能会有很多不同长度的字符串,VARCHAR 是更好的选择,可以节省存储空间。

本文作者:han

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!