值类型和引用类型

值类型有哪些?

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

引用类型有哪些?

切片slice、map、channel、指针、接口interface、

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

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

垃圾回收

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

堆和栈

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

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

切片

比较

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

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

比较的详解

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

len(s) == 0

来判断,而不应该使用

s == nil

来判断。

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

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

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

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

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

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

深拷贝和浅拷贝

操作对象

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

区别如下

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

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

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

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

举例如下:

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

new与make的区别

共同点:给变量分配内存

不同点:

1)作用变量类型不同:new给string,int和数组等值类型分配内存,make给切片,map,channel分配内存;

2)返回类型不一样:new返回指向变量的指针,make返回变量本身;

3)new 分配的空间被清零(比如0,false)。make 分配空间后,会进行初始化;

4) 字节的面试官还说了另外一个区别,就是分配的位置,在堆上还是在栈上?这块我比较模糊,大家可以自己探究下,我搜索出来的答案是golang会弱化分配的位置的概念,因为编译的时候会自动内存逃逸处理,懂的大佬帮忙补充下:make、new内存分配是在堆上还是在栈上?

new和make都在堆上分配内存 new 函数分配内存,make 函数初始化 https://www.cnblogs.com/chenpingzhao/p/9918062.html

go defer,多个 defer 的顺序,defer 在什么时机会修改返回值?

https://www.topgoer.cn/docs/golangxiuyang/golangxiuyang-1cmee0q64ij5p

作用:defer延迟函数,释放资源,收尾工作;如释放锁,关闭文件,关闭链接;捕获panic;

避坑指南:defer函数紧跟在资源打开后面,否则defer可能得不到执行,导致内存泄露。

多个 defer 调用顺序是 LIFO(后入先出),defer后的操作可以理解为压入栈中

defer,return,return value(函数返回值) 执行顺序:首先return,其次return value,最后defer。defer可以修改函数最终返回值,修改时机:有名返回值或者函数返回指针 参考:

【Golang】Go语言defer用法大总结(含return返回机制)_奶酪的博客-CSDN博客blog.csdn.net/Cassiezkq/article/details/108567205

有名返回值

func b() (i int) {     
    defer func() {         
        i++         
        fmt.Println("defer2:", i)     
    }()     
    defer func() {         
        i++         
        fmt.Println("defer1:", i)     
    }()     
    return i 
    //或者直接写成
    return 
} 
func main() {     
    fmt.Println("return:", b()) 
} 

打印结果:

defer1: 1
defer2: 2
return: 2

函数返回指针

func c() *int {     
    var i int     
    defer func() {         
        i++         
        fmt.Println("defer2:", i)     
    }()     
    defer func() {         
        i++         
        fmt.Println("defer1:", i)     
    }()     
    return &i 
} 
func main() {     
    fmt.Println("return:", *(c())) 
}

打印结果:

defer1: 1
defer2: 2
return: 2

Go的map如何实现排序

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

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

解决思路

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

代码实现

package main

import (
   "fmt"
   "math/rand"
   "sort"
   "time"
)

func main() {
   rand.Seed(time.Now().UnixNano()) //初始化随机数种子

   var scoreMap = make(map[string]int, 30)

   for i := 0; i < 30; i++ {
      key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
      value := rand.Intn(30)           //生成0~50的随机整数
      scoreMap[key] = value
   }
   //取出map中的所有key存入切片keys
   var keys = make([]string, 0, 30)
   for key := range scoreMap {
      keys = append(keys, key)
   }
   //对切片进行排序
   sort.Strings(keys)
   //按照排序后的key遍历map
   for _, key := range keys {
      fmt.Println(key, scoreMap[key])
   }
}

输出结果

stu00 25
stu01 13
stu02 11
stu03 20
stu04 19
stu05 4
stu06 15
stu07 8
stu08 14
stu09 12
stu10 7
stu11 27
stu12 1
stu13 14
stu14 10
stu15 5
stu16 23
stu17 29
stu18 8
stu19 5
stu20 5
stu21 3
stu22 7
stu23 16
stu24 4
stu25 8
stu26 15
stu27 13
stu28 22
stu29 17

golang map底层实现

map的底层是一个散列表,它是由hmap和bmap组成,
hmap管理着桶,包括桶的扩容以及溢出桶。
bmap管理着键值对,数据类型上相当于链表,每个节点由 tophash8个键值对溢出桶地址组成
tophash 相当于 key 的索引,
8个键值对 的存储方式是键与键存储在一起,值与值存储在一起,
溢出桶 是用来解决hash冲突的
map的存储过程是:key用hash函数计算出的值分出了高位的tophash和低位,高位用来定位key的位置,低位用来定位桶。

数组和切片的区别

数组:
数据是值类型
数组固定长度,数组长度是数组类型的一部分,所以[3]int和[4]int是两种不同的数组类型
赋值形式是值传递。
数组需要指定大小,不指定也会根据处初始化对的自动推算出大小,不可改变

切片:
数据是指针类型,
赋值类型是引用传递,
长度是可变的。
切片是轻量级的数据结构,三个属性,指针,长度,容量
不需要指定大小,它的底层是动态数组。
可以通过数组来初始化,也可以通过内置函数make()来初始化,初始化的时候len=cap,然后进行扩容

Redis变慢解决办法梳理

详解文档:
https://blog.csdn.net/skye_fly/article/details/119979126
https://blog.csdn.net/weixin_68009402/article/details/130806249

三次握手

第一次握手:客户端将标志位SYN置为1,随机产生一个值序列号seq=x,并将该数据包发送给服务端,客户端进入syn_sent状态,等待服务端确认。
第二次握手:服务端收到数据包后由标志位SYN=1,知道客户端在请求建立连接,服务端将标志位SYN和 ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,服务端进入syn_rcvd状态。
第三次握手:客户端收到确认后检查,如果正确则将标志位ACK为1,ack=y+1,并将该数据包发送给服务端,服务端进行检查如果正确则连接建立成功,客户端和服务端进入established状态,完成三次握手,随后客户端和服务端之间可以开始传输 数据了

四次挥手

第一次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入fin_wait_1状态。
第二次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1,服务端进入Close_wait状态。此时TCP连接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接收。
第三次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入Last_ack状态。
第四次挥手:客户端收到FIN后,客户端进入Time_wait状态,接着发送一个ACK给服务端,确认后,服务端进入Closed状态,完成四次挥手。

详解文档:
https://www.topgoer.cn/docs/interview/interview-1do5q31n4oj4i
https://blog.csdn.net/sq4521/article/details/90751241

参数文档:
作者:王中阳Go
链接:https://juejin.cn/post/7131717990558466062
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

作者:海马  创建时间:2023-11-21 12:08
最后编辑:海马  更新时间:2024-12-22 19:32