Golang面试系列1-基础知识

1. 内置函数

1.1 new,make的区别

1)作用类型不同:new给string,int和数组分配内存,make给slice,map,channel分配内存;

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

3)初始化值不同:new 分配的空间被清零(分配的内存置为零),make 分配空间后,会进行初始化为非零值;

1.2 简述defer延迟机制

defer是一种用于注册延迟调用的机制,让函数或者语句在当前函数执行完成(包括return正常结束/panic异常结束)之后进行调用。

特性

  • 延迟调用:defer在main函数return之前调用 & defer必须置于函数内部
  • LIFO:后进先出,压栈执行
  • 作用域:只作用于绑定的函数,若defer处于匿名函数中,会先调用匿名函数中的defer

应用场景

  • 并发处理:使用sync.WaitGroup时的Add()-Done()操作
  • 加锁解锁:mu.RLock() & mu.RUnLock()
  • 资源释放:打开关闭文件/客户端等成对操作

2. 数据结构

2.1 Slice

1)底层结构

type slice struct {
  array unsafe.Pointer //指向具体的底层数组
  len   int            //切片长度
  cap   int            //切片容量
}

2)简单描述底层扩容机制

1)切片较小( <= 1024):以2倍速扩容,避免频繁扩容,减少内存分配的次数和数据拷贝的代价;

  • 如果期望容量大于当前容量的两倍,使用期望容量
  • 如果当前切片长度小于 1024,容量翻倍

2)切片较大( > 1024):以1.25倍速扩容,避免空间浪费;

  • 如果当前切片长度大于 1024,每次扩容 25% 的容量,直到新容量大于期望容量
  • 在进行循环1.25倍计算时,最终容量计算值发生溢出,即超过了int的最大范围,则最终容量就是新申请的容量

3)数组与切片的异同

相同点

  • 只能存储同数据类型
  • 都通过下标访问元素

不同点

  • 切片是指针类型,数组是值类型【传递数组是通过拷贝的方式,传递切片是通过传递引用的方式】
  • 数组长度固定,切片可以动态扩容【数组是一组内存空间连续的数据,一旦初始化长度大小就不会再改变,切片的长度可以进行扩展,当切片底层的数组容量不够时,切片会创建新的底层数组】

2.2 Channel

本质是先进先出的队列,用于数据传递或数据通信;可使用goroutine + channel实现高效,线程安全的通信。

1)是否线程安全?如何保证?

结论:channel是线程安全的

原理:底层采用mutex锁来保证数据读写安全。在对循环数组buf中的数据进行入队和出队操作时,会先获取互斥锁,然后操作channel数据。

2)底层实现

关键字段及描述

        buf:缓冲区数组指针,只有缓存channel才有buf指针

        sendx & recvx: 底层循环数组的发送 & 接收元素位置索引

        sendq & recvq:发送和读取数据被阻塞的goroutine等待队列

向Channel发送数据流程(ch ← x)

从channel读取数据流程(x ← ch)

流程描述

sendq不为空:

  • 无缓冲区: 从sendq头部取一个goroutine,将数据读取出来,并唤醒对应的goroutine,结束读取过程
  • 有缓冲区: 说明缓冲区已满,则从缓冲区中首部读出数据,把sendq头部的goroutine数据 写入缓冲区尾部,goroutine唤醒,结束读取过程

sendq为空:

  • 缓冲区有数据:则直接从缓冲区读取数据,结束读取过程
  • 缓冲区无数据:则只能将当前的goroutine加入到recvq,并进入waiting状态,等待被写goroutine唤醒

3)channel状态和操作

操作nilclosedactive
close(ch)panicpanic正常关闭
ch ← val(写)永远阻塞panic成功发送/阻塞
val,ok = ← ch(读)永远阻塞正常接收成功接收/阻塞

4)应用场景

  • 生产者-消费者解耦:生产者只需要往channel发送数据,消费者只管从channel中获取数据;
  • 控制并发数

2.3 Map

1)底层实现

结合顺序存储数组和链式存储两种存储结构;数组作为主干,数组元素的类型为链表

2)扩容机制是怎样的?

  • bmap扩容的加载因数达到6.5(元素个数/bucket),bmap就会进行扩容,将原来bucket数组数量扩充一倍,产生一个新的bucket数组。bmap中的oldbuckets属性指向旧bucket数组。
  • map是渐进式扩容,首先开辟2倍的内存空间,创建一个新的bucket数组。当访问旧bucket数组时,会将旧bucket拷贝到新的bucket数组,然后去掉旧bucket的引用,等待GC回收

3)如何实现线程安全的map?

  • 加读写锁(RWMutex):加锁方式简单,但并发性能差
  • 分片加锁:锁粒度小,可提高访问map的吞吐,并发性能较好
  • sync.Map:官方标准,但应用场景特殊,最不常用
    • 场景一:只会增长的缓存系统,一个 key 值写入一次而被读很多次
    • 场景二:多个 goroutine 为不相交的键读、写和重写键值对
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值