1. golang语言中new和make的区别
在 Go 语言中,new 和 make 是用于创建不同类型的值的内置函数。它们有以下区别:
new 函数用于创建指向新分配的零值的指针。
它接受一个类型作为参数,并分配该类型的零值,并返回一个指向该值的指针。
new 函数返回的是指针类型,适用于创建指向结构体、基本类型(如整数、浮点数)等值的指针。
new 函数不会初始化内存,返回的指针指向的值将是该类型的零值。
以下是使用 new 函数的示例:
var p *int
p = new(int)
*p = 42
fmt.Println(*p) // 输出: 42
make 函数:
make 函数用于创建切片、映射和通道等引用类型的值。
它接受一个类型、长度和容量作为参数,并返回一个初始化后的引用类型的值。
make 函数返回的是引用类型的值,适用于创建切片、映射和通道等引用类型。
make 函数会初始化内部数据结构,并返回一个可用的引用类型的值。
以下是使用 make 函数的示例:
var s []int
s = make([]int, 3, 5)
s[0] = 1
s[1] = 2
s[2] = 3
fmt.Println(s) // 输出: [1 2 3]
总结:
new 用于创建指向零值的指针,适用于基本类型和结构体等值的指针。
make 用于创建引用类型(切片、映射、通道)的值,会初始化内部数据结构。
2 在计算机科学中,堆(Heap)和栈(Stack)是两种常见的内存管理方式,它们有以下区别:
内存结构:
栈:栈是一种线性数据结构,具有后进先出(LIFO)的特性。它在程序运行时使用固定的内存块,存储局部变量、函数调用和返回等信息。栈的大小在编译时就确定,并在函数的进入和退出时动态地分配和释放内存。
堆:堆是一种树状结构,用于动态分配和管理内存。堆的大小在程序运行时可以动态地增加或减少,由操作系统分配和管理。堆用于存储动态分配的对象、数据结构等,并且需要手动释放分配的内存。
分配方式:
栈:栈的内存分配是自动的,由编译器或运行时环境自动处理。每当进入一个新的函数时,栈会分配一定大小的内存用于存储局部变量和函数的上下文信息。当函数返回时,栈会自动释放分配的内存。
堆:堆的内存分配是手动控制的,程序员需要显式地请求内存分配,并在不再使用时手动释放内存。通常使用诸如 new、malloc 等函数来分配堆内存,并使用 delete、free 等函数来释放堆内存。
内存管理:
栈:栈的内存管理是自动的,由编译器或运行时环境负责。它的分配和释放过程是快速的,由于栈的内存空间是连续的,因此分配和释放内存只涉及栈指针的移动,效率较高。但栈的空间有限,一般较小。
堆:堆的内存管理是手动的,程序员需要负责分配和释放堆内存。堆的分配和释放过程相对较慢,由于堆是动态分配的,可能存在内存碎片问题。堆的空间较大,可以根据需要动态分配和释放内存。
生命周期:
栈:栈上的变量的生命周期是在其作用域结束时自动销毁,无需手动管理。栈上的局部变量的生命周期与函数的调用关系密切相关。
堆:堆上的对象的生命周期由程序员显式管理,需要手动分配和释放内存。堆上分配的对象可以在其不再被使用时手动释放,否则会造成内存泄漏。
3 uint类型介绍
uint 是 Go 语言中的一种无符号整数类型。它表示非负整数,没有符号位,可以表示正整数和零。
在 Go 语言中,uint 类型的大小取决于底层计算机体系结构的字长,即它的位数。在绝大多数情况下,uint 类型的大小为 32 位或 64 位。
具体而言,以下是 uint 类型及其大小的变体:
uint:根据底层计算机体系结构的字长,可能是 32 位或 64 位。
uint8:8 位无符号整数,取值范围为 0 到 255。
uint16:16 位无符号整数,取值范围为 0 到 65535。
uint32:32 位无符号整数,取值范围为 0 到 4294967295。
uint64:64 位无符号整数,取值范围为 0 到 18446744073709551615。
根据具体的需求,可以选择适当大小的 uint 类型来存储非负整数。例如,如果需要存储一个正整数,但不超过 65535,可以选择使用 uint16 类型,以节省内存空间。
4 go select机制
Go 语言中的 select 语句用于在多个通道上进行非阻塞的选择操作。它类似于其他编程语言中的 switch 语句,但是用于选择可读或可写的通道而不是选择不同的条件。
select 语句的语法如下:
select {
case <-channel1:
// 当 channel1 有数据可读时执行的代码
case channel2 <- data:
// 当 data 可以写入 channel2 时执行的代码
default:
// 当没有任何通道准备好时执行的代码
}
select 语句包含了一系列的 case 子句,每个 case 子句关联一个通道操作。<-channel1 表示从 channel1 读取数据,channel2 <- data 表示将 data 写入 channel2。
当 select 语句执行时,它会检查所有关联的通道,如果其中某个通道已准备好(即可读或可写),对应的 case 子句中的代码会被执行。如果多个通道都准备好,会随机选择其中一个执行。如果没有任何通道准备好,即所有通道都阻塞,那么 default 子句中的代码会被执行。
使用 select 语句可以解决在多个通道上进行并发操作的问题,允许我们在不阻塞的情况下等待多个通道的操作。这是 Go 语言并发编程中非常有用的机制。
5 单引号,双引号,反引号的区别?
单引号,表示byte类型或rune类型,对应 uint8和int32类型,默认是 rune 类型。byte用来强调数据是raw data,而不是数字;而rune用来表示Unicode的code point。
双引号,才是字符串,实际上是字符数组。可以用索引号访问某字节,也可以用len()函数来获取字符串所占的字节长度。
反引号,表示字符串字面量,但不支持任何转义序列。字面量 raw literal string 的意思是,你定义时写的啥样,它就啥样,你有换行,它就换行。你写转义字符,它也就展示转义字符。
6 go语言中 map 中删除一个 key,它的内存会释放么?
在 Go 语言中,当你从一个 map 中删除一个 key-value 对时,对应的 key 和 value 不会立即释放内存。相反,它们会等待垃圾回收器(garbage collector)来回收这些内存。
Go 语言的垃圾回收器是自动的,它会周期性地扫描和回收不再被引用的对象。当你删除一个 map 中的 key-value 对时,该 key 和 value 对象仍然存在于内存中,但如果它们没有被其他地方引用,它们将被标记为不可达对象,并在下一次垃圾回收时被回收。
垃圾回收器的工作是基于一些算法和策略的,具体的回收时间取决于多个因素,例如内存压力、对象大小和系统负载等。因此,无法准确预测何时会释放具体的内存。
如果你需要立即释放 map 中删除的 key-value 对所占用的内存,可以通过将对应的 value 设置为 nil 来帮助垃圾回收器更早地回收它们。例如,将其设置为 map[key] = nil。这样,如果没有其他地方引用该 value,它将成为不可达对象,并有望在下一次垃圾回收时被回收。
7 怎么处理对 map 进行并发访问?
方式一、使用内置sync.Map
方式二、使用读写锁实现并发安全map
参考:
https://www.zhihu.com/tardis/bd/art/519979757?source_id=1001
8 Go 中 init 函数的特征?
答:一个包下可以有多个 init 函数,每个文件也可以有多个 init 函数。多个 init 函数按照它们的文件名顺序逐个初始化。应用初始化时初始化工作的顺序是,从被导入的最深层包开始进行初始化,层层递出最后到 main 包。不管包被导入多少次,包内的 init 函数只会执行一次。应用初始化时初始化工作的顺序是,从被导入的最深层包开始进行初始化,层层递出最后到 main 包。但包级别变量的初始化先于包内 init 函数的执行
9 goroutine 的自旋占用资源如何解决
自旋锁是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断地判断是否能够被成功获取,直到获取到锁才会退出循环。