golang面试经验答案总结(二)字节跳动一面 golang

问题来源于字节跳动一面 golang,自学习使用,答案搜索来自于网络,若有不对的地方欢迎指正。

1 什么是内存逃逸,在什么情况下发生,原理是什么?

golang中内存分配方式:主要是堆(heap)和栈(stack)分配两种。
栈分配廉价,堆分配昂贵。
栈分配:对于栈的操作只有入栈和出栈两种指令,属于静态资源分配。
使用栈分配:函数的内部中不对外开放的局部变量,只作用于函数中。
堆分配:堆中分配的空间,在结束使用之后需要垃圾回收器进行闲置空间回收,属于动态资源分配。
使用堆分配:1.函数的内部中对外开放的局部变量。2.变量所需内存超出栈所提供最大容量。

什么事内存逃逸

内存逃逸就是指当内存分配完毕后,编译器会根据对象,变量和方法的调用情况进行逃逸分析后,内存分配情况发生了改变,例如从栈逃逸到了堆,或从堆逃逸到了栈。

在什么情况下发生

下面是四种最常见引发内存逃逸的情况:

  1. 发送指针或带有指针的值到 channel 中。在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。
  2. 在一个切片上存储指针或带指针的值。一个典型的例子就是 []*string。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。
  3. slice 的背后数组被重新分配了,因为 append 时可能会超出其容量(cap)。slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。
  4. 在 interface 类型上调用方法。在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r, 调用 r.Read(b) 会使得 r 的值和切片 b 的背后存储都逃逸掉,所以会在堆上分配。

2 函数传指针和传值有什么区别

当一个变量被声明为 T类型。将其作为参数传递时,传递的是变量的副本。你会发现它的内存地址以及引用内存地址与原变量都是不一样的。
如果变量被声明为 *T类型。传递变量时,会创建一个新的指针,同时这个指针会指向原变量的内存地址。所以这种传递方式可以看作是传递了一份变量引用地址的副本。

如何选择

  • 一般来说需要考虑的是副本创建的成本与需求,主要有以下几点标准:
    不想变量被函数修改则选择 T。否则,则应该使用*T 定义。
  • 如果变量是一个非常大的 struct 或者数组,则副本创建时会对性能有一定的影响。此时,就应该考虑使用*T,在传递时,只会创建指针。对性能的影响几乎可以忽略不计。
  • 如果变量只是在本函数内使用,不涉及到函数传递。则可以考虑使用T,这样 Go 编译器会尽量的把其声明到栈上,而*T很可能会分配到堆上。会对垃圾回收有一定影响。

3 new和make有什么区别?

new:

  • new的作用是初始化一个纸箱类型的指针
  • new函数是内建函数,函数定义
  • 使用new函数来分配空间
  • 传递给new函数的是一个类型,而不是一个值
  • 返回值是指向这个新非配的地址的指针

make:

  • make的作用是为slice, map or chan的初始化
  • make 返回类型的引用而不是指针,而返回值也依赖于具体传入的类型
  • make函数是内建函数,函数定义

总结

  • make和new都是golang用来分配内存的內建函数,且在堆上分配内存,**make 即分配内存,也初始化内存。**new只是将内存清零,并没有初始化内存。
  • make返回的还是引用类型本身;而new返回的是指向类型的指针。
  • make只能用来分配及初始化类型为slice,map,channel的数据;new可以分配任意类型的数据。

4 了解golang的GC吗?

golang垃圾回收机制

5 了解GMP模型吗,介绍一下?

GMP

6 channel了解吗,channel的工作原理是什么?

Go语言中的通道(channel)是一种特殊的类型。
在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间通过通道就可以通信。
通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。

  1. channel本身是一个队列,先进先出
  2. 线程安全,不需要加锁
  3. 本身是有类型的,string, int 等,如果要存多种类型,则定义成 interface类型
  4. channel是引用类型,必须make之后才能使用,一旦 make,它的容量就确定了,不会动态增加!!它和map,slice不一样

channel底层设计

chanel作为一个通信管道,他需要发送数据和接受数据,特别是如果这是一个带缓冲区的管道的话,它首先需要一个用于存放数据的缓冲区,除此以外,当channel缓冲区满的时候,如果某个发送方还像其发送数据,他就需要一个队列来存放该发送协程,接收方同理。故而,整体而言,一个channel需要三大块:中间缓冲区发送等待队列接受等待队列

请添加图片描述

7 TCP三次握手、四次挥手,timewait,closewait状态

TCP的三次握手与四次挥手详解

在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的
同时由于TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议,TCP是全双工模式,所以需要四次挥手关闭连接。

  • 现在分析一下TCP协议首部的各项信息:
    • TCP端口号
      TCP的连接是需要四个要素确定唯一一个连接:(源IP,源端口号)+ (目地IP,目的端口号)
      所以TCP首部预留了两个16位作为端口号的存储,而IP地址由上一层IP协议负责传递源端口号和目地端口各占16位两个字节,也就是端口的范围是2^16=65535,另外1024以下是系统保留的,从1024-65535是用户使用的端口范围。
    • TCP的序号和确认号

32位序号 seq:Sequence number 缩写seq
,TCP通信过程中某一个传输方向上的字节流的每个字节的序号,通过这个来确认发送的数据有序,比如现在序列号为1000,发送了1000个字节,下一个序列号就是2000。

32位确认号 ack(小写):Acknowledge number
缩写ack,TCP对上一次seq序号做出的确认号,用来响应TCP报文段,给收到的TCP报文段的序号seq加1。

- **TCP的标志位**

SYN:同步标志位,用于建立会话连接,同步序列号;
ACK: 确认标志位,对已接收的数据包进行确认;
FIN: 完成标志位,表示我已经没有数据要发送了,即将关闭连接;

PSH:简写为P,推送标志位,表示该数据包被对方接收后应立即交给上层应用,而不在缓冲区排队;
RST:简写为R,重置标志位,用于连接复位、拒绝错误和非法的数据包;
URG:简写为U,紧急标志位,表示数据包的紧急指针域有效,用来保证连接不被阻断,并督促中间设备尽快处理;

三次握手建立

所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个报文。
三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。
在这里插入图片描述

第一次握手:
客户端将TCP报文标志位SYN置为1,随机产生一个序号值seq=J,保存在TCP首部的序列号(Sequence Number)字段里,指明客户端打算连接的服务器的端口,并将该数据包发送给服务器端,发送完毕后,客户端进入SYN_SENT状态,等待服务器端确认。

第二次握手:
服务器端收到数据包后由**标志位SYN=1表示知道客户端请求建立连接,同时将标志位SYN和ACK都置为1,确认号ack=J+1,随机产生一个序号值seq=K,**并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。

第三次握手:
客户端收到确认后,**检查确认号ack是否为J+1,标志位ACK是否为1,如果正确则将标志位ACK置为1,确认号ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,**客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了

注意:我们上面写的ack和ACK,不是同一个概念:
小写的ack代表的是头部的确认号Acknowledge number, 缩写ack,是对上一个包的序号进行确认的号,ack=seq+1。
大写的ACK,则是我们上面说的TCP首部的标志位,用于标志的TCP包是否对上一个包进行了确认操作,如果确认了,则把ACK标志位设置成1。
四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。

TCP 四次挥手关闭连接

四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。
由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
在这里插入图片描述
第一次挥手:
Client端发起挥手请求,并且停止发送数据,向Server端发送标志位是FIN报文段,FIN=1,设置序列号seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,Client端进入FIN_WAIT_1(终止等待1)状态,这表示Client端没有数据要发送给Server端了(TCP规定,FIN报文段即使不携带数据,也要消耗一个序号)

第二次挥手:
Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK=1的报文段,ack设为seq加1(u+1),并且带上自己的序列号seq=v,服务端就进入了CLOSE-WAIT(关闭等待)状态,TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

第三次挥手:
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

第四次挥手:
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端依然没有收到回复,才进入CLOSED状态

服务器只要收到了客户端发出的确认,立即进入CLOSED状态。可以看到,服务器结束TCP连接的时间要比客户端早一些。

TIME_WAIT

  • TIME_WAIT 是主动关闭链接时形成的,等待2MSL时间,约4分钟。主要是防止最后一个ACK丢失。 由于TIME_WAIT 的时间会非常长,因此server端应尽量减少主动关闭连接。

CLOSE_WAIT

  • CLOSE_WAIT是被动关闭连接是形成的。根据TCP状态机,服务器端收到客户端发送的FIN,则按照TCP实现发送ACK,因此进入CLOSE_WAIT状态。但如果服务器端不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。此时,可能是系统忙于处理读、写操作,而未将已收到FIN的连接,进行close。此时,recv/read已收到FIN的连接socket,会返回0。

8 了解socket编程吗,其中accept方法是什么

socket编程:accept()函数详解

函数原型

accept函数允许在套接字上进行传入连接尝试。

SOCKET WSAAPI accept(
  SOCKET   s,
  sockaddr *addr,
  int      *addrlen
);

listen监听客户端来的链接,accept将客户端的信息绑定到一个socket上,也就是给客户端创建一个socket,通过返回值返回给我们客户端的socket。

一次只能创建一个,有几个客户端链接,就要调用几次。

9 get、post方法有什么区别

  1. 发送的数据数量
    在 GET 中,只能发送有限数量的数据,因为数据是在 URL 中发送的。
    在 POST 中,可以发送大量的数据,因为数据是在正文主体中发送的。
  2. 安全性
    GET 方法发送的数据不受保护,因为数据在 URL 栏中公开,这增加了漏洞和黑客攻击的风险。
    POST 方法发送的数据是安全的,因为数据未在 URL 栏中公开,还可以在其中使用多种编码技术,这使其具有弹性。
  3. 加入书签中
    GET 查询的结果可以加入书签中,因为它以 URL 的形式存在;而 POST 查询的结果无法加入书签中。
  4. 编码
    在表单中使用 GET 方法时,数据类型中只接受 ASCII 字符。
    在表单提交时,POST 方法不绑定表单数据类型,并允许二进制和 ASCII 字符。
  5. 可变大小
    GET 方法中的可变大小约为 2000 个字符
    POST 方法最多允许 8 Mb 的可变大小。
  6. 缓存
    GET 方法的数据是可缓存的。
    POST 方法的数据是无法缓存的。
  7. 主要作用
    GET 方法主要用于获取信息
    POST 方法主要用于更新数据

10 进程和线程的区别是什么

面经(一)第10个内容

11 mysql索引了解,原理是什么?

MySQL 索引原理
索引(Index)是帮助MySQL高效获取数据的数据结构,简单来说,索引是一种数据结构。以协助快速查询、 更新数据库表中数据

索引的原理

  • 把创建索引列的内容进行排序
  • 对排序的结果生成倒排表
  • 在倒排表内容上拼接上数据行地址
  • 查询数据时,先拿到倒排表内容,在取出数据行地址,从而拿到具体的数据

在这里插入图片描述

  1. 逻辑维度
  • 主键索引:是一 种特殊的唯一索引它还多了一个限制条件,要求键值不能为空。主键索引用primay key 创建。
  • 普通(Normal):也叫非唯一索引,是最普通的索引,没有任的限制。
  • 联合索引:多个字段创建的索引,使用时遵循最左前缀原则。
  • 唯一 (Unique):索引列中的值必须是唯一的,但是允许为空值。
  • 空间索引:MySQL5.7之后支持空间索引,在空间索引这方面遵循OpenGIS几何数据模型规则。
  1. 数据结构维度
  • 哈希索引: 适合等值查询,检索效率高,一次到位。
  • B+树索引:所有数据存储在叶子节点,复杂度为O(logn),适合范围查询。
  • 全文(Fulltext):针对比较大的数据,比如我们存放的是消息内容、一篇文章,有 几KB的数据的这种情况,如果要解决like査询在全文匹配的时候效率低的问题,可以创建全文索引。只有文本类型的字段才可以创建全文索引,比如char、varchar、text。
  • R-Tree索引: 用来对GIS数据类型创建SPATIAL索引
  1. 物理存储维度
  • 聚集索引:聚集索引就是以主键创建的索引,在叶子节点存储的是表中的数据。

    • 优点:
      • 查询通过聚集索引可以直接获取数据,相比非聚集索引需要第二次查询(非覆盖索引情况下)效率高
      • 对范围查询效率很高,因为数据是按照大小排列的
      • 适合排序的场景,非聚集索引不适合
    • 缺点:
      • 维护索引代价很高,特别插入新行或者更新主键导致导致页的分裂
      • 如果主键是随机ID(比如UUID),导致存储稀疏(磁盘碎片),可能比全表扫描还慢,或者主键比较大,导致辅助索引变的很大(节点占用更多的物理空间),这也是建议自增id作为主键的根本原因
  • 非聚集索引:非聚集索引就是以非主键创建的索引,在叶子节点存储的是主键和索引列。

12 hashmap原理

HashMap的工作原理

什么是Hashmap

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。

13 redis有哪些数据类型

Redis常见的数据类型

String

  • String 是 Redis 最基本的类型,可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value
  • String 类型是二进制安全的。Redis 的 String 可以包含任何数据。比如jpg图片或者序列化的对象。
  • String 类型是 Redis 最基本的数据类型,String 类型的值最大能存储 512MB。

基本操作

添加 / 修改数据:set key value
获取数据:get key
删除数据:del key
添加 / 修改多个数据:mset key value key1 value1
获取多个数据:mget key key1
追加信息到原始数据后边(不存在时则添加):append key value

Hash

  • Redis hash 是一个键值(key=>value)对集合。
  • Redis hash 是一个 String 类型的 field 和 value 的映射表,hash 特别适合用于存储对象

基本操作

添加 / 修改数据:hset key field value
获取数据
获取一个:hget key field
获取多个:hgetall
key 删除数据:hdel key field field1
添加 / 修改多个数据:hmset key field value
field1 value1 获取多个数据:hmget key field field1
获取表中字段数量:hlen key
获取表中是否存在某个字段:hexists key field

List

  • 数据存储需求:存储多个数据,并对数据进行存储空间的顺序进行区分
  • 需要的数据结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
  • list类型:保存多个数据,底层使用双向链表存储结构实现
  • 列表(list)用于存储多个有序的字符串。列表是一种比较灵活的数据结构,可以充当栈和队列的角色,在实际开发上有很多应用场景

列表的特点:

  • 列表中的元素是有序的,可以通过索引下标来获取某个元素或者某个范围内的元素列表
  • 列表中的元素是可以重复的

基本操作

添加 / 修改数据
左边添加:lpush key value value1
右边添加:rpush key value value1
获取数据
lrange key start end
lindex key index
llen key
删除数据
rpop key
lpop key

Set(集合)

  • 新的存储需求:存储大量的数据,在查询方便提供更高的效率
  • 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
  • Set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的

基本操作

添加 / 修改数据:sadd key member member1
获取数据:smembers key
删除数据:srem key member1
获取集合数据总量:scard key
判断集合中是否包含指定数据:sismember key member

ZSet(Sorted Set 有序集合)

在之前的四个类型中都不支持排序的,下来咱们看的sorted_set类型是既支持存储大数据,也支持排序功能

基本操作

添加数据:zadd key score member
获取数据
zrange key start stop
zrevrange key start stop
删除数据:zrem key member

欢迎点赞收藏加关注!!

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值