数组和切片的区别:
- 定义方式不一样, 但都只能存储一组相同类型的数据结构
- 初始化方式不一样,数组为定长,需要指定大小,切片比数组多一个容量(cap)属性;
- 在函数传递中,数组切片都是
值传递
golang 中 make 和 new 的区别?
- new分配内存返回指针,而make初始化slice、map和channel类型,返回类型本身。
- make 仅用于 slice、map和 channel 的初始化,返回值为类型本身,而不是指针.
- new一般用于string,int和数字分配内存.
uint 类型溢出问题
- 数据类型超过最大容量长度
例如: uint8是0-255,超过255就会发生内存溢出
rune类型
- byte类型等同于int8 用来处理ascii字符
- rune类型相当于int32 用来处理Unicode或者utf8
golang中的字符串底层实现是通过byte数组的,中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8
golang 中解析 tag 是怎么实现的?反射原理是什么?
- tag通过反射来解析
func TypeOf(i interface{}) Type {...}
func ValueOf(i interface{}) Value {...}
调用函数传入结构体时,应该传值还是指针? (Golang 都是传值)
- 首先,go里面没有引用类型,所有的函数传递都是值传递,但是像slice,map,channel由于其内置结构里存在指针,因此传递以上类型会被修改原先数据。
- 如果想要修改结构体,就传指针
- 如果不修改,传值
defer 的执行顺序
- 多个 defer 语句,遵从后进先出(Last In First Out,LIFO)的原则,最后声明的 defer 语句,最先得到执行(压入栈头)。
- defer 在 return 语句之后执行,但在函数退出之前,defer 可以修改返回值。
go defer,多个 defer 的顺序,defer 在什么时机会修改返回值?
- defer一般用于释放资源,关闭文件,关闭链接,释放锁,收尾工作,捕获panic等
- defer是顺序:return->return value->defer 所以defer可以修改最终返回值
- 有名返回值和指针在defer里修改时,会函数影响返回值
defer 底层数据结构和一些特性?
- 每个 defer 语句都对应一个_defer 实例,多个实例使用指针连接起来形成一个单连表,保存在 gotoutine 数据结构中,每次插入_defer 实>例,均插入到链表的头部,函数结束再一次从头部取出,从而形成后进先出的效果。申请资源后立即使用 defer 关闭资源是个好习惯。
defer 作用域
- defer 延迟调用时,需要保存函数指针和参数,因此链式调用的情况下,除了最后一个函数/方法外的函数/方法都会在调用时直接执行。
defer 语句执行时,会将需要延迟调用的函数和参数保存起来。
什么是
rune
类型
- ASCII 码只需要 7 bit 就可以完整地表示,但只能表示英文字母在内的128个字符,为了表示世界上大部分的文字系统,发明了 Unicode, 它是ASCII的超集,包含世界上书写系统中存在的所有字符,并为每个代码分配一个标准编号(称为Unicode CodePoint),在 Go 语言中称之为 rune,是 int32 类型的别名。
- Go 语言中,字符串的底层表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。
单引号,双引号,反引号的区别?
- 单引号,表示
byte类型
或rune类型
,对应uint8
和int32类型
,默认是 rune 类型。byte用来强调数据是raw data,而不是数字;而rune用来表示Unicode的code point。- 双引号,才是字符串,实际上是字符数组。可以用索引号访问某字节,也可以用len()函数来获取字符串所占的字节长度。
- 反引号,表示字符串字面量,但不支持任何转义序列。字面量 raw literal string 的意思是,你定义时写的啥样,它就啥样,你有换行,它就换行。你写转义字符,它也就展示转义字符。
go struct 能不能比较?
- 使用 reflect.DeepEqual 方法进行比较
字符串转成byte数组,会发生内存拷贝吗?
- 字符串转成切片,会产生拷贝。严格来说,只要是发生类型强转都会发生内存拷贝。
Go语言闭包
- 同一个外部调用者,再次调用时会带回上次返回的结果
可以让变量常驻内存,闭包函数不会被GC回收,同一个调用者的返回值会被保存起来,(本人怀疑)因为他转换成了函数。
如何高效地拼接字符串?
- Go 语言中字符串是只读的,也就意味着每次修改操作都会创建一个新的字符串。如果需要拼接多次,应使用 strings.Builder,最小化内存拷贝次数。
如何判断 map 中是否包含某个 key ?
- dict[“foo”] 有 2 个返回值,val 和 ok,如果 ok 等于 true,则说明 dict 包含 key “foo”,val 将被赋予 “foo” 对应的值。
例如:if _, ok := map[key]; ok { // 存在 }
如何交换 2 个变量的值?
- a, b = b, a
Go 语言 tag 的用处?
- tag 可以理解为 struct 字段的注解,可以用来定义字段的一个或多个属性。框架/工具可以通过反射获取到某个字段定义的属性,采取相应的处理方式。tag 丰富了代码的语义,增强了灵活性。
如何判断 2 个字符串切片(slice) 是相等的?
- go 语言中可以使用反射 reflect.DeepEqual(a, b) 判断 a、b 两个切片是否相等,但是通常不推荐这么做,使用反射非常影响性能。
- 通常采用的方式如下,遍历比较切片中的每一个元素(注意处理越界的情况)
字符串打印时,%v 和 %+v 的区别
- %v 和 %+v 都可以用来打印 struct 的值,区别在于 %v 仅打印各个字段的值,%+v 还会打印各个字段的名称。
但如果结构体定义了 String() 方法,%v 和 %+v 都会调用 String() 覆盖默认值。
Go 语言中如何表示枚举值(enums)
- 通常使用常量(const) 来表示枚举值。(iota)
空 struct{} 的用途
- 使用空结构体 struct{} 可以节省内存,一般作为占位符使用,表明这里并不需要一个值。
- 比如使用 map 表示集合时,只关注 key,value 可以使用 struct{} 作为占位符。如果使用其他类型作为占位符,例如 int,bool,不仅浪费了内存,而且容易引起歧义。
- 再比如,使用信道(channel)控制并发时,我们只是需要一个信号,但并不需要传递值,这个时候,也可以使用 struct{} 代替。
init() 函数是什么时候执行的?
- init() 函数是 Go 程序初始化的一部分。Go 程序初始化先于 main 函数,由 runtime 初始化每个导入的包,初始化顺序不是按照从上到下的导入顺序,而是按照解析的依赖关系,没有依赖的包最先初始化。
2.每个包首先初始化包作用域的常量和变量(常量优先于变量),然后执行包的 init() 函数。同一个包,甚至是同一个源文件可以有多个 init() 函数。init() 函数没有入参和返回值,不能被其他函数调用,同一个包内多个 init() 函数的执行顺序不作保证。
3.一句话总结: import –> const –> var –> init() –> main()
Go 语言的局部变量分配在栈上还是堆上?
- 由编译器决定。Go 语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有超出函数范围,就可以在栈上,反之则必须分配在堆上。
2 个 interface 可以比较吗?
- Go 语言中,interface 的内部实现包含了 2 个字段,类型 T 和 值 V,interface 可以使用 == 或 != 比较。2 个 interface 相等有以下 2 种情况
两个 interface 均等于 nil(此时 V 和 T 都处于 unset 状态)
类型 T 相同,且对应的值 V 相等。
两个 nil 可能不相等吗?
- 可能。
接口(interface) 是对非接口值(例如指针,struct等)的封装,内部实现包含 2 个字段,类型 T 和 值 V。一个接口等于 nil,当且仅当 T 和 V 处于 unset 状态(T=nil,V is unset)。
两个接口值比较时,会先比较 T,再比较 V。
接口值与非接口值比较时,会先将非接口值尝试转换为接口值,再比较。
函数返回局部变量的指针是否安全?
- 这在 Go 中是安全的,Go 编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上。
无缓冲的 channel 和 有缓冲的 channel 的区别?
- 对于无缓冲的 channel,发送方将阻塞该信道,直到接收方从该信道接收到数据为止,而接收方也将阻塞该信道,直到发送方将数据发送到该信道中为止。
对于有缓存的 channel,发送方在没有空插槽(缓冲区使用完)的情况下阻塞,而接收方在信道为空的情况下阻塞。
无缓冲的channel由于没有缓冲发送和接收需要同步.
有缓冲channel不要求发送和接收操作同步.
什么是协程泄露(Goroutine Leak)?
- 协程泄露是指协程创建后,长时间得不到释放,并且还在不断地创建新的协程,最终导致内存耗尽,程序崩溃。
常见的导致协程泄露的场景有以下几种:
缺少接收器,导致发送阻塞
缺少发送器,导致接收阻塞
死锁(dead lock)
两个或两个以上的协程在执行过程中,由于竞争资源或者由于彼此通信而造成阻塞,这种情况下,也会导致协程被阻塞,不能退出。
无限循环(infinite loops)
这个例子中,为了避免网络等问题,采用了无限重试的方式,发送 HTTP 请求,直到获取到数据。那如果 HTTP 服务宕机,永远不可达,导致协程不能退出,发生泄漏
for循环select时,如果通道已经关闭会怎么样?如果select中的case只有一个,又会怎么样?
- for循环select时,如果其中一个case通道已经关闭,则每次都会执行到这个case。
- 如果select里边只有一个case,而这个case被关闭了,则会出现死循环。
对已经关闭的的chan进行读写,会怎么样?为什么?
- 读已经关闭的 chan 能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。
如果 chan 关闭前,buffer 内有元素还未读 , 会正确读到 chan 内的值,且返回的第二个 bool 值(是否读成功)为 true。
如果 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个 bool 值一直为 false。
写已经关闭的 chan 会 panic
对未初始化的的chan进行读写,会怎么样?为什么?
- 读写未初始化的 chan 都会阻塞。
json包里使用的时候,结构体里的变量不加tag能不能正常转成json里的字段?
- 如果变量首字母小写,则为private。无论如何不能转,因为取不到反射信息。
如果变量首字母大写,则为public。不加tag,可以正常转为json里的字段,json内字段名跟结构体内字段原名一致。
加了tag,从struct转json的时候,json的字段名就是tag里的字段名,原字段名已经没用。
json包里使用的时候,会结构体里的字段边上加tag,有没有什么办法可以获取到这个tag的内容呢?
- tag信息可以通过反射(reflect包)内的方法获取。
Golang中除了加Mutex锁以外还有哪些方式安全读写共享变量?
- Golang中Goroutine 可以通过 Channel 进行安全读写共享变量,还可以通过原子性操作进行.
Go的GPM如何调度?
- 新创建的Goroutine会先存放在Global全局队列中,等待Go调度器进行调度,随后Goroutine被分配给其中的一个逻辑处理器P,并放到这个逻辑处理器对应的Local本地运行队列中,最终等待被逻辑处理器P执行即可。
在M与P绑定后,M会不断从P的Local队列中无锁地取出G,并切换到G的堆栈执行,当P的Local队列中没有G时,再从Global队列中获取一个G,当Global队列中也没有待运行的G时,则尝试从其它的P窃取部分G来执行相当于P之间的负载均衡。
Go的Struct能不能比较
- 相同struct类型的可以比较
- 不同struct类型的不可以比较,编译都不过,类型不匹配
Go的Slice如何扩容
- slice是 Go 中的一种基本的数据结构,使用这种结构可以用来管理数据集合。但是slice本身并不是动态数据或者数组指针。
- slice常见的操作有 reslice、append、copy。
Go中的map如何实现顺序读取
- Go中map如果要实现顺序读取的话,可以先把map中的key,通过sort包排序.例如sort.Strings(keys)
通过sort中的排序包进行对map中的key进行排序.
go map 的结构,扩容规则
Go语言中的map是一种哈希表的实现,用于存储键值对。map的结构由两个部分组成:存储指向哈希桶的指针和哈希桶数组。
当我们创建一个map时,Go会为它分配一定数量的初始哈希桶。随着元素的增加,当哈希桶的负载因子(即每个桶中的平均元素数)超过一定阈值时,map会自动进行扩容,以保持高效的性能。
Go值接收者和指针接收者的区别
- 值类型调用者
值接收者:方法会使用调用者的一个副本,类似于“传值”。
指针接收者:使用值的引用来调用方法。
指针类型调用者
值接收者:指针被引用为值
指针接收者:实际上也是“传值”,方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针
rpc、http、tcp的关系 ?
RPC(远程过程调用)和HTTP(超文本传输协议)是两种不同的通信协议
go的常见panic,有哪些panic不能被recover ?
go 各种类型的空值是什么?
设计一个 savego,安全地启动一个协程
如何等待多个协程执行完毕?用最简单的方式实现
http长链接和websocket有什么区别?
map 是线程安全的么?介绍一下线程安全的map
短链接能不能传递大文件?会有什么问题?
golang 生产者消费者,如何实现 channel 安全关闭?
go 一个协程中起另一个协程,子协程panic,主协程的 recover 是否能捕获?
mysql索引的结构,b树、b+树的区别和优点?如何降低B+树的高度?
阐述事务隔离级别?他们怎么解决脏读重复读和幻读的?
- mysql 可重复读没有彻底解决幻读,用间隙锁解决了当前读下的幻读,但如果没加间隙锁,只是依赖 mvcc,select 时可以不读到幻读,但 update/delete 的当前读状态下,就会出现幻读
- 解决的话,select 时手动for update成为当前读,加间隙锁,可以解决阻塞其他事务的插入行为,避免幻读。
package main
import (
"net/http"
"sync"
)
func main() {
wg := sync.WaitGroup{}
for true {
for i := 0; i < 1500; i++ {
wg.Add(1)
go func(i int) {
pdfUrl := "http://www..gov.cn/xxgk/fdzdgknr/zdmsxx/fp/201808/P020180807353570565427.pdf"
_, err := http.Get(pdfUrl)
if err != nil {
}
println(i)
wg.Done()
}(i)
}
wg.Wait()
}
}