• 【狂刷面试题】GO常见面试题汇总


     

    先给大家推荐一个实用面试题库

     1、前端面试题库 (面试必备)            推荐:★★★★★

    地址:前端面试题库

    2、前端技术导航大全      推荐:★★★★★

    地址:前端技术导航大全

    3、开发者颜色值转换工具   推荐:★★★★★

    地址 :开发者颜色值转换工具

    携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

    今年互联网的就业环境真的好糟糕啊,好多朋友被优化。

    我们平常在工作中除了撸好代码,跑通项目之外,还要注意内外兼修。内功和招式都得练👌,才能应对突如其来的变故,顺利的拿到新的offer,不要问我怎么知道的。

    这个月我会整理分享一系列后端工程师求职面试相关的文章,知识脉络图如下:

    • JAVA/GO/PHP 面试常问的知识点
    • DB:MySql PgSql
    • Cache: Redis MemCache MongoDB
    • 数据结构
    • 算法
    • 微服务&高并发
    • 流媒体
    • WEB3.0
    • 源码分析

    通过这一系列的文章,大家不仅能温习和梳理后端开发相关的知识点,也可以了解目前的市场环境对服务端开发,尤其是对Go开发工程师的岗位要求,需要掌握哪些核心技术。

    值类型和引用类型

    值类型有哪些?

    基本数据类型都是值类型,包括:int系列、float系列、bool、字符串、数组、结构体struct。

    引用类型有哪些?

    指针、切片slice、接口interface、管道channel

    值类型和引用类型的区别?

    1. 值类型在内存中存储的是值本身,而引用类型在内存中存储的是值的内存地址。
    2. 值类型内存通常在栈中分配,引用类型内存通常在堆中分配。

    垃圾回收

    引用类型的内存在堆中分配,当没有任何变量引用堆中的内存地址时,该内存地址对应的数据存储空间就变成了垃圾,就会被GO语言的GC回收。

    一图胜千言

    堆和栈

    在Go中,栈的内存是由编译器自动进行分配和释放,栈区往往存储着函数参数、局部变量和调用函数帧,它们随着函数的创建而分配,函数的退出而销毁。

    一个goroutine对应一个栈,栈是调用栈(call stack)的简称。一个栈通常又包含了许多栈帧(stack frame),它描述的是函数之间的调用关系,每一帧对应一次尚未返回的函数调用,它本身也是以栈形式存放数据。

    与栈不同的是,应用程序在运行时只会存在一个堆。狭隘地说,内存管理只是针对堆内存而言的。程序在运行期间可以主动从堆上申请内存,这些内存通过Go的内存分配器分配,并由垃圾收集器回收。

    切片

    比较

    切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。

    切片唯一合法的比较操作是和nil比较。

    比较的详解

    要检查切片是否为空,应该使用

    len(s) == 0
    

    来判断,而不应该使用

    s == nil
    

    来判断。

    原因是:一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil。

    我们通过下面的示例就很好理解了:

    1. var s1 []int //len(s1)=0;cap(s1)=0;s1==nil
    2. s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil
    3. s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

    所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。

    其根本原因在于后面两种初始化方式已经给切片分配了空间,所以就算切片为空,也不等于nil。但是len(s) == 0成立,则切片一定为空。

    注意:在go中 var是声明关键字,不会开辟内存空间;使用 := 或者 make 关键字进行初始化时才会开辟内存空间。

    深拷贝和浅拷贝

    操作对象

    深拷贝和浅拷贝操作的对象都是Go语言中的引用类型

    区别如下:

    引用类型的特点是在内存中存储的是其他值的内存地址;而值类型在内存中存储的是真实的值。

    我们在go语言中通过 := 赋值引用类型就是 浅拷贝,即拷贝的是内存地址,两个变量对应的是同一个内存地址对应的同一个值。

    1. a := []string{1,2,3}
    2. b := a

    如果我们通过copy()函数进行赋值,就是深拷贝,赋值的是真实的值,而非内存地址,会在内存中开启新的内存空间。

    举例如下:

    1. a := []string{1,2,3}
    2. b := make([]string,len(a),cap(a))
    3. copy(b,a)

    new和make

    new

    new是GO语言一个内置的函数,它的函数签名如下:

    func new(Type) *Type
    

    特点

    • Type表示类型,new函数只接受一个参数,这个参数是一个类型
    • *Type表示类型指针,new函数返回一个指向该类型内存地址的指针。

    new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。

    举个例子:

    1. func main() {
    2. a := new(int)
    3. b := new(bool)
    4. fmt.Printf("%T\n", a) // *int
    5. fmt.Printf("%T\n", b) // *bool
    6. fmt.Println(*a) // 0
    7. fmt.Println(*b) // false
    8. }

    使用技巧

    var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。

    应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:

    1. func main() {
    2. var a *int
    3. a = new(int)
    4. *a = 10
    5. fmt.Println(*a)
    6. }

    make

    make也是用于内存分配的,区别于new,它只用于slice、map以及channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型(指针类型),所以就没有必要返回他们的指针了。

    make函数的函数签名

    func make(t Type, size ...IntegerType) Type
    

    特点

    make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。

    使用技巧

    var b map[string]int这段代码,只是声明变量b是一个map类型的变量,需要像下面的示例代码一样使用make函数进行初始化操作之后,才能对其进行键值对赋值:

    1. func main() {
    2. var b map[string]int
    3. b = make(map[string]int, 10)
    4. b["分数"] = 100
    5. fmt.Println(b)
    6. }

    总结:new与make的区别

    1. 二者都是用来做内存分配的。

    2. make只用于slice、map以及channel的初始化,返回的是类型本身(类型本身就是引用类型(指针类型));

    3. 而new用于内存分配时,在内存中存储的是对应类型的型零值(比如0,false),返回的是该类型的指针类型。

    go的map实现排序

    我们知道go语言的map类型底层是有hash实现的,是无序的,不支持排序。

    如果我们的数据使用map类型存储,如何实现排序呢?

    解决思路

    排序map的key,再根据排序后的key遍历输出map即可。

    代码实现:

    1. package main
    2. import (
    3. "fmt"
    4. "math/rand"
    5. "sort"
    6. "time"
    7. )
    8. func main() {
    9. rand.Seed(time.Now().UnixNano()) //初始化随机数种子
    10. var scoreMap = make(map[string]int, 30)
    11. for i := 0; i < 30; i++ {
    12. key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
    13. value := rand.Intn(30) //生成0~50的随机整数
    14. scoreMap[key] = value
    15. }
    16. //取出map中的所有key存入切片keys
    17. var keys = make([]string, 0, 30)
    18. for key := range scoreMap {
    19. keys = append(keys, key)
    20. }
    21. //对切片进行排序
    22. sort.Strings(keys)
    23. //按照排序后的key遍历map
    24. for _, key := range keys {
    25. fmt.Println(key, scoreMap[key])
    26. }
    27. }

    运行结果

    逃逸分析

    我们在上面有提到堆和栈的概念,在go中逃逸分析是一个重要的概念,需要大家理解。

    正如我们上面提到的,内存分配既可以分配到堆中,也可以分配到栈中。

    那么什么样的数据会分配到栈中,什么数据又会被分配到堆中呢?GO语言是如何进行内存分配的呢?其设计初衷和实现原理是什么呢?

    内存管理

    内存管理主要包括两个动作:分配与释放。逃逸分析就是服务于内存分配,为了更好理解逃逸分析,我们再来回顾一下堆栈的特点:

    在Go中,栈的内存是由编译器自动进行分配和释放,栈区往往存储着函数参数、局部变量和调用函数帧,它们随着函数的创建而分配,函数的退出而销毁。一个goroutine对应一个栈,栈是调用栈(call stack)的简称。一个栈通常又包含了许多栈帧(stack frame),它描述的是函数之间的调用关系,每一帧对应一次尚未返回的函数调用,它本身也是以栈形式存放数据。

    与栈不同的是,应用程序在运行时只会存在一个堆。我们可以简单理解为:我们在GO开发过程中要考虑的内存管理只是针对堆内存而言的。 程序在运行期间可以主动从堆上申请内存,这些内存通过Go的内存分配器分配,并由垃圾收集器回收。

    堆和栈的对比

    加锁

    • 栈是每个goroutine独有的,这就意味着栈上的内存操作是不需要加锁的。

    • 堆上的内存,有时需要加锁防止多线程冲突(为什么要说有时呢?因为Go的内存分配策略学习了TCMalloc的线程缓存思想,他为每个处理器P分配了一个mcache,从mcache分配内存也是无锁的)。

    性能

    • 堆内存管理 性能差:对于程序堆上的内存回收,还需要通过标记清除阶段,例如Go采用的三色标记法。
    • 栈内存管理 性能好:栈上的内存,它的分配与释放非常廉价。简单地说,它只需要两个CPU指令:一个是分配入栈,另外一个是栈内释放。只需要借助于栈相关寄存器即可完成。

    缓存策略

    栈内存能更好地利用CPU的缓存策略,因为栈空间相较于堆来说是更连续的。

    逃逸分析

    上面说了这么多堆和栈的知识点,目的是为了让大家更好的理解逃逸分析

    正如我们知道的,相比于把内存分配到堆中,分配到栈中优势更明显。Go语言也是这么做的:Go编译器会尽可能将变量分配到到栈上。但是,当编译器无法证明函数返回后,该变量没有被引用,那么编译器就必须在堆上分配该变量,以此避免悬挂指针(dangling pointer)。另外,如果局部变量非常大,也会将其分配在堆上。

    Go是如何确定内存是分配到栈上还是堆上的呢?

    答案就是:逃逸分析。编译器通过逃逸分析技术去选择堆或者栈,逃逸分析的基本思想如下:检查变量的生命周期是否是完全可知的,如果通过检查,则在栈上分配。否则,就是所谓的逃逸,必须在堆上进行分配。

    逃逸分析原则

    Go语言虽然没有明确说明逃逸分析规则,但是有以下几点准则,是可以参考的。

    • 逃逸分析是在编译器完成的,这是不同于jvm的运行时逃逸分析;
    • 如果变量在函数外部没有引用,则优先放到栈中;
    • 如果变量在函数外部存在引用,则必定放在堆中;

    最后给大家推荐一个实用面试题库

     1、前端面试题库 (面试必备)            推荐:★★★★★

    地址:前端面试题库

    2、前端技术导航大全      推荐:★★★★★

    地址:前端技术导航大全

    3、开发者颜色值转换工具   推荐:★★★★★

    地址 :开发者颜色值转换工具

  • 相关阅读:
    CSS基础篇---01选择器、字体与文本样式
    ArcGIS Pro SDK (三)Addin控件 1 按钮类
    Spark性能调优案例-优化spark估计表大小失败 和 小表关联 走 broadcast join
    Java项目:JSP鲜花商城网站系统
    cuML机器学习GPU库
    String的理解
    多旋翼无人机仿真 rotors_simulator:基于PID控制器的位置控制---水平位置控制
    Sui主网升级至V1.10.1版本
    kile5上的一栏快捷键消失了,我手贱删了
    WMS仓储管理系统:从集成到面向未来的策略
  • 原文地址:https://blog.csdn.net/weixin_42981560/article/details/126343593