Golang面试题集锦

下面的程序的输出

第1题

package main

func f1(){
	defer println("f1 begin")
	f2()
	defer println("f1 end")
}

func f2(){
	defer println("f2 begin")
	f3()
	defer println("f2 end")
}

func f3(){
	defer println("f3 begin")
	panic(0)
	defer println("f3 end")
}
func main(){
	f1()
}
  • 输出:
    f3 begin
    f2 begin
    f1 begin
    panic: 0
  • 如果没有panic:
    f3 end
    f3 begin
    f2 end
    f2 begin
    f1 end
    f1 begin

第2题

package main

import "fmt"

type Object interface {}

type Reader interface {
	ReadToBuffer()
	Buffer()string
}

type read struct {
	buffer string
}

func (r read) ReadToBuffer(){
	r.buffer = "hello"
}

func (r read) Buffer() string{
	return r.buffer
}

func main(){
	var r = &read{}
	doSomething(r)

	r.ReadToBuffer()
	buf := r.Buffer()
	fmt.Println("main,buf = ", buf)
}

func doSomething(obj Object){
	reader,ok := obj.(Reader)
	if !ok{
		panic("invalid type")
	}
	reader.ReadToBuffer()
	buf := reader.Buffer()
	fmt.Println("doSomething,buf = ", buf)
}

输出:
doSomething,buf =
main,buf =

原因
方法的receiver不是引用,所以这里只是copy
How to set and get fields in struct’s method


第3题

和第2题结合起来看,来源于上一题的链接,这里receiver是指针,虽然传入的不是指针,但是golang会自动转换

package main

import "fmt"

type Foo struct {
    name string
}

// SetName receives a pointer to Foo so it can modify it.
func (f *Foo) SetName(name string) {
    f.name = name
}

// Name receives a copy of Foo since it doesn't need to modify it.
func (f Foo) Name() string {
    return f.name
}

func main() {
    // Notice the Foo{}. The new(Foo) was just a syntactic sugar for &Foo{}
    // and we don't need a pointer to the Foo, so I replaced it.
    // Not relevant to the problem, though.
    p := Foo{}
    p.SetName("Abc")
    name := p.Name()
    fmt.Println(name)
}

输出
Abc


第4题

package main

import (
	"fmt"
	"unsafe"
)

func main(){
	s := make([]byte,200)
	ptr := unsafe.Pointer(&s[0])
	fmt.Printf("%T\n",s)
	fmt.Printf("%T\n",ptr)

	s1 := ((*[1<<10]byte)(ptr))[:200]
	fmt.Printf("%T,%d,%d\n",s1,len(s1),cap(s1))

	var sl = struct {
		addr uintptr
		len int
		cap int
	}{uintptr(ptr),200,512}

	s2 := *(*[]byte)(unsafe.Pointer(&sl))
	fmt.Printf("%T,%d,%d\n",s2,len(s2),cap(s2))
}

输出:
[]uint8
unsafe.Pointer
[]uint8,200,1024
[]uint8,200,512


第5题

package main

import (
	"fmt"
	"time"
)

var (
	sem = make(chan int,5)
)

type Request int

func init(){
	for i:=0;i<5;i++{
		sem<-1
	}
}

func handle(r Request){
	<-sem
	process(r)
	sem<-1
}

func process(r Request){
	fmt.Println("process:",r)
}

func serve(queue chan Request){
	for{
		req,ok := <-queue
		if ok{
			go handle(req)
		}else{
			break
		}
	}
	time.Sleep(2*time.Second)
}

func main(){
	queue := make(chan Request)
	go func() {
		for i:=0;i<5;i++{
			time.Sleep(1*time.Second)
			queue <- Request(i)
		}
		close(queue)
	}()
	serve(queue)
}

输出:
process: 0
process: 1
process: 2
process: 3
process: 4

这个题不知道要考啥…


第6题

package main

import "fmt"

func main(){
	fmt.Println("f():", f())
	fmt.Println("f1():", f1())
	fmt.Println("f2():", f2())
}

func f()(res int){
	defer func(){
		res++
	}()
	return 0
}

func f1()(res int){
	t:=5
	defer func(){
		t += 5
	}()
	return t
}

func f2()(res int){
	defer func(res int){
		res += 5
	}(res)
	return 1
}

输出:
f(): 1
f1(): 5
f2(): 1


第7题

package main
import "fmt"
type People struct{}

func (p *People) ShowA() {
    fmt.Println("showA")
    p.ShowB()
}
func (p *People) ShowB() {
    fmt.Println("showB")
}

type Teacher struct {
    People
}

func (t *Teacher) ShowB() {
    fmt.Println("teacher showB")
}

func main() {
    t := Teacher{}
    t.ShowA()
}

输出:
showA
showB


第8题

package main

import (
    "fmt"
)

func main() {
    const (
        a = 0.1
        b = 0.2
        c = 0.3
    )
    fmt.Println(a+b == c)
	var (
		a1 = 0.1
		b1 = 0.2
		c1 = 0.3
	)
	fmt.Println(a1+b1 == c1)
}

输出
true
false

原因
In a related way, floating-point constants may have very high precision, so that arithmetic involving them is more accurate. The constants defined in the math package are given with many more digits than are available in a float64.
来源:The Go BlogConstants


第9题

package main
import (
    "fmt"
)
func main() {
	b := []int{1}
	notChange(b)
	fmt.Printf("经历过notChange函数后b = %+v\n",b)
	b = []int{1}
	change(b)
	fmt.Printf("经历过change函数后b = %+v\n",b)
}

func notChange(s []int){
	s = append(s,3)
	fmt.Println("in notChange, after append ", s)
	s = []int{4,5}
	fmt.Println("in notChange, after assign ", s)
}

func change(s []int){
	s[0]=100
	fmt.Printf("in change, s = %+v\n", s)
}

输出:
in notChange, after append [1 3]
in notChange, after assign [4 5]
经历过notChange函数后b = [1]
in change, s = [100]
经历过change函数后b = [100]

原因:
append和直接赋值导致底层数组改变,所以没有改变实参


第10题

package main

import "fmt"

func main() {
	f1,f2:=A(10)
	f1()
	f2()
}

func A(x int)(func(),func()){
	return func(){
		fmt.Println("1",x)
		x+=10
		
	},func(){
		fmt.Println("2",x)
	}
}

输出:
1 10
2 20

原因:
就是闭包


第11题

package main

func main() {
	t()
}

func t()(result []bool){
	result[0]=false // panic
	// result = append(result,false) // 
	return result
}

原因:
返回命名参数对于slice来说在函数体里面是不用make,但是像第一行那样是不行的,第二行是可以的

第12题

package main
import "fmt"
func main() {
    var a interface{}
    fmt.Printf("a == nil is %t\n", a == nil)  
    var b interface{}
    var p *int = nil
    b = p
    fmt.Printf("b == nil is %t\n", b == nil)
}
/*
输出:
a == nil is true
b == nil is false
*/

第13题

package main

import (
	"fmt"
)

const (
	a=iota
	b
	c=20
	d // d=iota
	e
	f
)

func main() {
	fmt.Println(a,b,c,d,e,f) 
}

输出: 0 1 20 20 20 20
d=iota, 输出: 0 1 20 3 4 5


第14题

package main

import "fmt"

func main() {
	a:=[]int{1,2,3,4}
	m:=make(map[int]*int)
	for index,value:=range a{
		m[index]=&value
	}
	for k,v:=range m{
		fmt.Printf("%v->%v\n",k,*v)
	}
}
  • 输出:
    1->4
    2->4
    3->4
    0->4
  • 解析: https://www.jianshu.com/p/b64221beb2f2
  • 另一个类似的
    func main() {
        arr := []int{1, 2, 3}
        newArr := []*int{}
        for _, v := range arr {
            newArr = append(newArr, &v)
        }
        for _, v := range newArr {
            fmt.Println(*v)
        }
    }
    

第14题

package main

import "fmt"

import "strings"
func main() {
    //  If s does not contain sep and sep is not empty, Split returns a slice of length 1 whose only element is s
	ret := strings.Split("", " ")
    fmt.Println(len(ret), ret)  
}

package main
import "strings"
import "fmt"
func main() {
    a:=strings.Split("abc", "d")
	fmt.Println(len(a),a) // 1 [abc],
}

strings.Split an empty string should get a zero length slice, but the return slice’s len is 1


资料集合

其他面试题

channle

Golang面试题

50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs

Golang 新手可能会踩的 50 个坑

如果让你来实现channel,你要怎么做

http协议是怎么划分边界的

io多路复用

图解 I/O 多路复用

  • 定义:
    IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操 作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程
    
  • 又叫事件驱动
  • 三种方式: select, poll, epoll
  • 区别:
    • 出现顺序: select,poll,epoll
    • select的缺点:
      1. 单个进程所打开的FD是有限制的,通过FD_SETSIZE设置,默认1024
      2. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
      3. 对socket扫描时是线性扫描,采用轮询的方法,效率较低(高并发时)
      
    • poll对select的改进:
      poll与select相比,只是没有fd的限制,其它基本一样
      
    • epoll
      • 只能工作在linux下
      • 不是轮询,是事件回调,O(1)
      • [Windows IOCP模型与Linux EPOLL模块之比较](Windows IOCP模型与Linux EPOLL模块之比较)
  • 参考资料:

几种常见的gc算法

并发和并行

  • 并发: 在操作系统中,某一时间段,几个程序在同一个CPU上运行,但在任意一个时间点上,只有一个程序在CPU上运行
  • 并行: 当操作系统有多个CPU时,一个CPU处理A线程,另一个CPU处理B线程,两个线程互相不抢占CPU资源,可以同时进行,这种方式成为并行
  • 参考:【面试高频问题】线程、进程、协程

进程,线程,协程

  • 对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。
  • 线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。
  • 进程拥有代码和打开的文件资源、数据资源、独立的内存空间
  • 直白地讲,进程就是应用程序的启动实例
  • 无论进程还是线程,都是由操作系统所管理的。
  • 协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程
  • 协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)
  • 协程的开销远远小于线程的开销
  • 参考资料:

goalng的sync.Mutex和sync.RWMutex

  • sync.RWMutex: 读写互斥,读读共享,写写互斥。
  • sync.Mutex: 全部互斥

golang的并发控制

golang的gc算法

  • 三色标记+写屏障
    • 三色标记
      程序创建的对象都标记为白色。
      gc开始:扫描所有可到达的对象,标记为灰色
      从灰色对象中找到其引用对象标记为灰色,把灰色对象本身标记为黑色
      监视对象中的内存修改,并持续上一步的操作,直到灰色标记的对象不存在
      此时只剩白色和黑色,清理白色
      
    • 写屏障
      • 该屏障之前的写操作和之后的写操作相比,先被系统其它组件感知。
      • 通俗的讲:就是在gc跑的过程中,可以监控对象的内存修改,并对对象进行重新标记。(实际上也是超短暂的stw,然后对对象进行标记)
        • gc一旦开始,无论是创建对象还是对象的引用改变,都会先变为灰色

golang的gmp模型

全局队列里的goroutine什么时候得到执行
  • 线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列

golang的函数是传值还是传指针

httprouter的底层结构

非golang面试

TCP主动断开连接会出现什么状态

交换机和路由器的区别

  • 工作层次不同:
    交换机主要工作在数据链路层(第二层)
    路由器工作在网络层(第三层)。
  • 转发依据不同:
    交换机转发所依据的对象时:MAC地址。(物理地址)
    路由转发所依据的对象是:IP地址。(网络地址)
  • 主要功能不同:
    交换机主要用于组建局域网,
    而路由主要功能是将由交换机组好的局域网相互连接起来,或者接入Internet。
    交换机能做的,路由都能做。
    交换机不能分割广播域,路由可以。
    路由还可以提供防火墙的功能。
    路由配置比交换机复杂

OSI七层模型

  • 速记
    物,数,网,传,会,表,应
  • 具体
    第7层 应用层
    第6层 表示层
    第5层 会话层
    第4层 传输层
    第3层 网络层
    第2层 数据链路层
    第1层 物理层

某个公司的电话面试题

  1. golang如何检查datarace的数据竞争
    Use -race to enable the built-in data race detector.

  2. 如何检查死锁

    • 检查业务,确定大概的死锁位置
    • 靠人工走读代码
  3. 如果需要作一个整数类型的计数器?

    • 如果用channel好还是你说的用锁好一点
    • channel的底层实现
  4. 实现一个客户端ping一百台服务器,最大10个并发数去ping

  5. 多线程不加锁的情况下,同一个线程只读写一个key,每个线程读写不同的key,会不会造成data race

  6. 多个线程去调同一个函数,这个函数可能是耗时的操作,第一个线程去调用了,其他线程就不要去调用,其他线程被阻塞等待,直接拿到结果,怎么实现

  7. 如果一个函数传递的是非指针的互斥锁,会有什么样的结果

  8. 如果从一个map里随机抽取3个key,概率保持一样,要怎么做

  9. channel,2个goroutine同时发送和接收,会发生什么?

    一个协程给 channel 发消息,一个协程从 channel 接收,要求是这两个协程都准备好,才能完成收发操作。
    
    形象一点的例子就是,快递员给你送快递,你要去拿快递,通过沟通约定了时间地点,快递员得带着快递过去,你得过去拿,这样才能完成这个流程。
    
    而对于有缓冲的 channel,是不需要收发同步的,但是队列满了之后就和无缓冲的一样了。
    
    所以,同时收发会直接完成这一次消息传递。非同时收发,那么发送端会被阻塞,接收端也会被阻塞。
    
  10. map里的元素被delete后,map的内存的体积会不会立即减小

  11. 一个work server,每次收到一个请求,就创建一个协程去处理这个请求,发生slice,append这种情况,短时间请求特别多,这个服务器会发生什么情况

  12. 封装一个接口,接收2个参数,一个是名字一个是分数,要更新这个名字和分数,并返回其排名

  13. CDN缓存静态资源60秒,怎么在你的服务器上操作

  14. golang, windows跨平台编译linux,需要怎么操作

    • CGO_ENABLED=0
    • GOOS=linux
    • GOARCH=amd64
  15. 如果有用cgo要怎么跨平台编译

  16. 还是int的整形计数器,加一个方法, 等待计数器变成0,不会就阻塞等待,要怎么实现

MySQL面试题

https://dongzl.github.io/2020/03/15/12-MySQL-Master-Slave-Replication/index.html

主从之间如何复制的

https://cheng-dp.github.io/2019/05/06/mysql-binlog-replica/#%E5%A4%8D%E5%88%B6%E8%BF%87%E7%A8%8B

MyISAM和Innodb的区别

几种隔离级别

  • 读未提交
  • 读已提交
  • 可重复读
  • 串行化

脏读和幻读分别出现在哪几种隔离

binlog和redolog的作用分别是啥

  • MySQL日志系统:redo log、binlog、undo log 区别与作用
  • redo log是属于innoDB层面,binlog属于MySQL Server层面的,这样在数据库用别的存储引擎时可以达到一致性的要求。
  • redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
  • redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
  • binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。

binlog在MySQL的那一层

  • binlog是MySQL Server层记录的日志
  • MySQL分2层server层和引擎层
  • 引申问题: server层有哪些东西
    • 连接器
    • 查询缓存
    • 分析器
    • 优化器
    • 执行器

binlog的格式

MySQL explain里的Extra

MySQL里的最左前缀

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值