值类型和引用类型
值类型有哪些?
基本数据类型都是值类型,包括: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
成立,则切片一定为空。
注意:
在 go
中 var
是声明关键字,不会开辟内存空间;使用 :=
或者 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管理着键值对,数据类型上相当于链表,每个节点由 tophash
,8个键值对
和 溢出桶地址组成
。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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最后编辑:海马 更新时间:2024-12-22 19:32