Go语言和字节跳动经典面试题目汇总:

1,861 阅读8分钟

最近投递了字节跳动基础架构组的日常实习生,整理一下Go语言和字节跳动的经典面试题目用于本人以及读者面试之前进行复习和参考。

Go语言部分

Go语言——垃圾回收

Go V1.3之前使用的是:标记-清除:

  • 1、暂停业务逻辑,找到不可达对象和可达对象
  • 2、开始标记,程序找出所有的可达对象并进行标记
  • 3、标记结束之后,开始清除未标记的对象
  • 4、停止暂停,程序继续。循环重复这个过程,直到process的生命周期结束

标记-清除的缺点: STW(Stop the World): 让程序暂停,程序出现卡顿 标记需要扫描整个heap 清除数据会产生heap碎片

为了减少STW的时间,后续对上述的第三步和第四步进行替换。

Go V1.5开始使用的是三色标记法:

1、把新创建的对象,默认的颜色都标记为“白色”
2、每次GC回收开始,然后从根节点开始遍历所有对象,把遍历到的对象从白色集合放入“灰色”集合。
3、遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合
4、重复第三步,直到灰色集合中没有对象为止
5、回收所有的白色标记对象,也即垃圾

三色标记法在不采用STW保护时会出现:

  • 1.一个白色对象会被一个黑色对象引用
  • 2.灰色对象和它之间的可达关系的白色对象遭到破坏

这两种情况同时满足,会出现对象丢失

解决方案:
1.强三色不变式:强制性的不允许黑色对象引用白色对象(破坏1)
2.弱三色不变式:黑色对象可以引用白色对象,白色对象存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象(破坏2)

屏障:
1.插入屏障:在A对象引用B对象的时候,B对象被标记为灰色(满足强三色不变式,黑色引用的白色对象会被强制转坏为灰色)。只有堆上的对象触发插入屏障,栈上的对象不触发插入屏障。在准备回收白色前,重新遍历扫描一次栈空间。此时加STW暂停保护栈,防止外界干扰。

不足:结束时需要通过STW重新扫描栈

2.删除屏障:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色(满足弱三色不变式)。

删除屏障的不足:回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉。

Go V1.8的三色标记法+混合写屏障机制 具体操作: 1.GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW)
2.GC期间,任何在栈上创建的新对象,均为黑色
3.被删除对象标记为灰色
4.被添加的对象标记为灰色
满足:变形的弱三色不变式(结合了插入、删除写屏障的优点)

GPM调度和CSP模型

CSP模型是“以通信的方式来共享内存”,不同于传统的多线程通过共享内存来通信。用于描述两个独立的并发实体通过共享的通讯channel来进行通信的并发模型。

GPM分别是什么,分别有多少数量?
G:goroutine,go的协程,每个go关键字都会创建一个协程
M:machine,工作线程,在Go中称为Machine,数量对应真实的CPU数
P:process,包含运行Go代码所需要的必要资源,用来调度G和M之间的关联关系,其数量可以通过GOMAXPROCS0来设置,默认为核心数

线程想运行任务就得获取 P,从 P 的本地队列获取 G,当 P 的本地队列为空时,M 也会尝试从全局队列或其他 P 的本地队列获取 G。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。

Goroutine调度策略 1.队列轮转:P会周期性的将G调度到M中执行,执行一段时间后,保存上下文,将G放到队列尾部,然后从队列中再取出一个G进行调度,P还会周期性的查看全局队列是否有G等待调度到M中执行 2.系统调用:当G0即将进入系统调用时,M0将释放P,进而某个空闲的M1获取P,继续执行P队列中剩下的G。M1的来源有可能是M的缓存池,也可能是新建的。 3.当G0系统调用结束后,如果有空闲的P,则获取一个P,继续执行G0。如果没有,则将G0放入全局队列,等待被其他的P调度。然后M0将进入缓存池睡眠。

Chan原理

Chan本质上是一个环形队列,其结构体如下:

type chan struct {
    qcount uint // 队列中的总元素个数
    dataqsiz uint // 环形队列大小,即可存放元素的个数
    buf unsafe.Pointer // 环形队列指针
    elemsize uint16 //每个元素的大小
    closed uint32 //标识关闭状态
    elemtype *_type // 元素类型
    sendx uint // 发送索引,元素写入时存放到队列中的位置

    recvx uint // 接收索引,元素从队列的该位置读出
    recvq waitq // 等待读消息的goroutine队列
    sendq waitq // 等待写消息的goroutine队列
    lock mutex //互斥锁,chan不允许并发读写
}

从channel中读数据: 1.若等待发送队列 sendq 不为空,且没有缓冲区,直接从 sendq 中取出 G(协程) ,把 G 中数据读出,最后把 G 唤醒,结束读取过程。

2.如果等待发送队列 sendq 不为空,说明缓冲区已满,从缓冲区中首部读出数据,把 G 中数据写入缓冲区尾部,把 G 唤醒,结束读取过程。

3.如果缓冲区中有数据,则从缓冲区取出数据,结束读取过程。.将当前 goroutine 加入 recvq ,进入睡眠,等待被写 goroutine 唤醒

从channel中写数据

1.若等待接收队列 recvq 不为空,则缓冲区中无数据或无缓冲区,将直接从 recvq 取出 G ,并把数据写入,最后把该 G 唤醒,结束发送过程。

2.若缓冲区中有空余位置,则将数据写入缓冲区,结束发送过程。

3.若缓冲区中没有空余位置,则将发送数据写入 G,将当前 G 加入 sendq ,进入睡眠,等待被读 goroutine 唤醒。

关闭 channel

关闭 channel 时会将 recvq 中的 G 全部唤醒,本该写入 G 的数据位置为 nil。将 sendq 中的 G 全部唤醒,但是这些 G 会 panic。

Context结构原理

Go 1.7 标准库引入 context,中文译作“上下文”,准确说它是 goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息。

context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。

随着 context 包的引入,标准库中很多接口因此加上了 context 参数,例如 database/sql 包。context 几乎成为了并发控制和超时控制的标准做法。

context.Context 类型的值可以协调多个 groutine 中的代码执行“取消”操作,并且可以存储键值对。最重要的是它是并发安全的。\
与它协作的 API 都可以由外部控制执行“取消”操作,例如:取消一个 HTTP 请求的执行。

context 包就是为了解决上面所说的这些问题而开发的:在 一组 goroutine 之间传递共享的值、取消信号、deadline……

用简练一些的话来说,在Go 里,我们不能直接杀死协程,协程的关闭一般会用 channel+select 方式来控制。但是在某些场景下,例如处理一个请求衍生了很多协程,这些协程之间是相互关联的:需要共享一些全局变量、有共同的 deadline 等,而且可以同时被关闭。再用 channel+select 就会比较麻烦,这时就可以通过 context 来实现。

一句话:context 用来解决 goroutine 之间退出通知元数据传递的功能。

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

「Deadline」 方法:可以获取设置的截止时间,返回值 deadline 是截止时间,到了这个时间,Context 会自动发起取消请求,返回值 ok 表示是否设置了截止时间。 「Done」 方法:返回一个只读的 channel ,类型为 struct{}。如果这个 chan 可以读取,说明已经发出了取消信号,可以做清理操作,然后退出协程,释放资源。 「Err」 方法:返回Context 被取消的原因。 「Value」 方法:获取 Context 上绑定的值,是一个键值对,通过 key 来获取对应的值。

竞态、资源逃逸

  1. 竞态、内存逃逸 1.资源竞争,就是在程序中,同一块内存同时被多个 goroutine 访问。我们使用 go build、go run、go test 命令时,添加 -race 标识可以检查代码中是否存在资源竞争。

解决这个问题,我们可以给资源进行加锁,让其在同一时刻只能被一个协程来操作。

sync.Mutex sync.RWMutex

2.逃逸分析就是程序运行时内存的分配位置(栈或堆),是由编译器来确定的。堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。

逃逸场景:

指针逃逸
栈空间不足逃逸
动态类型逃逸
闭包引用对象逃逸

Golang中make和new的区别

1.make 仅用来分配及初始化类型为 slice、map、chan 的数据。
2.new 可分配任意类型的数据,根据传入的类型申请一块内存,返回指向这块内存的指针,即类型 *Type。
3.make返回引用,即 Type,new 分配的空间被清零, make分配空间后,会进行初始化。

Golang中对nil的slice和空的slice的处理是否相同

首先Go的JSON 标准库对 nil slice 和 空 slice 的处理是不一致。 1.slice := make([]int,0):slice不为nil,但是slice指向的底层空间是空的。 2.slice := []int{} :slice的值是nil,可用于需要返回slice的函数,当函数出现异常的时候,保证函数依然会有nil的返回值。

Golang的内存模型中为什么小对象多了会造成GC压力?

通常小对象过多会导致GC三色法消耗过多的CPU。优化思路是,减少对象分配。

channel为什么能做到线程安全?

channel可以理解是一个先进先出的循环队列,通过管道进行通信,发送一个数据到Channel和从Channel接收一个数据都是原子性的。不要通过共享内存来通信,而是通过通信来共享内存,前者就是传统的加锁,后者就是Channel。设计Channel的主要目的就是在多任务间传递数据的,本身就是安全的。

GC的触发条件

1.主动触发(手动触发),通过调用 runtime.GC 来触发GC,此调用阻塞式地等待当前GC运行完毕。 2.被动触发,分为两种方式:

2.1.使用步调(Pacing)算法,其核心思想是控制内存增长的比例,每次内存分配时检查当前内存分配量是否已达到阈值(环境变量GOGC):默认100%,即当内存扩大一倍时启用GC。
2.2.使用系统监控,当超过两分钟没有产生任何GC时,强制触发 GC。

如何查看Goroutine的数量?如何限制Goroutine的数量?

1.在Golang中,GOMAXPROCS中控制的是未被阻塞的所有Goroutine,可以被 Multiplex 到多少个线程上运行,通过GOMAXPROCS可以查看Goroutine的数量。
2.使用通道。每次执行的go之前向通道写入值,直到通道满的时候就阻塞了

Channel是同步的还是异步的?

Channel是异步进行的, channel存在3种状态:

1.nil,未初始化的状态,只进行了声明,或者手动赋值为nil
2.active,正常的channel,可读或者可写
3.closed,已关闭,千万不要误认为关闭channel后,channel的值是nil

线程和协程的区别?外加进程和线程的区别?

线程与协程的区别:

  1. 一个线程可以拥有多个协程,一个进程也可以单独拥有多个协程。

  2. 线程进程都是同步机制,而协程则是异步。

  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。

  4. 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。

  5. 协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建线程。

  6. 线程是协程的资源。协程通过Interceptor来间接使用线程这个资源。

进程与线程的区别:

  1. 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间

  2. 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源

  3. 线程是处理器调度的基本单位,但进程不是

  4. 二者均可并发执行

  5. 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

Go的Struct能不能比较?

1.不同struct类型的不可以比较,编译都不过,类型不匹配
2.对于相同类型,也分为两种情况:
(1)情况1:struct的所有成员都是可以比较的,则该 strcut 的不同实例可以比较
(2)情况2:struct中含有不可比较的成员(如 Slice),则该 struct 不可以比较

Go语言的Slice如何进行扩容?

在分配内存空间之前需要先确定新的切片容量,运行时根据切片的当前容量选择不同的策略进行扩容:

  1. 如果期望容量大于当前容量的两倍就会使用期望容量;
  2. 如果当前切片的长度小于 1024 就会将容量翻倍;
  3. 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;

在Go函数中为什么会发生内存泄露?发生了泄漏如何检测?

Goroutine 作为一种逻辑上的轻量级线程,需要维护执行用户代码的上下文信息。在运行过程中也需要消耗一定的内存来保存这类信息,而这些内存在目前版本的 Go 中是不会被释放的。
因此,如果一个程序持续不断地产生新的 goroutine、且不结束已经创建的 goroutine 并复用这部分内存,就会造成内存泄漏的现象

Go语言中两个nil可能不相等吗?

两个nil值可能并不相等

如果可被比较的两个nil值中的一个的类型为接口类型,而另一个不是,则比较结果总是false。 原因是,在进行此比较之前,此非接口nil值将被转换为另一个nil值的接口类型,从而将此比较转化为两个接口值的比较。每个接口值可以看作是一个包裹非接口值的盒子。 一个非接口值被转换为一个接口类型的过程可以看作是用一个接口值将此非接口值包裹起来的过程。 一个nil接口值中什么也没包裹,但是一个包裹了nil非接口值的接口值并非什么都没包裹。 一个什么都没包裹的接口值和一个包裹了一个非接口值(即使它是nil)的接口值是不相等的。

Go语言中的内存对齐

由于CPU 并不会以一个一个字节去读取和写入内存。相反 CPU 读取内存是按照块读取的(Cache的实现机制也是块机制),块的大小可以为 2、4、6、8、16 字节等大小。块大小我们称其为内存访问粒度,内存访问粒度跟机器字长有关。

对齐规则: 1.结构体的成员变量,第一个成员变量的偏移量为 0。往后的每个成员变量的对齐值必须为编译器默认对齐长度或当前成员变量类型的长度,取最小值作为当前类型的对齐值。其偏移量必须为对齐值的整数倍
2.结构体本身,对齐值必须为编译器默认对齐长度,或结构体的所有成员变量类型中的最大长度,取最大数的最小整数倍作为对齐值
3.结合以上两点,可得知若编译器默认对齐长度,超过结构体内成员变量的类型最大长度时,默认对齐长度是没有任何意义的

两个interface是否可以比较

可以通过Go语言的反射机制进行比较

1.判断类型是否一样 reflect.TypeOf(a).Kind() == reflect.TypeOf(b).Kind()

2.判断两个interface{}是否相等 reflect.DeepEqual(a, b interface{})

3.将一个interface{}赋值给另一个interface{} reflect.ValueOf(a).Elem().Set(reflect.ValueOf(b))

Go语言打印时,%v,%+v,%#v有何不同

%v 只输出所有的值;
%+v 先输出字段名字,再输出该字段的值;
%#v 先输出结构体名字值,再输出结构体(字段名字+字段的值);

什么是 rune 类型?

Go语言的字符有以下两种:

1.uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
2.rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。

空 struct{} 占用空间么?用途是什么?

空结构体 struct{} 实例不占据任何的内存空间。

用途: 1.将 map 作为集合(Set)使用时,可以将值类型定义为空结构体,仅作为占位符使用即可。 2.不发送数据的信道(channel) 使用 channel 不需要发送任何的数据,只用来通知子协程(goroutine)执行任务,或只用来控制协程并发度。 3.结构体只包含方法,不包含任何的字段

字节跳动经典面试题

页面置换算法

最优页面置换算法为OPT,但本质上不可能实现,因为进程是无法预知哪个页面他之后不再使用。

先进先出算法FIFO,优势:实现简单,劣势:这种算法只有在线性访问内存地址时才是有效的,否则效率不高,而且它有一种异常现象,即在增加存储块的情况下,反而使缺页中断率增加了。当然,导致这种异常现象的页面走向实际上是很少见的。

LRU算法,优势:从概率来看,LRU算法发生缺页的概率比FIFO算法低得多。缺陷:实现难度高,需要真实的硬件支持例如计数器或者栈,并且也有对应的软件开销。一般来说实现的都是近似算法,例如:最近未使用过,只需要一个flag硬件位,每过一段时间,将该位设置为0。替换时,优先替换flag为0的页面。Clock置换算法:通过给每一个访问的页面关联一个附加位(reference bit),有些地方也叫做使用位(use bit)。他的主要思想是:当某一页装入主存时,将use bit置成1;如果该页之后又被访问到,使用位也还是标记成1。对于页面置换算法,候选的帧集合可以看成是一个循环缓冲区,并且有一个指针和缓冲区相关联。遇到页面替换时,指针指向缓冲区的下一帧。如果这页进入主存后发现没有空余的帧(frame),即所有页面的使用位均为1,那么这时候从指针开始循环一个缓冲区,将之前的使用位都清0,并且留在最初的位置上,换出该桢对应的页。

手撕LRU算法:hash表+双向链表

Http: Keep Alive

在http早期,每个http请求都要求打开一个tpc socket连接,并且使用一次之后就断开这个tcp连接。

使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高http服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。

但是,keep-alive并不是免费的午餐,长时间的tcp连接容易导致系统资源无效占用。配置不当的keep-alive,有时比重复利用连接带来的损失还更大。所以,正确地设置keep-alive timeout时间非常重要。

MAC协议:介质访问控制协议

信道划分的MAC协议

时间(TDMA)、频带(FDMA)、码片(CDMA)划分

随机访问MAC协议:

ALOHA,S-ALOHA,CSMA,CSMA/CD,其中CSMA/CD应用于以太网,CSMA/CA应用于802.11无线局域网

轮转访问MAC协议:

主节点轮询;令牌传递

TCP中的拥塞控制

TCP中进行拥塞控制的方法主要有:慢启动、拥塞避免、快速重传和快速恢复 在拥塞控制上,采用广受好评的TCP拥塞控制算法(也称AIMD算法)。该算法主要包括四个主要部分:

(1)慢启动

每当建立一个TCP连接时或一个TCP连接发生超时重传后,该连接便进入慢启动阶段。进入慢启动后,TCP实体将拥塞窗口的大小初始化为一个报文段,即:cwnd=1。此后,每收到一个报文段的确认(ACK),cwnd值加1,即拥塞窗口按指数增加。当cwnd值超过慢启动阐值(ssthresh)或发生报文段丢失重传时,慢启动阶段结束。前者进入拥塞避免阶段,后者重新进入慢启动阶段。

(2)拥塞避免

在慢启阶段,当cwnd值超过慢启动阐值(ssthresh)后,慢启动过程结束,TCP连接进入拥塞避免阶段。在拥塞避免阶段,每一次发送的cwnd个报文段被完全确认后,才将cwnd值加1。在此阶段,cwnd值线性增加。

(3)快速重传

快速重传是对超时重传的改进。当源端收到对同一个报文的三个重复确认时,就确定一个报文段已经丢失,因此立刻重传丢失的报文段,而不必等到重传定时器(RTO)超时。以此减少不必要的等待时间。

(4)快速恢复

快速恢复是对丢失恢复机制的改进。在快速重传之后,不经过慢启动过程而直接进入拥塞避免阶段。每当快速重传后,置ssthresh=cwnd/2、ewnd=ssthresh+3。此后,每收到一个重复确认,将cwnd值加1,直至收到对丢失报文段和其后若干报文段的累积确认后,置cwnd=ssthresh,进入拥塞避免阶段。

Http 1.1 的新特性

1)缓存策略:HTTP 1.1 中引入更多的缓存头来控制缓存策略;

2)带宽和连接优化:引入 range 头域来请求资源的一部分,返回 206 状态码,支持断点续传;

3)错误通知管理:新增 24 个错误状态响应码,如 409(Conflict)、410(Gone);

4)Host头处理:1.1 的请求和响应消息都应支持 Host(主机名)头域,且请求消息中如果没有会报一个 400 错误;

5)长连接:默认开启keep-alive,支持长连接和请求的流水线(流水线未实现)。

Http 2.0的新特性

1)二进制分帧;

2)多路复用;(与Http1.1长连接(流水线)区别:1.x依旧是串行,会阻塞后续请求。)

3)首部压缩:通过缓存表避免重复header的传输;

4)优先级和依赖性:请求时告知服务器资源分配权重,优先加载重要资源;

5)服务端推送。

Https与Http的区别

1)HTTPS 协议需要到 CA(Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。

2)HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。

3)HTTP 和 HTTPS 使用的是完全不同的连接方式,使用的端口也不一样,前者是80,后者是443。

4)HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)

SSL的四次握手过程

1)客户端请求建立SSL链接,并向服务端发送一个随机数–Client random和客户端支持的加密方法,比如RSA公钥加密,此时是明文传输。

2)服务端回复一种客户端支持的加密方法、一个随机数–Server random、授信的服务器证书和非对称加密的公钥。

3)客户端收到服务端的回复后利用服务端的公钥,加上新的随机数–Premaster secret 通过服务端下发的公钥及加密方法进行加密,发送给服务器。

4)服务端收到客户端的回复,利用已知的加解密方式进行解密,同时利用Client random、Server random和Premaster secret通过一定的算法生成HTTP链接数据传输的对称加密key – session key。

此后的HTTP链接数据传输即通过对称加密方式进行加密传输。

对称加密和非对称加密之间的区别

对称加密和非对称加密的主要区别在于是否使用同一个密钥加解密。

Https 为何采用非对称加密

证书是公开的,中间人可以随意得到证书,但私钥是无法获取的,一份公钥是不可能推算出其对应的私钥,中间人即使拿到证书也无法伪装成合法服务端,因为无法对客户端传入的加密数据进行解密。

从输入网址到浏览器显示过程

1)DNS对输入网址进行域名解析;
2)建立TCP连接(三次握手);
3)客户端发送HTTP请求,服务端响应请求;
4)浏览器解析渲染页面;
5)连接结束(四次挥手)。

Session、Cookie和Token区别

1.session 和cookie区别:

  • 数据存放位置不同:Session数据是存在服务器中的,cookie数据存放在浏览器当中。

  • 安全程度不同:cookie放在服务器中不是很安全,session放在服务器中,相对安全。

  • 性能使用程度不同:session放在服务器上,访问增多会占用服务器的性能;考虑到减轻服务器性能方面,应使用cookie。

  • 数据存储大小不同:单个cookie保存的数据不能超过4K,session存储在服务端,根据服务器大小来定。

2.token 和session区别:

  • token是开发定义的,session是http协议规定的;

  • token不一定存储,session存在服务器中;

  • token可以跨域,session不可以跨域,它是与域名绑定的。

CSRF是什么?

CSRF(Cross-site request forgery),跨站请求伪造

一般来说,攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等。攻击者利用网站对请求的验证漏洞而实现这样的攻击行为,网站能够确认请求来源于用户的浏览器,却不能验证请求是否源于用户的真实意愿下的操作行为。

客户端禁用 Cookie 后如何实现 Session?

1)URL重写;

2)表单隐藏字段;

3)Token+localStorage。

Redis中ZSet使用的什么数据结构?此类数据结构与平衡二叉树的优劣性?

ZSet中使用的数据结构为跳表。跳表本质上是一种基于概率的有序链表数据结构,保证插入、删除、查找等操作都是O(logN)级别的时间复杂度。

优势:

  • 在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
  • 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
  • 从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
  • 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;
  • 从算法实现难度上来比较,skiplist比平衡树要简单得多。

劣势:

  • 由于跳表是随机性算法,因此在极端情况下,可能跳表的时间复杂度会很高。

osi7层模型与5层模型

应用层、表示层、会话层、传输层、网络层、链路层和物理层

5层模型将应用层、表示层、会话层合并称为应用层

vim编辑模式 常用的快捷键有哪些?

vim按i进入编辑模式。
常用快捷键:":"进入末行模式,k;j;h;l用于光标上下左右的移动。yy拷贝光标所在的行,dd删除光标所在行,p用于黏贴yy或者dd的行内容。D删除当前光标到行尾的内容,dG用于删除当前行到文档尾部的内容。CTRL+f上一页,CTRL+b下一页。

什么是布隆过滤器

它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

当一个元素被加入集合时,通过 K 个 Hash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:

如果这些点有任何一个 0,则被检索元素一定不在; 如果都是 1,则被检索元素很可能在。

  • 添加元素的原理
    1 将要添加的元素给k个hash函数
    2 得到对应于位数组上的k个位置
    3 将这k个位置设置成 1

  • 查询元素原理
    1 将要查询的元素给k个hash函数
    2 得到对应数组的k个元素
    3 如果k个位置中有一个为0,则肯定不在集合中
    4.如果k个位置全部为1,则有可能在集合中

优点:查询效率和空间效率极高 缺点:可能会有误查的现象,并且无法进行删除

排序算法中的堆排序是否是不稳定排序?其为何不稳定?

首先明确稳定性排序的概念:稳定性排序指的是,对于相同的两个项x1==x2,在排序前后能够保证其相对位置没有变化,也就是说假设排序前x1排在x2之前,那么排序之后x1应该仍然排在x2之前。

为何堆排序是不稳定的:有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。

进程间有哪些通信方式?

1)管道 2)信号量(计数器) 3)信号(事件)

4)消息队列 5)共享内存 6)Socket

线程之间有哪些通信方式?

1)互斥量 2)信号量 3)事件

IO 模型分为哪些?

  • 同步和异步:消息通信机制,同步需要用户线程轮询,异步为内核通知
  • 阻塞和非阻塞:程序在等待调用结果时的状态,操作方式

MySQL如何查看最近慢查询

show variables like 'slow_query%'

MySQL索引什么时候会失效,  为什么会失效。

1)like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效;

2)or 语句前后没有同时使用索引。当 or 左右查询字段只有一个是索引,该索引失效,只有左右查询字段均为索引时,才会生效;

3)联合索引不使用第一列,索引失效;

4)数据类型出现隐式转化。如 varchar 不加单引号的话可能会自动转换为 int 型,使索引无效,产生全表扫描;

5)在索引列上使用 IS NULL 或 IS NOT NULL操作。最好给列设置默认值。

6)在索引字段上使用not,<>,!=。不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。 优化方法: key<>0 改为 key>0 or key<0。

7)对索引字段进行计算操作、字段上使用函数。

8)当 MySQL 觉得全表扫描更快时(数据少);

MVCC是什么

MVCC——Multible-Version Concurrency Control分布式并发控制.MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。
如果有人从数据库中读数据的同时,有另外的人写入数据,有可能读数据的人会看到『半写』或者不一致的数据。有很多种方法来解决这个问题,叫做并发控制方法。最简单的方法,通过加锁,让所有的读者等待写者工作完成,但是这样效率会很差。MVCC 使用了一种不同的手段,每个连接到数据库的读者,在某个瞬间看到的是数据库的一个快照,写者写操作造成的变化在写操作完成之前(或者数据库事务提交之前)对于其他的读者来说是不可见的。