深夜禁用直播app软件,企业网站设计seo,湖南建设人社网,网站建设服务费标准Go基础
1. 基础特性 Go的优势 天生支持并发#xff0c;性能高。 单一的标准代码格式#xff0c;比其他语言更具可读性。 自动垃圾收集机制比Java和Python更有效#xff0c;因为它与程序同时执行。 Go数据类型 int, string, float, bool, array, slice, map, channel, p…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 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语言))) // 4Go 支持默认参数或可选参数吗 Go 语言不支持可选参数python 支持也不支持方法重载java支持。 如何交换 2 个变量的值 a, b : A, B
a, b b, a
fmt.Println(a, b) // B AGo 语言 tag 的用处 tag 可以理解为 struct 字段的注解可以用来定义字段的一个或多个属性。框架/工具可以通过反射获取到某个字段定义的属性采取相应的处理方式。tag 丰富了代码的语义增强了灵活性。 例如 package mainimport fmt
import encoding/jsontype Stu struct {Name string json:stu_nameID string json:stu_idAge 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 表示集合时只关注 keyvalue 可以使用 struct{} 作为占位符。如果使用其他类型作为占位符例如 intbool不仅浪费了内存而且容易引起歧义。 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 OutLIFO)的原则最后声明的 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、一个是bmapgo中也有一个哈希函数用来对map中的键生成哈希值hash结果的低位用于把k/v放到bmap数组中的哪个bmap中高位用于key的快速预览快速试错 go中的map如何扩容 翻倍扩容如果map中的键值对个数/桶的个数6.5就会引发翻倍扩容等量扩容当B15时如果溢出桶的个数2的B次方就会引发等量扩容当B15时如果溢出桶的个数2的15次方时就会引发等量扩容 go中map的查找 go中的map采用的是哈希查找表由哈希函数通过key和哈希因此计算出哈希值根据hamp中的B来确定放到哪个桶中如果B5那么就根据哈希值的后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 (fmtsynctime
)
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)numlock.Unlock()}}()for i : 0; i 100000; i{lock.Lock()//fmt.Println(num)numlock.Unlock()}fmt.Println(num)fmt.Println(time.Now().Sub(start))
}总结 如果对数据写的比较多使用Mutex同步锁/互斥锁性能更高如果对数据读的比较多使用RWMutex读写锁性能更高 goroutine案例两个goroutine一个负责输出数字另一个负责输出26个英文字母格式如下12ab34cd56ef78gh … yz package main
import (fmtsyncunicode/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 : abcdefghigklmnopqrstuvwxyzi : 0for {-chAlpha // 接到信号运行该goroutinefmt.Printf(%v, str[i:i2])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、recvxrecvx指向最早被读取的位置sendx指向再次写入时插入的位置 go语言中channel通道有什么特点需要注意什么 总结 给一个nil channel发送数据时会一直堵塞从一个nil channel接收数据时会一直阻塞给一个已关闭的channel发送数据时会panic从一个已关闭的channel中读取数据时如果channel为空则返回通道中类型的零值 案例 package main
import (fmtsync
)
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)
整理不易给个赞吧~~~