简单
golang里的数组和切片有了解过吗
数组和切片都是拥有相同类型一组元素集合
数组为固定长度,切片为可变长度
数组不可扩容,切片可以,切片扩容,如果不足1024每次扩容为两倍扩容,如果高于1024,为1.25倍扩容
切片实际底层指向的也是一个数组的指针,切片的底层的结构体可以看到,有 指针 容量长度
数组是值类型,将一个数组赋值给另一个数组时,传递的是一份深拷贝,赋值和函数传参操作都会复制整个数组数据,会占用额外的内存;切片是引用类型,将一个切片赋值给另一个切片时,传递的是一份浅拷贝,赋值和函数传参操作只会复制len和cap,但底层共用同一个数组,不会占用额外的内存。
for range (slice和map)时遇到的“坑”
func main() {s := []int{1, 2, 3}m := make(map[int]*int)for i, v := range s {// m[i] = &v 这样是不行的,// 因为for range 迭代过程是根据slice中的变量遍历出来的新变量,遍历出来的值都是同一地址。所以应该用一个变量进行接受,这样每个不同值的地址就不同了n := vm[i] = &n //这样才是正确的姿势}fmt.Println(s) // [1 2 3]for _, v := range m {fmt.Println(*v)// 1 2 3}
}
多个切片如果共享同一个底层数组,这种情况下,对其中一个切片或者底层数组的更改,会影响到其他切片
func main() {slice1 := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}Q2 := slice1[3:6]fmt.Println(Q2, len(Q2), cap(Q2)) // [4 5 6] 3 9Q3 := slice1[5:8]fmt.Println(Q3, len(Q3), cap(Q3)) // [6 7 8] 3 7Q3[0] = "Unknown"fmt.Println(Q2, Q3) // [4 5 Unknown] [Unknown 7 8]a := []int{1, 2, 3, 4, 5}shadow := a[1:3]fmt.Println(shadow, a) // [2 3] [1 2 3 4 5]shadow = append(shadow, 100)// 会修改指向数组的所有切片fmt.Println(shadow, a) // [2 3 100] [1 2 3 100 5]
}
使用 func copy(dst, src []Type) int 解决
func main() {slice1 := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}Q2 := make([]string, 3)copy(Q2, slice1[3:6])fmt.Println(Q2, len(Q2), cap(Q2)) // [4 5 6] 3 3Q3 := make([]string, 3)copy(Q3, slice1[5:8])fmt.Println(Q3, len(Q3), cap(Q3)) // [6 7 8] 3 3Q3[0] = "Unknown"fmt.Println(Q2, Q3) // [4 5 6] [Unknown 7 8]a := []int{1, 2, 3, 4, 5}shadow := make([]int, 2)copy(shadow, a[1:3])fmt.Println(shadow, a) // [2 3] [1 2 3 4 5]shadow = append(shadow, 100)fmt.Println(shadow, a) // [2 3 100] [1 2 3 4 5]
}
实现slice线程安全有两种方式
通过加锁实现slice线程安全,适合对性能要求不高的场景
func main() {var lock sync.Mutex //互斥锁a := make([]int, 0)var wg sync.WaitGroupfor i := 0; i < 10000; i++ {wg.Add(1)go func(i int) {defer wg.Done()lock.Lock()defer lock.Unlock()a = append(a, i)}(i)}wg.Wait()fmt.Println(len(a)) // 10000
}
通过channel实现slice线程安全,适合对性能要求高的场景
func main() {buffer := make(chan int)a := make([]int, 0)// 消费者go func() {for v := range buffer {a = append(a, v)}}()// 生产者var wg sync.WaitGroupfor i := 0; i < 10000; i++ {wg.Add(1)go func(i int) {defer wg.Done()buffer <- i}(i)}wg.Wait()fmt.Println(len(a)) // 10000
}
数组怎么转集合
func main() {m := make(map[int]int)arr := []int{1, 2, 3, 4, 5}for i, v := range arr {m[i] = v}fmt.Println(m)
}
介绍一下通道
通道用于协程之间数据的传递,通道有三种类型,读和写的单向通道和双向通道
通道可以控制协程的并发数
map取一个key,然后修改这个值,原map数据的值会不会变化
func main() {ma := make(map[int]int)ma[1] = 123updateMapValue(ma)fmt.Println(ma[1]) //321
}func updateMapValue(ma map[int]int) {ma[1] = 321
}
channel有缓冲和无缓冲在使用上有什么区别?
无缓冲的与有缓冲channel有着重大差别:一个是同步的 一个是非同步的
比如
ch1:=make(chan int) 无缓冲
ch2:=make(chan int,1) 有缓冲
ch1<-1 无缓冲的
不仅仅是 向 c1 通道放 1 而是 一直要有别的协程 <-ch1 接手了 这个参数,那么ch1<-1之后的代码才会继续执行下去,要不然就一直阻塞着
而 ch2<-1 则不会阻塞,因为缓冲大小是1 (其实是缓冲大小为0)只有当 放第二个值的时候 第一个还没被人拿走,这时候才会阻塞
打个比喻
无缓冲的 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。
无缓冲保证信能到你手上
有缓冲的 就是一个送信人去你家仍到你家的信箱 转身就走 ,除非你的信箱满了 他必须等信箱空下来。
有缓冲的 保证 信能进你家的邮箱
如何判断channel是否关闭
用 select 和 <-ch 来结合可以解决这个问题
ok的结果和含义:
true:读到数据,并且通道没有关闭。
false:通道关闭,无数据读到。
func main() {ch := make(chan int, 10)for i := 1; i <= 9; i++ {ch <- i}close(ch)for i := 0; i < 10; i++ {select {case v, ok := <-ch:if ok {fmt.Println(v)} else {fmt.Println("关掉了")}default:fmt.Println("没啥事")}}
}
需要注意:
case 的代码必须是 _, ok:= <- ch 的形式,如果仅仅是 <- ch 来判断,是错的逻辑,因为主要通过 ok的值来判断;
select 必须要有 default 分支,否则会阻塞函数,我们要保证一定能正常返回;
写入channel的时候判断其是否已经关闭,此时如果 channel 关闭,写入时触发panic: send on closed channel
make 与 new 的区别
简单的说,new只分配内存,make用于slice,map,和channel的初始化。
func main() {var v *int*v = 8fmt.Println(*v)// 会报错// panic: runtime error: invalid memory address or nil pointer dereference
}
解决:
func main() {var v *intv = new(int)*v = 8fmt.Printf("%d\n", *v)
}
go语言的引用类型有什么?
map:golang中map是一种无序的、键值对的集合,其是通过key检索数据,且key类似于索引,指向数据的值,golang中常使用hash表来实现map。
pointers:golang中golang是指计算机内存中变量所在的内存地址,使用pointers可以节省内存,但golang中pointers不能进行偏移和运算,只能读取指针的位置。
slice:golang中slice是对数组的抽象,相对于数组,slice的长度是不固定的,可以追加元素,且在追加元素时可以增大slice的容量。
channel:golang中channel是指管道,是一种用于实现并行计算方程间通信的类型,允许线程间通过发送和接收来传输指定类型的数据,初始值为nil。
interface:golang中interface是指接口,是一组方法签名的集合,可以使用接口来识别一个对象够进行的操作。
function:golang中function是指函数,function不支持嵌套、重载和默认参数,但无需声明原型,常使用func关键字定义函数。