Go基础面经大全(持续补充中)
Go基础
1. 基础特性
-
Go的优势
-
天生支持并发,性能高。
-
单一的标准代码格式,比其他语言更具可读性。
-
自动垃圾收集机制比Java和Python更有效,因为它与程序同时执行。
-
-
Go数据类型
- int, string, float, bool, array, slice, map, channel, pointer, struct, interface, method
-
go中的25个关键字
- 程序声明2个:
package import - 程序实体声明和定义8个:
var const type func struct map chan interface - 程序流程控制15个:
for range continue break select switch case default if else fallthrough defer go goto return
- 程序声明2个:
-
Go程序中的包是什么?
- 项目中包含go源文件以及其它包的目录,源文件中的函数、变量、类型都存储在该包中
- 每个源文件都属于一个包,该包在文件顶部使用
package packageName声明 - 当我们在源文件中引用第三包时,需要还用
import packageName
-
Go支持什么形式的类型转换?如何实现整数转为浮点数
-
go支持显示类型转换,即严格强制类型转换
-
a := 15 b := float64(a) fmt.Println(b, reflect.TypeOf(b))
-
2. 初级语法
-
=和:=的区别?-
:= 声明+赋值= 仅赋值
-
var foo int foo = 10 // 等价于 foo := 10
-
-
指针的作用?
-
指针用来保存变量的地址。
-
例如:
-
var x = 5 var p *int = &x fmt.Printf("x = %d", *p) // x 可以用 *p 访问* -
*运算符,也称为解引用运算符,用于访问地址中的值。
-
&运算符,也称为地址运算符,用于返回变量的地址。
-
-
Go 允许多个返回值吗?
-
允许
-
func swap(x, y string) (string, string) {return y, x }func main() {a, b := swap("A", "B")fmt.Println(a, b) // B A }
-
-
Go 有异常类型吗?
-
Go 没有异常类型,只有错误类型(Error),通常使用返回值来表示异常状态。
-
f, err := os.Open("test.txt") if err != nil {log.Fatal(err) }
-
-
什么是协程(Goroutine)?
- Goroutine 是与其他函数或方法同时运行的函数或方法。 Goroutines 可以被认为是轻量级的线程。 与线程相比,创建 Goroutine 的开销很小。 Go应用程序同时运行数千个 Goroutine 是非常常见的做法。
-
如何高效地拼接字符串?
-
Go 语言中,字符串是只读的,也就意味着每次修改操作都会创建一个新的字符串。如果需要拼接多次,应使用 strings.Builder,最小化内存拷贝次数。
-
var str strings.Builder for i := 0; i < 1000; i++ {str.WriteString("a") } fmt.Println(str.String())
-
-
什么是 rune 类型?
-
ASCII 码只需要 7 bit 就可以完整地表示,但只能表示英文字母在内的128个字符,为了表示世界上大部分的文字系统,发明了 Unicode, 它是ASCII的超集,包含世界上书写系统中存在的所有字符,并为每个代码分配一个标准编号(称为Unicode CodePoint),在 Go 语言中称之为 rune,是 int32 类型的别名。
-
Go 语言中,字符串的底层表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。例如下面的例子中 语 和 言 使用 UTF-8 编码后各占 3 个 byte,因此 len(“Go语言”) 等于 8,当然我们也可以将字符串转换为 rune 序列。
-
fmt.Println(len("Go语言")) // 8 fmt.Println(len([]rune("Go语言"))) // 4
-
-
Go 支持默认参数或可选参数吗?
- Go 语言不支持可选参数(python 支持),也不支持方法重载(java支持)。
-
如何交换 2 个变量的值?
-
a, b := "A", "B" a, b = b, a fmt.Println(a, b) // B A
-
-
Go 语言 tag 的用处?
-
tag 可以理解为 struct 字段的注解,可以用来定义字段的一个或多个属性。框架/工具可以通过反射获取到某个字段定义的属性,采取相应的处理方式。tag 丰富了代码的语义,增强了灵活性。
-
例如:
-
package mainimport "fmt" import "encoding/json"type Stu struct {Name string `json:"stu_name"`ID string `json:"stu_id"`Age int `json:"-"` }func main() {buf, _ := json.Marshal(Stu{"Tom", "t001", 18})fmt.Printf("%s\n", buf) } -
这个例子使用 tag 定义了结构体字段与 json 字段的转换关系,Name -> stu_name, ID -> stu_id,忽略 Age 字段。很方便地实现了 Go 结构体与不同规范的 json 文本之间的转换。
-
-
字符串打印时,
%v和%+v的区别-
%v 和 %+v 都可以用来打印 struct 的值,区别在于 %v 仅打印各个字段的值,%+v 还会打印各个字段的名称。
-
type Stu struct {Name string }func main() {fmt.Printf("%v\n", Stu{"Tom"}) // {Tom}fmt.Printf("%+v\n", Stu{"Tom"}) // {Name:Tom} } -
但如果结构体定义了 String() 方法,%v 和 %+v 都会调用 String() 覆盖默认值。
-
-
Go 语言中如何表示枚举值(enums)?
-
通常使用常量(const) 来表示枚举值。
-
type StuType int32const (Type1 StuType = iotaType2Type3Type4 )func main() {fmt.Println(Type1, Type2, Type3, Type4) // 0, 1, 2, 3 } -
参考 What is an idiomatic way of representing enums in Go? - StackOverflow
-
-
空 struct{} 的用途?
-
使用空结构体 struct{} 可以节省内存,一般作为占位符使用,表明这里并不需要一个值。
-
fmt.Println(unsafe.Sizeof(struct{}{})) // 0
-
-
比如使用 map 表示集合时,只关注 key,value 可以使用 struct{} 作为占位符。如果使用其他类型作为占位符,例如 int,bool,不仅浪费了内存,而且容易引起歧义。
-
type Set map[string]struct{}func main() {set := make(Set)for _, item := range []string{"A", "A", "B", "C"} {set[item] = struct{}{}}fmt.Println(len(set)) // 3if _, ok := set["A"]; ok {fmt.Println("A exists") // A exists} }
-
-
再比如,使用信道(channel)控制并发时,我们只是需要一个信号,但并不需要传递值,这个时候,也可以使用 struct{} 代替。
-
func main() {ch := make(chan struct{}, 1)go func() {<-ch// do something}()ch <- struct{}{}// ... }
-
-
再比如,声明只包含方法的结构体。
-
type Lamp struct{}func (l Lamp) On() {println("On")} func (l Lamp) Off() {println("Off") }
-
-
-
go中的cap函数可以作用于哪些内容?
-
可作用于的类型有:
- 数组(array)
- 切片(slice)
- 通道(channel)
-
查看他们的容量大小,而不是装的数据大小
-
-
go语言中new的作用是什么?
- 使用new函数来分配内存空间
- 传递给new函数的是一个类型,而不是一个值
- 返回值是指向这个新分配的地址的指针
-
go语言中的make作用是什么?
- 分配内存空间并进行初始化, 返回值是该类型的实例而不是指针
- make只能接收三种类型当做参数:slice、map、channel
-
总结make和new的区别?
- new可以接收任意内置类型当做参数,返回的是对应类型的指针
- make只能接收slice、map、channel当做参数,返回值是对应类型的实例
-
如何在运行时检查变量类型?
- 类型开关(
Type Switch)是在运行时检查变量类型的最佳方式。
-
类型开关按类型而不是值来评估变量。每个
Switch至少包含一个case用作条件语句 -
如果没有一个
case为真,则执行default。
- 类型开关(
-
switch case fallthrough default使用场景
-
func main() {var a intfor i := 0; i < 10; i++{a = rand.Intn(100)switch {case a >= 80:fmt.Println("优秀", a)fallthrough // 强制执行下一个casecase a >= 60:fmt.Println("及格", a)fallthroughdefault:fmt.Println("不及格", a)}} }
-
-
fmt包中Printf、Sprintf、Fprintf都是格式化输出,有什么不同?
-
虽然这三个函数都是格式化输出,但是输出的目标不一样
- Printf输出到控制台
- Sprintf结果赋值给返回值
- FprintF输出到指定的io.Writer接口中
-
例如:
-
func main() {var a int = 15file, _ := os.OpenFile("test.log", os.O_CREATE|os.O_APPEND, 0644)// 格式化字符串并输出到文件n, _ := fmt.Fprintf(file, "%T:%v:%p", a, a, &a)fmt.Println(n) }
-
-
-
go语言中的数组和切片的区别是什么?
- 数组:
- 数组固定长度,数组长度是数组类型的一部分,所以[3]int和[4]int是两种不同的数组类型
- 数组类型需要指定大小,不指定也会根据初始化,自动推算出大小,大小不可改变,数组是通过值传递的
- 切片:
- 切片的长度可改变,切片是轻量级的数据结构,三个属性:指针、长度、容量
- 不要指定切片的大小,切片也是值传递只不过切片的一个属性指针指向的数据不变,所以看起来像引用传递
- 切片可以通过数组来初始化也可以通过make函数来初始化,初始化时的len和cap相等,然后进行扩容
- 切片扩容的时候会导致底层的数组复制,也就是切片中的指针属性会发生变化
- 切片也是拷贝,在不发生扩容时,底层使用的是同一个数组,当对其中一个切片append的时候, 该切片长度会增加
但是不会影响另外一个切片的长度 - copy函数将原切片拷贝到目标切片,会导致底层数组复制,因为目标切片需要通过make函数来声明初始化内存,然后
将原切片指向的数组元素拷贝到新切片指向的数组元素
- 重点:数组保存真正的数据,切片值保存数组的指针和该切片的长度和容量
- append函数如果切片容量足够的话,只会影响当前切片的长度,数组底层不会复制,不会影响与数组关联的其它切片的长度
- copy直接会导致数组底层复制。
- 数组:
-
go语言中值传递和地址传递(引用传递)如何运行?有什么区别?举例说明
- 值传递会把参数的值复制一份放到对应的函数里,两个变量的地址不同,不可互相修改
- 地址传递会把参数的地址复制一份放到对应的函数里,两个变量的地址相同,可以互相修改
- 例如:数组传递就是值传递,而切片传递就是数组的地址传递(本质上切片值传递,只不过是保存的数据地址相同)
-
go中的参数传递、引用传递
-
go语言中的所有的传参都是值传递(传值),都是一个副本,一个拷贝,
-
因为拷贝的内容有时候是非引用类型(int, string, struct)等,这样在函数中就无法修改原内容数据
-
有的是引用类型(指针、slice、map、chan),这样就可以修改原内容数据
-
go中的引用类型包含slice、map、chan,它们有复杂的内部结构,除了申请内存外,还需要初始化相关属性
-
内置函数new计算类型大小,为其分配零值内存,返回指针。
-
而make会被编译器翻译成具体的创建函数,由其分配内存并初始化成员结构,返回对象而非指针
-
-
go中数组和切片在传递时有什么区别?
- 数组是值传递
- 切片地址传递(引用传递)
-
go中slice的底层实现
- 切片是基于数组实现的,它的底层是数组,它本身非常小,它可以理解为对底层数组的抽闲
- 因为基于数组实现,所以它的底层内存是连续分配的,效率非常高,还可以通过索引获取数据
- 切片本身并不是动态数组或数组指针,它内部实现的数据结构体通过指针引用底层数组
- 设定相关属性将读写操作限定在指定的区域内,切片本身是一个只读对象,其工作机制类似于数组指针的一种封装
- 切片对象非常小,因为它只有三个字段的数据结构:指向底层数组的指针、切片的长度、切片的容量
-
go中slice的扩容机制,有什么注意点?
- 首先判断,如果新申请的容量大于2倍的旧容量,最终容量就是新申请的容量
- 否则判断,如果旧切片的长度小于1024,最终容量就是旧容量的两倍
- 否则判断,如果旧切片的长度大于等于1024,则最终容量从旧容量开始循环增加原来的1/4,直到最终容量大于新申请的容量
- 如果最终容量计算值溢出,则最终容量就是新申请的容量
-
go中是如何实现切片扩容的?[答案有误,需要重新确定]
-
当容量小于1024时,每次扩容容量翻倍,当容量大于1024时,每次扩容加25%.
-
func main() {s1 := make([]int, 0)for i := 0; i < 3000; i++{fmt.Println("len =", len(s1), "cap = ", cap(s1))s1 = append(s1, i)} }
-
-
扩容前后的slice是否相同?
- 情况一:
- 原来数组还有容量可以扩容(实际容量没有填充完),这种情况下,扩容之后的切片还是指向原来的数组
- 对一个切片的操作可能影响多个指针指向相同地址的切片
- 情况二:
- 原来数组的容量已经达到了最大值,在扩容,go默认会先开辟一块内存区域,把原来的值拷贝过来
- 然后再执行append操作,这种情况丝毫不影响原数组
- 注意:要复制一个slice最好使用copy函数
- 情况一:
-
如何判断 2 个字符串切片(slice) 是相等的?
-
go 语言中可以使用反射 reflect.DeepEqual(a, b) 判断 a、b 两个切片是否相等,但是通常不推荐这么做,使用反射非常影响性能。
-
通常采用的方式如下,遍历比较切片中的每一个元素(注意处理越界的情况)。
-
func StringSliceEqualBCE(a, b []string) bool {if len(a) != len(b) {return false}if (a == nil) != (b == nil) {return false}b = b[:len(a)]for i, v := range a {if v != b[i] {return false}}return true }
-
-
-
看下面代码defer的执行顺序是什么?defer的作用和特点是什么?
-
在普通函数或方法前加上defer关键字,就完成了defer所需要的语法,当defer语句被执行时,跟在defer语句后的函数会被延迟执行
-
知道包含该defer语句的函数执行完毕,defer语句后的函数才会执行,无论包含defer语句的函数是通过return正常结束,还是通过panic导致的异常结束
-
可以在一个函数中执行多条defer语句,由于在栈中存储,所以它的执行顺序和声明顺序相反
-
多个 defer 语句,遵从后进先出(Last In First Out,LIFO)的原则,最后声明的 defer 语句,最先得到执行。defer 在 return 语句之后执行,但在函数退出之前,defer 可以修改返回值。
-
例子:
-
func test() int {i := 0defer func() {fmt.Println("defer1")}()defer func() {i += 1fmt.Println("defer2")}()return i }func main() {fmt.Println("return", test()) } // defer2 // defer1 // return 0 -
这个例子中,可以看到 defer 的执行顺序:后进先出。但是返回值并没有被修改,这是由于 Go 的返回机制决定的,执行 return 语句后,Go 会创建一个临时变量保存返回值,因此,defer 语句修改了局部变量 i,并没有修改返回值。那如果是有名的返回值呢?
-
func test() (i int) {i = 0defer func() {i += 1fmt.Println("defer2")}()return i }func main() {fmt.Println("return", test()) } // defer2 // return 1 -
这个例子中,返回值被修改了。对于有名返回值的函数,执行 return 语句时,并不会再创建临时变量保存,因此,defer 语句修改了 i,即对返回值产生了影响。
-
-
defer的常用场景
- defer语句经常被用于处理成对的操作打开/关闭,链接/断开连接,加锁/释放锁
- 通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放
- 释放资源的defer语句应该直接跟在请求资源处理错误之后
- 注意:defer一定要放在请求资源处理错误之后
-
defer语句中通过recover捕获panic例子
-
注意要在defer后函数里的recover()
-
func main() {defer func() {err := recover()fmt.Println(err)}()defer fmt.Println("first defer")defer fmt.Println("second defer")defer fmt.Println("third defer")fmt.Println("哈哈哈哈")panic("abc is an error") }
-
-
哈希概念讲解
-
哈希表又称为散列表,由一个直接寻址表和一个哈希函数组成
-
由于哈希表的大小是有限的而要存储的数值是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到相同位置的情况,这种情况叫做哈希冲突
-
通过拉链法解决哈希冲突:
- 哈希表每个位置都连接一个链表,当冲突发生是,冲突的元素将会被加到该位置链表的最后
-
哈希表的查找速度起决定性作用的就是哈希函数: 除法哈希发、乘法哈希法、全域哈希法
-
哈希表的应用?
- 字典与集合都是通过哈希表来实现的
- md5曾经是密码学中常用的哈希函数,可以把任意长度的数据映射为128位的哈希值
-
-
go中的map底层实现
- go中map的底层实现就是一个散列表,因此实现map的过程实际上就是实现散列表的过程
- 在这个散列表中,主要出现的结构体由两个,一个是hmap、一个是bmap
- go中也有一个哈希函数,用来对map中的键生成哈希值
- hash结果的低位用于把k/v放到bmap数组中的哪个bmap中
- 高位用于key的快速预览,快速试错
-
go中的map如何扩容
- 翻倍扩容:如果map中的键值对个数/桶的个数>6.5,就会引发翻倍扩容
- 等量扩容:当B<=15时,如果溢出桶的个数>=2的B次方就会引发等量扩容
- 当B>15时,如果溢出桶的个数>=2的15次方时就会引发等量扩容
-
go中map的查找
- go中的map采用的是哈希查找表,由哈希函数通过key和哈希因此计算出哈希值,
- 根据hamp中的B来确定放到哪个桶中,如果B=5,那么就根据哈希值的后5位确定放到哪个桶中
- 在用哈希值的高8位确定桶中的位置,如果当前的bmap中未找到,则去对应的overflow bucket中查找
- 如果当前map处于数据搬迁状态,则优先从oldbuckets中查找
-
如何判断 map 中是否包含某个 key ?
-
if val, ok := dict["foo"]; ok {//do something here } -
dict[“foo”] 有 2 个返回值,val 和 ok,如果 ok 等于 true,则说明 dict 包含 key “foo”,val 将被赋予 “foo” 对应的值。
-
2. 代码输出
2.1 常量与变量
-
下面代码的输出是:
func main() {const (a, b = "golang", 100d, ef bool = trueg)fmt.Println(d, e, g) }答案:
golang 100 true在同一个 const group 中,如果常量定义与前一行的定义一致,则可以省略类型和值。编译时,会按照前一行的定义自动补全。即等价于
gofunc main() { const ( a, b = "golang", 100 d, e = "golang", 100 f bool = true g bool = true )fmt.Println(d, e, g) } -
下面代码输出是:
func main() {const N = 100var x int = Nconst M int32 = 100var y int = Mfmt.Println(x, y) }答案:
编译失败:cannot use M (type int32) as type int in assignment Go 语言中,常量分为无类型常量和有类型常量两种,const N = 100,属于无类型常量,赋值给其他变量时,如果字面量能够转换为对应类型的变量,则赋值成功,例如,var x int = N。但是对于有类型的常量 const M int32 = 100,赋值给其他变量时,需要类型匹配才能成功,所以显示地类型转换: var y int = int(M) -
下面代码的输出是:
func main() {var a int8 = -1var b int8 = -128 / afmt.Println(b) }答案:
-128 int8 能表示的数字的范围是 [-2^7, 2^7-1],即 [-128, 127]。-128 是无类型常量,转换为 int8,再除以变量 -1,结果为 128,常量除以变量,结果是一个变量。变量转换时允许溢出,符号位变为1,转为补码后恰好等于 -128。 对于有符号整型,最高位是是符号位,计算机用补码表示负数。补码 = 原码取反加一。 例如:-1 : 11111111 00000001(原码) 11111110(取反) 11111111(加一) -128: 10000000(原码) 01111111(取反) 10000000(加一) -1 + 1 = 011111111 + 00000001 = 00000000(最高位溢出省略) -128 + 127 = -110000000 + 01111111 = 11111111 -
下面代码输出是:
func main() {const a int8 = -1var b int8 = -128 / afmt.Println(b) }答案:
编译失败:constant 128 overflows int8 -128 和 a 都是常量,在编译时求值,-128 / a = 128,两个常量相除,结果也是一个常量,常量类型转换时不允许溢出,因而编译失败。
2.2 作用域
-
下列代码输出是:
func main() {var err errorif err == nil {err := fmt.Errorf("err")fmt.Println(1, err)}if err != nil {fmt.Println(2, err)} }答案:
1 err := 表示声明并赋值,= 表示仅赋值。 变量的作用域是大括号,因此在第一个 if 语句 if err == nil 内部重新声明且赋值了与外部变量同名的局部变量 err。对该局部变量的赋值不会影响到外部的 err。因此第二个 if 语句 if err != nil 不成立。所以只打印了 1 err。
2.3 defer 延迟调用
-
下列代码输出:
type T struct{}func (t T) f(n int) T {fmt.Print(n)return t }func main() {var t Tdefer t.f(1).f(2)fmt.Print(3) }答案:
132 defer 延迟调用时,需要保存函数指针和参数,因此链式调用的情况下,除了最后一个函数/方法外的函数/方法都会在调用时直接执行。也就是说 t.f(1) 直接执行,然后执行 fmt.Print(3),最后函数返回时再执行 .f(2),因此输出是 132。 -
func f(n int) {defer fmt.Println(n)n += 100 }func main() {f(1) }答案:
1 打印 1 而不是 101。defer 语句执行时,会将需要延迟调用的函数和参数保存起来,也就是说,执行到 defer 时,参数 n(此时等于1) 已经被保存了。因此后面对 n 的改动并不会影响延迟函数调用的结果。 -
func main() {n := 1defer func() {fmt.Println(n)}()n += 100 }答案:
101 匿名函数没有通过传参的方式将 n 传入,因此匿名函数内的 n 和函数外部的 n 是同一个,延迟执行时,已经被改变为 101。 -
func main() {n := 1if n == 1 {defer fmt.Println(n)n += 100}fmt.Println(n) }答案:
101 1 先打印 101,再打印 1。defer 的作用域是函数,而不是代码块,因此 if 语句退出时,defer 不会执行,而是等 101 打印后,整个函数返回时,才会执行。
3. 中级语法
-
go两个接口之间可以存在什么关系?
- 如果两个接口有相同的方法列表,那么他俩就是等价的,可以相互赋值
- 接口A可以嵌套到接口B里面,那么接口B就有了自己的方法列表+接口A的方法列表
-
什么是 goroutine,你如何停止它?
-
goroutine是协程/轻量级线程/用户态线程,不同于传统的内核态线程
-
占用资源特别少,创建和销毁只在用户态执行不会到内核态,节省时间
-
创建goroutine需要使用go关键字
-
可以向goroutine发送一个信号通道来停止它,goroutine内部需要检查信号通道
-
例子:
-
func main() {var wg sync.WaitGroup // 等待组进行多个任务的同步,可以保证并发环境中完成指定数量的任务,每个sync.WaitGroup值在内部维护着一个计数,此计数的初始默认值为0var exit = make(chan bool)wg.Add(1) // 等待组的计数器+1go func() {for {select {case <-exit: // 接收到信号后return退出当前goroutinefmt.Println("goroutine接收到信号退出了!")wg.Done() // 等待组的计数器-1returndefault:fmt.Println("还没有接收到信号")}}}()exit <- truewg.Wait() // 当等待组计数器不等于0时阻塞,直到变为0 }
-
-
go中同步锁(也叫互斥锁)有什么特点,作用是什么?何时使用互斥锁,何时使用读写锁?
-
当一个goroutine获得了Mutex(互斥锁)后,其它goroutine就只能乖乖等待,除非该goroutine释放Mutex
-
RWMutext(读写互斥锁)在读锁占用的情况下会阻止写,但不会阻止读,在写锁占用的情况下,会阻止任何其它goroutine进来
-
无论是读还是写,整个锁相当于由该goroutine独占
-
作用:保证资源在使用时的独有性,不会因为并发导致数据错乱,保证系统稳定性
-
案例:
-
package main import ("fmt""sync""time" ) var (num = 0lock = sync.RWMutex{} // 耗时:100+毫秒//lock = sync.Mutex{} // 耗时:50+毫秒 ) func main() {start := time.Now()go func() {for i := 0; i < 100000; i++{lock.Lock()//fmt.Println(num)num++lock.Unlock()}}()for i := 0; i < 100000; i++{lock.Lock()//fmt.Println(num)num++lock.Unlock()}fmt.Println(num)fmt.Println(time.Now().Sub(start)) }
-
-
总结:
- 如果对数据写的比较多,使用Mutex同步锁/互斥锁性能更高
- 如果对数据读的比较多,使用RWMutex读写锁性能更高
-
-
goroutine案例(两个goroutine,一个负责输出数字,另一个负责输出26个英文字母,格式如下:12ab34cd56ef78gh … yz)
-
package main import ("fmt""sync""unicode/utf8" ) // 案例:两个goroutine,一个负责输出数字,另一个负责输出26个英文字母,格式如下:12ab34cd56ef78gh ... yz var (wg = sync.WaitGroup{} // 和第五题很相关。申明等待组chNum = make(chan bool)chAlpha = make(chan bool) ) func main() {go func() {i := 1for {<-chNum // 接到信号,运行该goroutinefmt.Printf("%v%v", i, i + 1)i += 2chAlpha <- true // 发送信号}}()wg.Add(1) // 等待组的计数器+1go func() {str := "abcdefghigklmnopqrstuvwxyz"i := 0for {<-chAlpha // 接到信号,运行该goroutinefmt.Printf("%v", str[i:i+2])i += 2if i >= utf8.RuneCountInString(str){wg.Done() // 等待组的计数器-1return}chNum <- true // 发送信号}}()chNum <- true // 发送信号wg.Wait() // 等待组的计数器不为0时,阻塞main进程,直到等待组的计数器为0 }
-
-
介绍一下channel
- go中不要通过共享内存来通信,而要通过通信实现共享内存
- go中的csp并发模型,中文名通信顺序进程,就是通过goroutine和channel实现的
- channel收发遵循先进先出,分为有缓冲通道(异步通道),无缓冲通道(同步通道)
-
go中channel的特性
- 给一个nil的channel发送数据,会造成永久阻塞
- 从一个nil的channel接收数据,会造成永久阻塞
- 给一个已经关闭的channel发送数据,会造成panic
- 从一个已经关闭的channel接收数据,如果缓冲区为空,会返回零值
- 无缓冲的channel是同步的,有缓冲的channel是异步的
- 关闭一个nil channel会造成panic
-
channel中ring buffer的实现
- channel中使用了ring buffer(环形缓冲区)来缓存写入数据,
- ring buffer有很多好处,而且非常适合实现FiFo的固定长度队列
- channel中包含buffer、sendx、recvx
- recvx指向最早被读取的位置,sendx指向再次写入时插入的位置
-
go语言中,channel通道有什么特点,需要注意什么?
-
总结:
- 给一个nil channel发送数据时会一直堵塞
- 从一个nil channel接收数据时会一直阻塞
- 给一个已关闭的channel发送数据时会panic
- 从一个已关闭的channel中读取数据时,如果channel为空,则返回通道中类型的零值
-
案例:
-
package main import ("fmt""sync" ) func main() {var wg sync.WaitGroup // 等待组var ch chan int // nil channelvar ch1 = make(chan int) // 创建channelfmt.Println(ch, ch1) // <nil> 0xc000086060wg.Add(1) // 等待组的计数器+1go func() {//ch <- 15 // 如果给一个nil的channel发送数据会造成永久阻塞//<-ch // 如果从一个nil的channel中接收数据也会造成永久阻塞ret := <-ch1fmt.Println(ret)ret = <-ch1 // 从一个已关闭的通道中接收数据,如果缓冲区中为空,则返回该类型的零值fmt.Println(ret)wg.Done() // 等待组的计数器-1}()go func() {//close(ch1)ch1 <- 15 // 给一个已关闭通道发送数据就会包panic错误close(ch1)}()wg.Wait() // 等待组的计数器不为0时阻塞 }
-
-
-
go中channel缓冲有什么特点?
- 无缓冲的通道是同步的,有缓冲的通道是异步的
-
写一个定时任务,每秒执行一次
-
func main() {t1 := time.NewTicker(time.Second * 1) // 创建一个周期定时器var i = 1for {if i == 10{break}select {case <-t1.C: // 一秒执行一次的定时任务task1(i)i++}} } func task1(i int) {fmt.Println("task1执行了---", i) }
-
4. 基础应用
-
如何关闭 HTTP 的响应体的?
-
直接在处理 HTTP 响应错误的代码块中,直接关闭非 nil 的响应体;
-
手动调用 defer 来关闭响应体。
-
正确示例:
-
func main() {resp, err := http.Get("http://www.baidu.com") // 发出请求并返回请求结果// 关闭 resp.Body 的正确姿势if resp != nil {defer resp.Body.Close()}checkError(err) // 检查错误,省略写法defer resp.Body.Close() // 手动调用defer来关闭响应体body, err := ioutil.ReadAll(resp.Body) // 一次性读写文件的全部数据checkError(err)fmt.Println(string(body)) }
-
-
-
是否主动关闭过http连接,为啥要这样做?
-
有关闭,不关闭会程序可能会消耗完 socket 描述符。有如下2种关闭方式:
-
直接设置请求变量的 Close 字段值为 true,每次请求结束后就会主动关闭连接。
-
设置 Header 请求头部选项 Connection: close,然后服务器返回的响应头部也会有这个选项,此时 HTTP 标准库会主动断开连接
-
// 主动关闭连接 func main() {req, err := http.NewRequest("GET", "http://golang.org", nil)checkError(err)req.Close = true // 直接设置请求变量的Close字段值为true,每次请求结束后主动关闭连接//req.Header.Add("Connection", "close") // 等效的关闭方式resp, err := http.DefaultClient.Do(req)if resp != nil {defer resp.Body.Close()}checkError(err)body, err := ioutil.ReadAll(resp.Body)checkError(err)fmt.Println(string(body)) }
-
-
你可以创建一个自定义配置的 HTTP transport(传输) 客户端,用来取消 HTTP 全局的复用连接。
-
func main() {tr := http.Transport{DisableKeepAlives: true} // 自定义配置传输客户端,用来取消HTTP全部的复用连接。client := http.Client{Transport: &tr}resp, err := client.Get("https://golang.google.cn/")if resp != nil {defer resp.Body.Close()}checkError(err)fmt.Println(resp.StatusCode) // 200body, err := ioutil.ReadAll(resp.Body)checkError(err)fmt.Println(len(string(body))) }
-
-
-
解析 JSON 数据时,默认将数值当做哪种类型?
-
在 encode/decode JSON 数据时,Go 默认会将数值当做 float64 处理。
-
func main() {var data = []byte(`{"status": 200}`)var result map[string]interface{}if err := json.Unmarshal(data, &result); err != nil {log.Fatalln(err)} }解析出来的 200 是 float 类型。
-
-
JSON 标准库对 nil slice 和 空 slice 的处理是一致的吗?
-
首先 JSON 标准库对 nil slice 和 空 slice 的处理是不一致。
-
通常错误的用法,会报数组越界的错误,因为只是声明了slice,却没有给实例化的对象。
var slice []int // nil slice slice[1] = 0此时slice的值是nil,这种情况可以用于需要返回slice的函数,当函数出现异常的时候,保证函数依然会有nil的返回值。
empty slice 是指slice不为nil,但是slice没有值,slice的底层的空间是空的,此时的定义如下:
slice := make([]int,0)// 空slice,没有值,空间也是空的 slice := []int{}当我们查询或者处理一个空的列表的时候,这非常有用,它会告诉我们返回的是一个列表,但是列表内没有任何值。总之,nil slice 和 empty slice是不同的东西,需要我们加以区分的。
-
5. 扩展了解
-
go convey是什么,一般用来做什么?
- go convey是一个支持golang的单元测试框架
- 能够自动监控文件修改并启动测试,并可以将测试结果实时输出到web界面
- 提供了丰富的断言简化测试用例的编写
-
说说go语言的beego框架
- beego 是一个 golang 实现的轻量级HTTP框架
- beego 可以通过注释路由、正则路由等多种方式完成 url 路由注入
- 可以使用 bee new 工具生成空工程,然后使用 bee run 命令自动热编译
-
GoStub的作用是什么?
-
GoStub也是一种测试框架:
-
GoStub 可以对全局变量打桩
-
GoStub 可以对函数打桩
-
GoStub 不可以对类的成员方法打桩
-
GoStub 可以打动态桩,比如对一个函数打桩后,多次调用该函数会有不同的行为
-
-
6.参考
- Go 语言笔试面试题汇总 | 极客面试 | 极客兔兔 (geektutu.com)
- 极客时间-轻松学习,高效学习-极客邦 (geekbang.org)
整理不易,给个赞吧!~~~
相关文章:
Go基础面经大全(持续补充中)
Go基础 1. 基础特性 Go的优势 天生支持并发,性能高。 单一的标准代码格式,比其他语言更具可读性。 自动垃圾收集机制比Java和Python更有效,因为它与程序同时执行。 Go数据类型 int, string, float, bool, array, slice, map, channel, p…...
uniapp heckbox-group实现多选
文章目录 html 代码JS 代码 混了业务逻辑,谨慎观看 html 代码 <view><!--可滚动视图区域。用于区域滚动 --><scroll-view :style"{ height: clientHeight px }" :scroll-top"scrollTop" scroll-y"true"scrolltouppe…...
读懂:“消费报销”模式新零售打法,适用连锁门店加盟的营销方案
读懂:“消费报销”模式新零售打法,适用连锁门店加盟的营销方案 引言:2023年的双十一已经落下帷幕,作为每年的经典电商促销节,今年已是第15个年头,但是今年各大电商平台却都是非常默契的,没有公布…...
一个基本的http客户端
高可用 客户端 1. httpClient.h #include <iostream> #include <string> #include <functional>class HttpClient { public:HttpClient(std::string url) : url_(url), port_(0) {}int write_http(const std::string &method, const std::string &…...
html-网站菜单-点击菜单展开相应的导航栏,加减号可切换
一、效果图 1.点击显示菜单栏,点击x号关闭; 2.点击一级菜单,展开显示二级,并且加号变为减号; 3.点击其他一级导航,自动收起展开的导航。 二、代码实现 <!DOCTYPE html> <html><head>&…...
2.FastRunner定时任务Celery+RabbitMQ
注意:celery版本和Python冲突问题 不能用高版本Python 用3.5以下,因为项目的celery用的django-celery 3.2.2 python3.7 async关键字 冲突版本 celery3.x方案一: celery3.xpython3.6方案二 : celery4.xpython3.7 解决celery执…...
vb.net 实时监控双门双向门禁控制板源代码
本示例使用设备介绍:实时网络双门双向门禁控制板可二次编程控制网络继电器远程开关-淘宝网 (taobao.com) Imports System.Net.Sockets Imports System.Net Imports System.Text Imports System.ThreadingImports System.Net.NetworkInformation Imports System.Man…...
文具办公产品展示预约小程序的作用如何
从整体来看,文具办公品牌/门店的生意来源于线下自然流量或线上自营商城/入驻第三方商城的的流量,线上多数情况都是以直接销售配送为主,但其实对文具品牌/门店而言还有信息展示、服务预约、在线咨询、产品介绍等需求。 虽然小区周边的消费者需…...
渗透测试流程是什么?7个步骤给你讲清楚!
在学习渗透测试之初,有必要先系统了解一下它的流程,静下心来阅读一下,树立一个全局观,一步一步去建设并完善自己的专业领域,最终实现从懵逼到牛逼的华丽转变。渗透测试是通过模拟恶意黑客的攻击方法,同时也…...
如何解决网站被攻击的问题:企业网络攻防的关键路径
在当今数字化时代,企业面临着不断升级的网络威胁,网站遭受攻击的风险也与日俱增。解决网站被攻击的问题对企业发展至关重要,不仅关系到企业的信息安全,也直接影响到企业的声誉和利益。从企业发展的角度出发,我们将探讨…...
大健康产业的先行者「完美公司」携手企企通,推进企业采购供应链数字化进程
随着中国经济持续向好,消费升级和美妆步骤增加,美妆和个人护理产品已逐渐成为中国消费者的日用消费品,推动了护肤品和化妆品的销售额增速均超过10%,成为中国整个快速消费品市场中的一颗亮眼明珠。 据国家统计局数据显示࿰…...
在windows Server安装Let‘s Encrypt的SSL证书
1、到官网(https://certbot.eff.org/instructions?wswebproduct&oswindows)下载 certbot客户端。 2、安装客户端(全部默认安装即可) 3、暂停IIS中的网站 开始菜单中找到并运行“Certbot”,输入指令: …...
GPT实战系列-P-Tuning本地化训练ChatGLM2等LLM模型,到底做了什么?(二)
GPT实战系列-如何使用P-Tuning本地化训练ChatGLM2等LLM模型?(二) 文章目录 GPT实战系列-1.训练参数配置传递2.训练前准备3.训练参数配置4.训练对象,seq2seq训练5.执行训练6.训练模型评估依赖数据集的预处理 P-Tuning v2 将 ChatGLM2-6B 模型需要微调的参…...
Python3.7+PyQt5 pyuic5将.ui文件转换为.py文件、Python读取配置文件、生成日志
1.实际开发项目时,是使用Qt Designer来设计UI界面,得到一个.ui的文件,然后利用PyQt5安装时自带的工具pyuic5将.ui文件转换为.py文件: pyuic5 -o mywindow.py mywindow.ui #先是py文件名,再是ui文件名样式图 QT5 UI&am…...
使用 VPN ,一定要知道的几个真相!
你们好,我的网工朋友。 今天想和你聊聊VPN。在VPN出现之前,企业分支之间的数据传输只能依靠现有物理网络(例如Internet)。 但由于Internet中存在多种不安全因素,报文容易被网络中的黑客窃取或篡改,最终造…...
数电实验-----实现74LS153芯片扩展为8选1时间选择器以及应用(Quartus II )
目录 一、74LS153芯片介绍 管脚图 功能表 二、4选1选择器扩展为8选1选择器 1.扩展原理 2.电路图连接(Quartus II ) 3.仿真结果 三、8选1选择器的应用 1.三变量表决器 2.奇偶校验电路 一、74LS153芯片介绍 74ls153芯片是属于四选一选择器的芯片。…...
如何实现MATLAB与Simulink的数据交互
参考链接:如何实现MATLAB与Simulink的数据交互 MATLAB是一款强大的数学计算软件,Simulink则是一种基于模型的多域仿真平台,常用于工程和科学领域中的系统设计、控制设计和信号处理等方面。MATLAB和Simulink都是MathWorks公司的产品࿰…...
【数据结构】归并排序
👦个人主页:Weraphael ✍🏻作者简介:目前正在学习c和算法 ✈️专栏:数据结构 🐋 希望大家多多支持,咱一起进步!😁 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你…...
数字引领,智慧赋能|袋鼠云与易知微共同亮相2023智慧港口大会
2023年10月19日,由中国港口协会、中国交通通信信息中心、天津港(集团)有限公司主办,中国港口协会智慧港口专业委员会、《港口科技》杂志社等单位承办的以“数字引领 智慧赋能”为主题的“2023智慧港口大会”在天津顺利召开。 袋鼠…...
星火模型(Spark)的langchain 实现
星火模型的langchain实现 测试已通过,希望有所帮助。 使用前请先安装环境: pip install githttps://github.com/shell-nlp/spark-ai-python.git注意: 一定要使用上面方式安装spark库,因对官方的库做了改动。官方的库已经长时间不…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
