计算机基础知识总结(八股文--计算机网络、操作系统、数据库、c++、数据结构与算法)

m0_48022770 2024-09-15 13:05:02 阅读 52

一、操作系统

0.内存管理

01.什么是虚拟内存?为什么需要虚拟内存?

虚拟内存为程序提供比实际物理内存更大的内存空间,同时提高内存管理的灵活性和系统的多任务处理能力。虚拟地址空间就是进程所能看到的内存空间,这段空间是连续的、独立的,实际地址空间则是内存上的空间,这段是所有进程共享的、有限的空间。虚拟内存就是把实际地址空间映射到虚拟地址空间的技术,这样就实现了内存隔离、内存扩展、物理内存管理、页面交换等技术。内存隔离就是每个进程都有自己的虚拟地址空间,因此一个进程无法访问另一个进程的内存。内存扩展就是虚拟内存让每个进程拥有比实际大的内存空间地址,可以处理更多的数据、更大的进程。物理内存管理,内存空间不足时把不常用的数据转移到硬盘上,释放内存,以助于更多进程使用。页面交换,进程可能会造成外部内存碎片,可能会导致内存空间不足,这时把不常用的数据交换到硬盘上,再交换回来,就能消除内存碎片,之前技术是内存分段,现在都是内存分页,一页或几页的内存交换就能解决内存不足的问题,而且效率高,内存分段的大数据在硬盘上读取速度慢。

02.什么是内存分段和分页?作用是什么?

内存分段是将一个程序的内存空间分为不同的逻辑段 segments ,每个段代表程序的一个功能模块

或数据类型,如代码段、数据段、堆栈段等。每个段都有其自己的大小和权限。内存分页是把整个虚拟和物理内存空间分成固定大小的页(如4KB)。这样一个连续并且尺寸固定的内存空间,我们叫页Page。内存分段容易产生外部碎片,内存分页容易产生内部碎片。

作用:逻辑隔离、内存保护、虚拟内存、共享内存、内存管理。(所以说这些技术都是相辅相成的)

03.解释一下内核态和用户态

操作系统中的两种执行模式,用来控制计算机对硬件资源的访问权限和操作范围。处理器硬件根据状态位决定当前可以执行哪些指令、访问哪些内存区域,操作系统依赖处理器的执行模式来实现安全和稳定性,确保应用程序在用户态执行,而特权操作(系统调用、异常处理、中断处理)以及直接访问操作系统的核心部分只能在内核态执行,占用CPU时不能被抢占。

04.中断和异常?区别?

中断和异常是两种不同的事件,二者都会导致CPU暂停当前的程序执行,转而去执行特定的处理程序。中断一般是由于外部设备或其他处理器导致的,一般是异步的,也就是说可以在任何时候发生,不会由当前的指令造成。比如键盘输入、鼠标输入、网络数据到达,来提醒CPU去处理这些事件。异常则是在当前CPU内部发生的,一般是同步的,由当前的指令造成。比如除数异常、访问非法内存地址、执行非法指令等,来提醒CPU去处理这些错误。

05.数据结构

数组、链表、栈stack、队列、哈希表hash table、树、图、集合set、字典map。每种数据结构有自己适合解决的问题。

1. 进程管理

11.进程和线程的区别

四个方面:定义、资源开销、独立安全性、通信方式

进程是运行中的程序,是系统进行资源分配的基本单位,而线程是进程的子任务,是系统能够进行CPU调度的最小单位,是进程内的执行单元。一个进程可以有多个线程,这些线程共享同一块内存空间。进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈。因为进程都有独立的内存空间,所以创建和销毁的开销比较大;当切换进程时,要保存和恢复整个进程的状态,所以上下文切换的花销较大。而线程共享同一块内存空间,所以创建和销毁开销比较小;线程间切换只需要保存和恢复少数的线程上下文,因此线程上下文切换开销较小。由于共享内存空间,所以线程之间可以访问共享数据,可以直接通信;而进程是独立的内存空间,只能通过进程间通信方式来进行通信,比如管道、消息队列、信号、信号量、共享内存、socket。进程间是相互隔离的,具有独立安全性,一个进程崩溃不会影响其他进程;而线程共享内存空间,一个线程崩溃,整个进程都会受影响。

线程共享内存空间就会导致创建、销毁、切换开销小,直接通信,非独立安全性;进程独立内存空间就会导致创建、销毁、切换开销大,进程间通信方式,独立安全性。

线程之间可以并发运行且共享相同的地址空间。

110.什么是协程?

协程是一种比线程更加轻量级的并发执行单元,通常由程序本身而不是操作系统内核来调度。轻量级、协作式调度、不需要锁机制、适合高并发。

协程与并发问题

协程通常运行在同一个线程内,因此所有协程共享相同的内存空间和资源。由于协程在同一线程中按顺序执行(虽然看起来是并发的),它们不会像线程那样同时访问共享资源,因此减少了许多并发问题,如竞态条件和死锁。

协程的优势:

无需锁机制:由于协程在同一线程中切换(按顺序切换),协程之间不会发生实际的并发竞争,因此不需要像线程那样使用锁来同步数据访问。

简化编程:协程通过让出执行权的方式实现多任务处理,避免了多线程编程中的复杂性(如锁和条件变量),使得异步编程更加直观。

12.调度原则

为了提高 CPU 利用率,在这种发送 I/O 事件致使 CPU 空闲的情况下,调度程序需要从就绪队列中选择一个进程来运行。要提高系统的吞吐率,调度程序要权衡长任务和短任务进程的运行完成数量。如果进程的等待时间很长而运行时间很短,那周转时间就很长,这不是我们所期望的,调度程序应该避免这种情况发生。就绪队列中进程的等待时间也是调度程序所需要考虑的原则。对于交互式比较强的应用,响应时间也是调度程序需要考虑的原则。

CPU 利用率、系统吞吐量、周转时间、等待时间、响应时间。

13.调度算法

先来先服务调度算法、 最短作业优先调度算法、高响应比优先调度算法、时间片轮转调度算法、最高优先级调度算法、多级反馈队列调度算法。

14.进程间通信

每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。

管道:单向,匿名管道,命名管道,mkfifo,通信效率低,但简单,实质是内核中的一段缓存,一端写入一端读取。

消息队列:消息队列是保存在内核中的消息链表,一是通信不及时,二是附件也有大小限制,不适合比较大数据的传输。存在用户态与内核态之间的数据拷贝开销。

共享内存:共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。

信号量:信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。

信号:对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。信号是进程间通信机制中唯一的异步通信机制。

Socket :跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。

15.互斥与同步

由于多线程执行操作共享变量的这段代码可能会导致竞争状态,因此我们将此段代码称为临界区,它是访问共享资源的代码片段,一定不能给多线程同时执行。我们希望这段代码是互斥的,也就说保证一个线程在临界区执行时,其他线程应该被阻止进入临界区。

同步,就是并发进程/线程在一些关键点上可能需要互相等待与互通消息,这种相互制约的等待与互通信息称为进程/线程同步

同步就好比:「操作 A 应在操作 B 之前执行」,「操作 C 必须在操作 A 和操作 B 都完成之后才能执行」等;互斥就好比:「操作 A 和操作 B 不能在同一时刻执行」;

为了实现进程/线程间正确的协作,操作系统必须提供实现进程协作的措施和方法,主要的方法有两种::加锁、解锁操作;信号量:P、V 操作;

假设有两个线程A和B,线程B需要在线程A执行完某个操作之后再继续执行。我们可以使用信号量来实现这个同步:初始时,信号量的值设为0。线程A在完成操作后,对信号量执行V操作(信号量加1)。线程B在执行之前,对信号量执行P操作(信号量减1)。由于初始时信号量为0,线程B会被阻塞,直到线程A完成操作并执行V操作,释放信号量。

16.线程同步的方式有哪些?

线程同步机制是指在多线程编程中,为了保证线程之间的互不干扰,而采用的一种机制。常见的线程同步机制有以下几种:

1. 互斥锁:互斥锁是最常见的线程同步机制。它允许只有一个线程同时访问被保护的临界区(共享

资源)

2. 条件变量:条件变量用于线程间通信,允许一个线程等待某个条件满足,而其他线程可以发出信

号通知等待线程。通常与互斥锁一起使用。

3. 读写锁:读写锁允许多个线程同时读取共享资源,但只允许一个线程写⼊资源。

4. 信号量:用于控制多个线程对共享资源进行访问的工具。

 

17.生产者消费者模型

锁实现了互斥,信号量实现了同步,生产者消费者模型实现了同步和互斥。生消模型让生产者和消费者解耦,让两个线程独立工作、并行执行,这种并行工作机制充分利用了系统资源,提升了程序的并发效率。生产者-消费者模型通过锁、条件变量或信号量来保证同步,避免竞态条件。同步的主要目的是协调多个线程或进程之间的执行顺序,互斥的主要目的是防止多个线程或进程在同一时间访问共享资源。

18.介绍一下几种典型的锁

忙等待锁(自旋锁):线程在尝试获取锁时会不断轮询,直到锁被释放。在单处理器上,需要抢占式的调度器。

互斥锁:互斥锁是一种最常见的锁类型,用于实现互斥访问共享资源。在任何时刻,只有一个线

程可以持有互斥锁,其他线程必须等待直到锁被释放。这确保了同一时间只有一个线程能够访问

被保护的资源。

读写锁:允许多个线程同时读共享资源,只允许一个线程进行写操作。分为读(共享)和写(排他)两种状态。

悲观锁:认为多线程同时修改共享资源的概率比较高,所以访问共享资源时候要上锁。互斥锁、自旋锁、读写锁,都是属于悲观锁。

乐观锁:先不管,修改了共享资源再说,如果出现同时修改的情况,再放弃本次操作。

19.死锁?如何避免?

死锁是指两个或多个进程在争夺系统资源时,由于互相等待对方释放资源而无法继续执行的状态。

死锁只有同时满足以下四个条件才会发生:互斥条件、持有并等待条件、不可剥夺条件、环路等待条件。最常见的并且可行的就是使用资源有序分配法,来破环环路等待条件

2.进程

在一个进程的活动期间至少具备三种基本状态,即运行状态、就绪状态、阻塞状态。

21.孤儿进程和僵尸进程

僵尸进程是指一个子进程已经执行完毕并退出,但其父进程还没有读取该子进程的退出状态信息(即通过调用 wait() 或 waitpid() 系统调用获取子进程的终止状态)。在这种情况下,子进程的进程表项仍然保留在系统中,虽然子进程已经终止,但它的进程号(PID)和退出状态信息还占用着系统资源,这种进程称为僵尸进程。

关键点:

僵尸进程并不占用系统的CPU和内存资源,只占用一个进程号和一些内核数据结构。僵尸进程会在父进程读取其退出状态后被完全清理掉。如果父进程在子进程终止后不及时调用 wait() 函数处理其退出状态,可能会导致系统中的僵尸进程过多,耗尽系统的进程号资源。

孤儿进程是指一个父进程终止了,但它的子进程还在继续运行。这种情况下,这些子进程将变为孤儿进程。为了防止这些孤儿进程无人管理,操作系统会将它们的父进程重新指定为 init 进程(PID为1的进程,在现代系统中可能是 systemd 进程)。init 进程会自动接管这些孤儿进程,并在它们终止时清理它们的资源。

关键点:

孤儿进程会被系统的 init 进程接管,确保它们的资源能够被正确回收。孤儿进程继续运行,不会对系统产生负面影响,系统会妥善管理它们。

22.守护进程

一种在后台运行的特殊类型的进程,通常不与用户直接交互,而是执行系统级的任务或服务,如网络服务、打印服务、日志记录等。守护进程在系统启动时启动,并在后台持续运行,直到系统关闭。它们主要负责系统的维护和服务,保证系统的某些功能或服务持续可用。

3.网络系统

31.什么是零拷贝

要想提高文件传输的性能,就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数要想减少上下文切换到次数,就要减少系统调用的次数

DMA 技术,也就是直接内存访问。

没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。零拷贝技术可以把文件传输的性能提高至少一倍以上

传输大文件的时候,使用「异步 I/O + 直接 I/O」;传输小文件的时候,则使用「零拷贝技术」;「内核缓冲区」实际上是磁盘高速缓存(PageCache的优点主要是两个:缓存最近被访问的数据;预读功能;

在传统的文件传输过程中(如通过网络发送文件),数据的拷贝过程大致如下:

从磁盘到内核缓冲区(第一次拷贝):数据从磁盘读入内核空间的缓冲区。从内核缓冲区到用户空间缓冲区(第二次拷贝):数据从内核缓冲区复制到用户空间缓冲区。从用户空间缓冲区回到内核缓冲区(第三次拷贝):数据从用户空间缓冲区复制回内核缓冲区,准备通过网络发送。通过网络发送(可能有 DMA,但涉及系统总线传输):数据从内核缓冲区传输到网络接口卡(NIC),并通过网络发送。

零拷贝的目标是减少或消除步骤 2 和 3 中的数据拷贝。零拷贝就是在以上的流动途径中,避免一些用户空间和内核缓冲区的拷贝。

32.I/O多路复用

一个单一的线程或进程中同时监视多个文件描述符(如套接字、文件、管道等),以确定这些文件描述符中的哪些已经准备好进行I/O操作(如读、写、或发生异常)。比如一个服务器程序,它需要处理多个客户端的网络请求。每个客户端连接到服务器的一个套接字。可以用多路复用技术(如 epoll)来“监视”所有这些套接字。为每个请求分配一个进程/线程的方式不合适,所以使用一个进程来维护多个 Socket。进程可以通过一个系统调用函数(select、poll、epoll)从内核中获取多个事件

selectpoll 都是内核通过轮询(遍历)的方式来检查多个文件描述符是否有 I/O 操作就绪(如可读、可写、异常情况等)。poll 没有文件描述符数量的限制,因为它采用的是动态数组的方式来存储文件描述符和事件。epoll 使用事件通知机制,一旦某个文件描述符的状态发生变化,内核会将这个事件通知给 epoll,使得程序不需要反复轮询所有文件描述符。

epoll实现的数据结构是红黑树和双向链表,红黑树用于存储和管理所有被监视的文件描述符(文件描述符通常是 socket)。当你向 epoll 实例添加、删除或修改一个文件描述符时,epoll 会将这个文件描述符插入或移除红黑树中。红黑树是一种自平衡的二叉搜索树,确保了插入、删除和查找操作的时间复杂度为 O(log n),这使得 epoll 能够高效地管理大量的文件描述符。双向链表用于存储就绪事件(即有 I/O 操作准备好的文件描述符)。当某个文件描述符就绪时,它会被加入到这个双向链表中。双向链表的插入和删除操作非常高效,时间复杂度为 O(1),这对于 epoll 处理大量并发连接时的性能至关重要。

33.epoll支持两种时间触发模式

边缘触发(ET水平触发(LT),ET: 当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完;LT: 当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;你的快递被放到了一个快递箱里,如果快递箱只会通过短信通知你一次,即使你一直没有去取,它也不会再发送第二条短信提醒你,这个方式就是边缘触发;如果快递箱发现你的快递没有被取出,它就会不停地发短信通知你,直到你取出了快递,它才消停,这个就是水平触发的方式。

33.Reactor

对事件反应」,也就是来了一个事件,Reactor 就有相对应的反应/响应收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程

二、计算机网络

 0.OSI七层模型

自己的理解:应用层:生成HTTP请求报文-----表示层:将请求报文转换成适合网络传输的数据格式,加密压缩编码等-----会话层:管理两个应用程序之间的会话,包括连接中断等------传输层:实现端到端通信(主机到主机),但它的职责不仅仅是主机到主机之间的通信,还需要确保数据能够被正确的应用程序接收。所以将数据分成更小的数据段(

适应MTU限制:分段可以确保数据能够在网络中传输,而不会因为过大而被拒绝传输。提高可靠性:分段减少了重传的开销,提高了错误处理的效率。流量与拥塞控制:分段有助于动态调整传输速度,避免网络拥塞。网络兼容性:分段提高了数据在不同类型网络中的传输兼容性。)

,并分别加上端口号,以使其到达正确的应用程序------网络层:给数据段加上网络IP地址,并且选择最佳路由------数据链路层:可靠传输、差错检测、封装成帧-----物理层:将数据转换成适合在网线中传输的电信号。

另外,数据链路层也会和传输层一样进行校验、流量控制,但是针对的对象、作用是不一样的。传输层实现的是端到端的通信,数据链路层实现的则是点对点的通信。点对点一般指设备相连,是局部的,而端到端则可能是跨越了全局网络,中间会经历多次路由器和交换机。

另外,物理层是用什么传输介质、什么物理接口、什么信号表示0和1,解决了两台计算机可以通过信号来传输比特0或1。数据链路层是如何标识MAC地址,如何从比特流中区分地址、数据,如何协调主机争用主线,实现了分组在一个网络上传输。网络层如何标识IP地址,如何路由选择,解决了在多个网络上传输的问题。传输层确定哪个进程通信,解决传输错误问题。应用层制定网络协议,编写网络应用,来实现特定的网络功能。

通过IP地址确定目标主机所在的局域网,然后通过ARP协议把IP地址对应的MAC地址找到,然后在同一片网络中找到目标主机。IP地址的前三个数标识网络,后一个数标识主机。

OSI七层模型的功能概述

物理层:负责物理设备之间的原始比特流的传输,包括电缆、网卡等硬件。它定义了接口类型、电压、电流等物理特性。

数据链路层:负责节点之间的可靠传输,包括错误检测与纠正、帧的创建与识别、流量控制等。数据链路层将数据封装成帧并通过物理层传输。

网络层:负责数据包的路由与转发,决定数据如何从源节点到达目标节点。典型的协议如IP(Internet Protocol)。

传输层:提供端到端的通信服务,确保数据的完整性和可靠性。主要协议包括TCP(传输控制协议)和UDP(用户数据报协议)。

会话层:管理会话,建立、维持和终止通信会话,确保数据交换的有序性。

表示层:处理数据格式的转换,确保发送端和接收端能够理解数据的格式。它涉及数据的加密解密、压缩解压等。

应用层:直接为用户或应用程序提供网络服务,如电子邮件、文件传输等。应用层是用户与网络的接口。

通过例子说明七层模型的作用

假设你在计算机上使用浏览器访问一个网站(比如打开一个网页),从输入网址到网页显示在屏幕上的过程,可以说明OSI模型各层的作用:

应用层:你在浏览器中输入网址并点击“回车”,浏览器(作为应用层)生成一个HTTP请求,这个请求包含你想要访问的网页地址。

表示层:在发送HTTP请求之前,表示层负责将请求数据转换为可以被网络传输的格式,可能还会进行数据的压缩和加密。

会话层:表示层之后,会话层建立一个通信会话,这个会话保证请求和响应之间的数据交换有序且同步。

传输层:会话层之后,传输层将数据分成更小的数据段(如TCP段),并为每个数据段加上端口号,以确保数据能够传输到正确的应用程序。

网络层:传输层之后,网络层根据目标网址的IP地址(通过DNS解析获取)为每个数据段加上源和目标IP地址,并选择最优路径将数据段发送到目标服务器。

数据链路层:网络层之后,数据链路层将数据段封装成帧,并通过物理介质(如网线)传输。数据链路层还负责检测和纠正物理层传输中可能出现的错误。

物理层:最终,物理层将数据帧转化为电信号,通过网络电缆或无线信号发送到目标服务器的物理设备。

目标服务器接收到这些信号后,会逆向处理这些层,从物理层到应用层,将最终的HTTP响应发送回你的浏览器,浏览器再将网页显示在屏幕上。

1.UDP头部格式

UDP的头部比较简单,只有8个字节,这也是为什么UDP不能像TCP那样实现可靠传输的原因。源端口和目标端口表示数据传输的来源和去向,包长度表示数据报文的总长度(包含了头部和数据部分),方便接收方正确地处理数据。检验和则是用于验证UDP数据报在传输过程中是否发生了错误。它帮助确保数据的完整性。

2.TCP头部格式

TCP的头部一般是20个字节,复杂的头部决定了它的可靠传输。校验和保证了数据的完整性和错误检测;序列号进行了数据的顺序控制;ACK实现了重传机制,确认和重传;滑动窗口进行流量控制;拥塞控制;三次挥手和四次握手进行连接管理。

序列号解决网络乱序问题,确认应答号解决丢包问题,窗口大小用来进行流量控制,控制位(ACK确认和重传、FIN断开连接、SYN请求连接)。

3.IP地址

IPV4地址,四位,主机号全为0的是网络地址,全为1的是广播地址。A类,第一位范围1-126,127是本地环回测试地址。后三位范围是1-2^24-1,全1表示广播地址。

网络号是IP地址中的一部分,用于标识某个特定的网络。相同网络号的设备处于同一网络中。主机号是IP地址中的另一部分,用于标识网络内的具体设备(主机)。网络地址是指IP地址中主机号部分全为0的地址,它标识的是整个网络,而不是网络中的具体设备。广播地址是指IP地址中主机号部分全为1的地址,用于向网络中的所有设备发送信息。

4.流量控制

滑动窗口机制中的流量控制主要依赖接收方的接收窗口大小来调节数据传输速率。

接收窗口大小的动态调整

接收方在接收到数据包后,会根据当前缓冲区的可用空间和处理能力来决定是否调整接收窗口的大小。如果接收方的缓冲区空间充足且处理能力良好,它可能会增大接收窗口的大小,允许发送方发送更多的数据包。反之,如果接收方的缓冲区接近满载,它会缩小接收窗口,以减缓发送方的数据传输速度。

通过ACK通知窗口大小的变化

每当接收方收到数据包并处理完毕后,它会发送一个ACK(Acknowledgment,确认)消息回给发送方。这个ACK消息不仅确认了已成功接收的数据包,还携带了一个重要的字段:接收窗口大小(Receive Window Size)。这个字段明确告知发送方,接收方当前还能接收的数据量是多少。比如,如果接收窗口大小是500字节,发送方就知道它最多还能发送500字节的数据而不必等待进一步的ACK。如果接收方的缓冲区快要满了,它会在ACK消息中报告一个较小的接收窗口大小,通知发送方降低数据传输速率,避免缓冲区溢出。

5.拥塞控制

慢开始、拥塞避免、快重传、快恢复。

TCP发送方一开始使用慢开始算法,让拥塞窗口值从1开始按指数规律增大,当拥塞窗口值增大到慢开始门限值时,执行拥塞避免算法,让拥塞窗口值按线性加1的规律增大,当发生超时重传时,就判断网络很可能出现了拥塞,这时将慢开始门限值更新为发生拥塞时拥塞窗口值的一半,并将拥塞窗口值减少为1,并重新执行慢开始算法,拥塞窗口值又从1开始按指数规律增大,当增大到了新的慢开始门限值时,停止使用慢开始算法,执行拥塞避免算法。

后来又加入了快重传和快恢复,快重传是指收到三个重复确认。快恢复是指快重传之后门限值更新发生拥塞时拥塞窗口值的一半,并将拥塞窗口值也更新为发生拥塞时拥塞窗口值的一半,而不是更新为1。

6.三次握手

第一次握手发送端向接收端发送请求连接SYN,接收端收到,并且得知发送端具有发送能力。第二次握手接收端向发送端发送确认连接ACK,发送端得知接收端具有接受能力,并且向发送端发送请求连接,发送端收到,得知接收端具有发送能力。第三次握手,发送端向接收端发送确认连接,接收端得知发送端具有接收能力。这样一来,两端都知道对方具有发送和接收能力,两端建立连接可以正常的传送数据。三次握手才能确认双方的接收和发送能力是否正常。

如果是两次握手,如果某次发送的报文因为网络延误了,又重新发送了一次连接,延误的发送在重新的连接释放之后才到达服务端,这时候服务端会认为发送端又发送了一次连接请求,于是向发送端发送确认请求,同意建立连接,但客户端实际并未发送,所以忽略请求,于是服务端一直等待,浪费资源。

不采用两次或者四次:避免历史连接、避免资源浪费、确认对方收发能力。

7.四次挥手

通过四次挥手的过程,TCP协议确保了双方都能够正常地关闭连接,并且所有未完成的数据传输都能顺利结束。第一次挥手:客户端发送FIN,表示没有数据要发送了。第二次挥手:服务器发送ACK,确认收到FIN请求。第三次挥手:服务器发送FIN,表示没有数据要发送了。第四次挥手:客户端发送ACK,确认收到服务器的FIN请求,连接关闭。

8.HTTP请求方法

应用层产生的请求报文里面会含有请求行、请求头、请求体(post会有,get没有),

请求行:包括请求方法、请求 URI 和 HTTP 版本。请求头:提供关于请求的附加信息,例如主机名、用户代理、接受的内容类型等。请求体:包含要发送给服务器的数据,适用于需要提交数据的请求方法如 POSTPUT

GET  POST  PUT  PATCH  TRACE  OPTIONS  CONNECT  HEAD  DELETE 

9.HTTP状态码

1XX  信息性状态码  接收的请求正在处理,正常,继续;

2XX  成功状态码  请求正常处理完毕; 200   204   206

3XX  重定向状态码   资源位置发生变化,需要新的URL; 301   302  304

4XX  客户端错误状态码  服务端无法处理客户端的请求;400   403   404 

5XX  服务端错误状态码  服务端在处理请求时自身错误; 500  501  502  503

10.HTTP协议的几个版本

HTTP1.0  短连接,请求响应模型,多媒体 (支持文本、音视频等),简单易用,扩展性,但是短连接导致效率低、延迟高、浪费资源,无持久连接导致没法在同一个连接中处理多个请求;

HTTP1.1  持久连接,请求管道化,缓存控制,性能提升,带宽利用率高,更灵活的请求处理,但是管道化局限,队头阻塞;

HTTP2  二进制格式,头部压缩,多路复用(流),服务器推送,但复杂性增加,基于TCP(仍受到握手、丢包重传等限制);

HTTP3  基于QUIC协议,更快的连接建立,改进的流量控制,内建的加密,性能卓越,但是依赖基础设施,实现复杂。QUIC允许对每个流单独进行流量控制,而不仅仅是针对整个连接进行流量控制。这样可以更好地适应不同流的数据传输需求,提高传输效率。

11.GET和POST的区别

1. 用途:GET用来请求数据,POST修改/提交数据;

2. 数据传输方式:URL传递,请求体传递;

3. 安全性:URL上数据可见,请求体内稍微安全一些,但是都不安全,HTTP明文;

4. 幂等性:GET每次请求都会得到相同的数据,POST每次创建则会产生不同的资源;

5. 数据大小限制:URL长度限制,请求体可以大数据;

6. 缓存:GET每次请求可以缓存下来,方便下次请求,POST每次创建不同,不缓存;

7. 语义安全:GET修改服务器数据,POST不修改。

12.HTTP缓存方式

1. 强制缓存(HTTP1.0  Expires 头部字段,绝对时间,HTTP1.1 Cache-Control 头部字段,更灵活)

优点是在缓存有效期内,浏览器无需与服务器通信,直接使用本地缓存资源,提升加载速度。缺点是如果资源在缓存期间发生了变化,用户可能无法及时获取到最新的内容。

2. 协商缓存是指浏览器每次请求资源时,都会先向服务器询问资源是否已经更新,如果没有更新,则继续使用缓存。如果更新了,则下载新的资源。

优点是既能减少不必要的数据传输,又能保证获取最新的资源。缺点是相比强制缓存,每次请求都需要与服务器进行验证,带来了一定的延迟。

13.HTTP和HTTPS

区别:安全、连接、端口、证书

1. HTTP超文本传输协议,明文,不安全。HTTPS在TCP和HTTP之间加入了SSL/TLS安全协议,使得报文能够加密安全传输。

2. HTTP只需要三次握手就能建立连接,HTTPS则还需要SSL/TLS握手。

3. 端口号80与443。

4. HTTPS需要向CA申请身份证书,来保证服务器是安全可靠的。

14.HTTPS能解决什么问题

信息加密解决安全机密,校验机制解决篡改信息,身份证书解决不可信服务器。

15.为什么每次建立 TCP 连接,初始化的序列号要求不一样呢?

主要原因有两个方面:

为了防止历史报文被下一个相同四元组的连接接收(主要方面);为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收;

客户端和服务端建立一个 TCP 连接,在客户端发送数据包被网络阻塞了,然后超时重传了这个数据包,而此时服务端设备断电重启了,之前与客户端建立的连接就消失了,于是在收到客户端的数据包的时候就会发送 RST 报文。紧接着,客户端又与服务端建立了与上一个连接相同四元组的连接;在新连接建立完成后,上一个连接中被网络阻塞的数据包正好抵达了服务端,刚好该数据包的序列号正好是在服务端的接收窗口内,所以该数据包会被服务端正常接收,就会造成数据错乱。可以看到,如果每次建立连接,客户端和服务端的初始化序列号都是一样的话,很容易出现历史报文被下一个相同四元组的连接接收的问题

16.第一次、第二次、第三次握手丢失了分别会发生什么?

第一次握手丢失:超时重传,而且重传的 SYN 报文的序列号都是一样的。每次等待几秒,下一次重传的等待时间是这次的两倍,直至重传上限次数,断开连接。

第二次握手丢失:客户端和服务端都会重传,客户端会重传 SYN 报文,也就是第一次握手,最大重传次数由 tcp_syn_retries内核参数决定;服务端会重传 SYN-ACK 报文,也就是第二次握手,最大重传次数由 tcp_synack_retries 内核参数决定。

第三次握手丢失:ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。

17.什么是 SYN 攻击?如何避免 SYN 攻击?

半连接队列:服务器第一次收到客户端的SYN之后,就会处于SYN_RCVD状态,此时双方还没有完全建立连接,服务器会把这种状态下的请求连接放在一个队列里,称为半连接队列。完成三次握手建立起连接的就会放入全连接队列中。

SYN攻击是客户端短时间内伪造大量不存在的IP地址,并向服务器不断发送SYN包,服务器回复确认包,并且等待客户端确认,由于源地址不存在,因此服务器不断重发直至超时,这些伪造的SYN包将长时间占用半连接队列,导致正常的SYN请求因队列满而丢弃,从而引起网络拥塞甚至瘫痪。

防御方法:缩短超时时间,增大半连接队列,防火墙过滤,SYN Cookies(是一种在服务器接收到 SYN 请求时,不立即为其分配资源,而是将请求信息编码到 TCP 序列号中的技术。只有当客户端发送 ACK 确认时,服务器才会验证这个序列号并为连接分配资源。),SYN 速率限制。

18.四次挥手丢失分别会发生什么?

第一次挥手丢失:客户端迟迟收不到被动方的 ACK 的话,触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。如果还是没能收到服务端的第二次挥手(ACK报文),那么客户端就会断开连接。

第二次挥手丢失:ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。

第三次挥手丢失:如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。

第四次挥手丢失:如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。

也就是说,四次丢失导致的结果都是FIN重传。

19.为什么 TIME_WAIT 等待的时间是 2MSL?

四次挥手开始时,客户端和服务器都是连接已建立状态,客户端向服务器发送一个终止信号FIN,进入终止等待1,服务器收到后发送一个确认信号ACK,进入关闭等待状态,客户端收到后进入终止等待2,整个系统进入半连接状态,服务器向客户端发送FIN和ACK,ACK是对第一次挥手的重复确认,进入最后确认状态,客户端收到后发送ACK,进入时间等待状态(2MSL),服务器收到后进入关闭状态,2MSL之后客户端也进入关闭状态。至于为什么是2MSL,2倍的最大报文生存时间,是为了确保ACK能够到达服务器 ,以使其进入关闭状态,如果丢失能够重传,时间重新计时。

需要 TIME-WAIT 状态,主要是两个原因:

防止历史连接中的数据,被后面相同四元组的连接错误的接收;(这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。)保证「被动关闭连接」的一方,能被正确的关闭。

20.一些连接问题

服务器出现大量 TIME_WAIT 状态的原因有哪些?说明服务器主动断开了很多 TCP 连接,什么场景下服务端会主动断开连接呢?第一个场景:HTTP 没有使用长连接;第二个场景:HTTP 长连接超时;第三个场景:HTTP 长连接的请求数量达到上限。

服务器出现大量 CLOSE_WAIT 状态的原因有哪些?

如果已经建立了连接,但是客户端突然出现故障了怎么办?保活机制:定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。

如果已经建立了连接,但是服务端的进程崩溃会发生什么?TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。

保活机制根本上是断开空闲的TCP连接的。如果两端的 TCP 连接一直没有数据交互,达到了触发 TCP 保活机制的条件,那么内核里的 TCP 协议栈就会发送探测报文。

TCP 连接,一端断电和进程崩溃有什么区别?一端断电触发保活机制,进程崩溃不同。TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP四次挥手的过程。

拔掉网线几秒,再插回去,原本的 TCP 连接还存在吗?如果有数据传输,只要及时就会赶上超时重传。如果没有就触发保活机制,没有及时的话会断开连接。

HTTPS 中 TLS 和 TCP 能同时握手吗?不管 TLS 握手次数如何,都得先经过 TCP 三次握手后才能进行,因为 HTTPS 都是基于 TCP 传输协议实现的,得先建立完可靠的 TCP 连接才能做 TLS 握手的事情。前两次握手并不能携带数据,第三次可以。

TCP Keepalive 和 HTTP Keep-Alive 是一个东西吗?

HTTP 的 Keep-Alive,是由应用层(用户态) 实现的,称为 HTTP 长连接;TCP 的 Keepalive,是由 TCP 层(内核态) 实现的,称为 TCP 保活机制;

TCP 协议有哪些缺陷?主要有四个方面:升级 TCP 的工作很困难;TCP 建立连接的延迟;TCP 存在队头阻塞问题;网络迁移需要重新建立 TCP 连接。

服务端没有 listen,客户端发起连接建立,会发生什么?服务端如果只 bind 了 IP 地址和端口,而没有调用 listen 的话,然后客户端对服务端发起了连接建立,服务端会返回 RST 报文。

不使用 listen ,可以建立 TCP 连接吗?可以的,客户端是可以自己连自己的形成连接(TCP自连接),也可以两个客户端同时向对方发出请求建立连接(TCP同时打开),这两个情况都有个共同点,就是没有服务端参与,也就是没有listen,就能建立连接。

没有 accept,能建立 TCP 连接吗?就算不执行accept()方法,三次握手照常进行,并顺利建立连接。半连接队列(SYN队列),服务端收到第一次握手后,会将sock加入到这个队列中,队列内的sock都处于SYN_RECV 状态。全连接队列(ACCEPT队列),在服务端收到第三次握手后,会将半连接队列的sock取出,放到全连接队列中。队列里的sock都处于 ESTABLISHED状态。这里面的连接,就等着服务端执行accept()后被取出了。建立连接的过程中根本不需要accept()参与,执行accept()只是为了从全连接队列里取出一条连接。全连接队列(icsk_accept_queue)是个链表,而半连接队列(syn_table)是个哈希表。出于效率考虑设计的,复杂度尽量低。全连接队列:服务端取走连接的过程中,并不关心具体是哪个连接,只要是个连接就行,所以直接从队列头取就行了。这个过程算法复杂度为O(1)。半连接队列:有一个第三次握手来了,则需要从队列里把相应IP端口的连接取出,哈希表,那么查找半连接的算法复杂度就回到O(1)了。

总结:

每一个socket执行listen时,内核都会自动创建一个半连接队列和全连接队列。第三次握手前,TCP连接会放在半连接队列中,直到第三次握手到来,才会被放到全连接队列中。accept方法只是为了从全连接队列中拿出一条连接,本身跟三次握手几乎毫无关系。出于效率考虑,虽然都叫队列,但半连接队列其实被设计成了哈希表,而全连接队列本质是链表。全连接队列满了,再来第三次握手也会丢弃,此时如果tcp_abort_on_overflow=1,还会直接发RST给客户端。半连接队列满了,可能是因为受到了SYN Flood攻击,可以设置tcp_syncookies,绕开半连接队列。客户端没有半连接队列和全连接队列,但有一个全局hash,可以通过它实现自连接或TCP同时打开。

TCP 是一个可靠的传输协议,那它一定能保证数据不丢失吗?数据从发送端到接收端,链路很长,任何一个地方都可能发生丢包,几乎可以说丢包不可避免。TCP只保证传输层的消息可靠性,并不保证应用层的消息可靠性。

21.针对 TCP 应该如何 Socket 编程?

没有 accept,能建立 TCP 连接吗?accpet 系统调用并不参与 TCP 三次握手过程,它只是负责从 TCP 全连接队列取出一个已经建立连接的 socket,用户层通过 accpet 系统调用拿到了已经建立连接的socket,就可以对该 socket 进行读写操作了。

22.TCP的一些点

超时重传时间RTO要略微大于报文往返时间RTT.

三次同样的ACK就会触发快速重传。

窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值

SYN 报文什么时候情况下会被丢弃?1. 如果发现收到的数据包中时间戳不是递增的,则表示该数据包是过期的,就会直接丢弃这个数据包。2. 当服务器造成syn攻击,就有可能导致 TCP 半连接队列满了,这时后面来的 syn 包都会被丢弃。但是,如果开启了syncookies 功能,即使半连接队列满了,也不会丢弃syn 包。3. 在服务端并发处理大量请求时,如果 TCP accpet 队列过小,或者应用程序调用 accept() 不及时,就会造成 accpet 队列满了 ,这时后续的连接就会被丢弃,这样就会出现服务端请求数量上不去的现象。

在 TCP 正常挥手过程中,处于 TIME_WAIT 状态的连接,收到相同四元组的 SYN 后会发生什么?如果处于 TIME_WAIT 状态的连接收到「合法的 SYN 」后,就会重用此四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。如果处于 TIME_WAIT 状态的连接收到「非法的 SYN 」后,就会再回复一个第四次挥手的 ACK 报文,客户端收到后,发现并不是自己期望收到确认号,就回 RST 报文给服务端RST标志用于强制关闭一个TCP连接。

23.TCP面向字节流,粘包和拆包

TCP 是面向字节流的协议,UDP 是面向报文的协议。是因为操作系统对 TCP 和 UDP 协议的发送方的机制不同。当用户消息通过 UDP 协议传输时,操作系统不会对消息进行拆分每个 UDP 报文就是一个用户消息的边界。当用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个的 TCP 报文。因此,我们不能认为一个用户消息对应一个 TCP 报文,正因为这样,所以 TCP 是面向字节流的协议。当两个消息的某个部分内容被分到同一个 TCP 报文时,就是我们常说的 TCP 粘包问题,这时接收方不知道消息的边界的话,是无法读出有效的消息。粘包的问题出现是因为不知道一个用户消息的边界在哪,所以解决办法是:固定长度的消息、特殊字符作为边界、长度前缀、自定义协议来明确数据包的结构和边界。

24. 如何基于 UDP 协议实现可靠传输?

 TCP 可靠传输的特性(序列号、确认应答、超时重传、流量控制、拥塞控制)

QUIC 是如何实现可靠传输的?

QUIC 通过单向递增的 Packet Number,配合 Stream ID 与 Offset字段信息,可以支持乱序确认而不影响数据包的正确组装,摆脱了TCP 必须按顺序确认应答 ACK 的限制,解决了 TCP 因某个数据包重传而阻塞后续所有待发送数据包的问题。

QUIC 是如何解决 TCP 队头阻塞问题的?

QUIC 给每一个 Stream 都分配了一个独立的滑动窗口,这样使得一个连接上的多个 Stream 之间没有依赖关系,都是相互独立的,各自控制的滑动窗口

QUIC 是如何做流量控制的?

Stream 级别的流量控制:Stream 可以认为就是一条 HTTP 请求,每个 Stream 都有独立的滑动窗口,所以每个 Stream 都可以做流量控制,防止单个 Stream 消耗连接(Connection)的全部接收缓冲。Connection 流量控制:限制连接中所有 Stream 相加起来的总字节数,防止发送方超过连接的缓冲容量。

QUIC 对拥塞控制改进

QUIC 是处于应用层的,应用程序层面就能实现不同的拥塞控制算法,不需要操作系统,不需要内核支持。这是一个飞跃,因为传统的 TCP 拥塞控制,必须要端到端的网络协议栈支持,才能实现控制效果。而内核和操作系统的部署成本非常高,升级周期很长,所以 TCP 拥塞控制算法迭代速度是很慢的。而 QUIC 可以随浏览器更新,QUIC 的拥塞控制算法就可以有较快的迭代速度。TCP 更改拥塞控制算法是对系统中所有应用都生效,无法根据不同应用设定不同的拥塞控制策略。但是因为 QUIC 处于应用层,所以就可以针对不同的应用设置不同的拥塞控制算法,这样灵活性就很高了。

QUIC 更快的连接建立

 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果

QUIC 是如何迁移连接的?

基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立 TCP 连接。QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。

三、数据库

 1.MySQL部分

10.关系型数据库、非关系型数据库

关系型数据库一般将数据存储在表格中,核心思想是数据之间的关系可以通过表格中的键(如主键和外键)来表示和管理。如:MySQL。

非关系型数据库通常不使用表格结构,而是采用其他模型,如键值对、文档、图、列族等。如:Redis。NoSQL数据库在处理海量、快速变化的数据时表现出色。

11.基本写法

列出数据库:show databases;创建数据库:create database mysql_test;切换数据库:use mysql_test;列出表:show tables;创建表:create table student(s_id int,s_name varchar(8),s_birth date);表的列名在前,数据类型在后。在表中插入数据:insert into student values (1,'赵雷','1990-01-01'), (8,'王菊','1990-01-20');  

12.执行一条 SQL 查询语句,期间发生了什么?

连接器:建立连接,管理连接、校验用户身份;查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。解析 SQL,通过解析器对 SQL 查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型;执行 SQL:执行 SQL 共有三个阶段:1. 预处理阶段:检查表或字段是否存在;将 select * 中的 * 符号扩展为表上的所有列。2. 优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划。3. 执行阶段:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端。

13.索引部分

131.索引的定义

索引是数据的目录,就是帮助存储引擎快速获取数据的一种数据结构。存储引擎,说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。MySQL 存储引擎有 MyISAM 、InnoDB、Memory,其中 InnoDB 是在 MySQL 5.5 之后成为默认的存储引擎。索引和数据就是位于存储引擎中。

132.索引的分类

按「数据结构」分类:B+tree索引、Hash索引、Full-text索引。按「物理存储」分类:聚簇索引(主键索引)、二级索引(辅助索引)。按「字段特性」分类:主键索引、唯一索引、普通索引、前缀索引。按「字段个数」分类:单列索引、联合索引

为什么 MySQL InnoDB 选择 B+tree 作为索引的数据结构?这是因为:B+树和B树相比,B+Tree 只在叶子节点存储数据,而 B 树 的非叶子节点也要存储数据,所以 B+Tree 的单个节点的数据量更小,在相同的磁盘 I/O 次数下,就能查询更多的节点。另外,B+Tree 叶子节点采用的是双链表连接,适合 MySQL 中常见的基于范围的顺序查找,而 B 树无法做到这一点。B+树和二叉树相比,对于有 N 个叶子节点的 B+Tree,其搜索复杂度为O(logdN),其中 d 表示节点允许的最大子节点个数为 d 个。而二叉树的每个父节点的儿子节点个数只能是 2 个,意味着其搜索复杂度为 O(logN),这已经比 B+Tree 高出不少,因此二叉树检索到目标数据所经历的磁盘 I/O 次数要更多。B+树和Hash表相比, Hash 表不适合做范围查询,它更适合做等值的查询。

从物理存储的角度来看,索引分为聚簇索引(主键索引)、二级索引(辅助索引)。

主键索引的 B+Tree 的叶子节点存放的是实际数据,所有完整的用户记录都存放在主键索引的 B+Tree 的叶子节点里;二级索引的 B+Tree 的叶子节点存放的是主键值,而不是实际数据。

主键索引就是去用一个B+树去查主键字段,叶子节点存储的是整行数据,通过主键就可以获知全部信息,而二级索引则是去查其他字段,比如学生表中的姓名、成绩等列的字段,叶子节点存储的是该二级字段以及主键,通过二级字段可以获知主键,然后通过回表去查该主键对应的整行数据,所以这种方式可能需要两个B+树才能查到数据。

133.为什么索引会加快查询的速度?

134.什么时候需要索引?什么时候不需要索引?

135.优化索引的办法

前缀索引优化;覆盖索引优化;主键索引最好是自增的;防止索引失效;

136.为什么 MySQL 采用 B+ 树作为索引?

要设计一个适合 MySQL 索引的数据结构,至少满足以下要求:能在尽可能少的磁盘的 I/O 操作中完成查询工作;要能高效地查询某一个记录,也要能高效地执行范围查找。

1361.什么是二分查找?

索引数据最好能按顺序排列,这样可以使用「二分查找法」高效定位数据。

1362.什么是二分查找树?

用数组来实现线性排序的数据虽然简单好用,但是插入新元素的时候性能太低。二叉查找树的特点是一个节点的左子树的所有节点都小于这个节点,右子树的所有节点都大于这个节点。当每次插入的元素都是二叉查找树中最大的元素,二叉查找树就会退化成了一条链表,查找数据的时间复杂度变成了 O(n)。由于树是存储在磁盘中的,访问每个节点,都对应一次磁盘 I/O 操作(假设一个节点的大小「小于」操作系统的最小读写单位块的大小),也就是说树的高度就等于每次查询数据时磁盘 IO 操作的次数,所以树的高度越高,就会影响查询性能。

1363.什么是自平衡二叉树?

主要是在二叉查找树的基础上增加了一些条件约束:每个节点的左子树和右子树的高度差不能超过 1不管平衡二叉查找树还是红黑树,都会随着插入的元素增多,而导致树的高度变高,这就意味着磁盘 I/O 操作次数多,会影响整体数据查询的效率当树的节点越多的时候,并且树的分叉数 M 越大的时候,M 叉树的高度会远小于二叉树的高度

1364.什么是 B 树

为了解决降低树的高度的问题,后面就出来了 B 树,它不再限制一个节点就只能有 2 个子节点,而是允许 M 个子节点 (M>2),从而降低树的高度。但是 B 树的每个节点都包含数据(索引+记录),而用户的记录数据的大小很有可能远远超过了索引数据,这就需要花费更多的磁盘 I/O 操作次数来读到「有用的索引数据」。

1365.什么是 B+ 树?

B+ 树与 B 树差异的点,主要是以下这几点:

叶子节点(最底部的节点)才会存放实际数据(索引+记录),非叶子节点只会存放索引;所有索引都会在叶子节点出现,叶子节点之间构成一个有序链表;非叶子节点的索引也会同时存在在子节点中,并且是在子节点中所有索引的最大(或最小);非叶子节点中有多少个子节点,就有多少个索引;

所以,性能差距如下:

B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少

B+ 树的插入和删除效率更高

B+ 树所有叶子节点间还有一个链表进行连接,这种设计对范围查找非常有帮助。

14.事务部分

141.什么是事务?事务的特性?

事务是数据库操作的基本单元,事务内的语句具有一致性,要么全部执行成功,要么全部执行失败。事务的四个特性ACID:原子性,一致性,隔离性,持久性。原子性要么全成功,要么全失败。一致性数据库状态要保持一致性。隔离性并发进程不会互相影响。持久性事务结束操作之后数据库的修改永久保存。

142.并行事务会引发什么问题?

在同时处理多个事务的时候,就可能出现脏读、不可重复读、幻读的问题

如果在上面这种情况事务 A 发生了回滚,那么事务 B 刚才得到的数据就是过期的数据,这种现象就被称为脏读。

脏读:读到其他事务未提交的数据;不可重复读:前后读取的数据不一致;幻读:前后读取的记录数量不一致。

143.事务的隔离级别有哪些?

SQL 标准提出了四种隔离级别来规避这些现象,隔离级别越高,性能效率就越低,这四个隔离级别如下:

读未提交(read uncommitted,指一个事务还没提交时,它做的变更就能被其他事务看到;读提交(read committed,指一个事务提交之后,它做的变更才能被其他事务看到;可重复读(repeatable read,指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别串行化(serializable );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;

15.锁部分

151.MySQL 有哪些锁?

全局锁、表级锁(表锁、元数据锁、意向锁、AUTO-INC 锁)和行锁(Record Lock,记录锁、Gap Lock,间隙锁、Next-Key Lock,两锁组合

16.MySQL如何调优?

建立索引:加速数据访问和操作。索引优化查询优化事务和锁覆盖查询:减少数据读取,提高查询效率。避免索引失效:确保索引能够被有效利用,避免性能下降。

2.Redis部分

四、C++、数据结构与算法

1.内存基础

11.内存分区

代码区:存储可执行代码(程序指令)。

全局区:存储全局变量和静态变量(已初始化和未初始化)。

堆区:用于动态内存分配,由程序员管理。

栈区:存储函数调用中的局部数据(局部变量、参数、返回地址)。

12.堆和栈有什么区别

堆和栈是用来内存分配和释放的两种不同的内存区域,内存管理就是内存分配和释放。栈的存储空间比较小,用于存储局部变量、函数参数、返回值,适合小数据结构的存储,内存分配和释放速度快,自动管理(自动分配、自动销毁)。堆的存储空间较大,用于动态内存分配,适合大型数据结构存储,内存分配和释放速度较慢,手动管理(malloc、new、free、delete)。

2.指针

21.智能指针

智能指针是C++标准库中的一种工具,用于自动管理动态内存,避免内存泄漏和指针悬挂问题。智能指针主要有以下几种类型:

1. std::unique_ptr

独占所有权unique_ptr表示独占的所有权,即一个指针对象在任何时刻只有一个所有者。不可复制unique_ptr不能被复制,只有通过std::move转移所有权。场景:适合用于需要严格的所有权语义的场合,如资源管理。

2. std::shared_ptr

共享所有权shared_ptr允许多个智能指针共享同一块内存。当最后一个shared_ptr释放时,内存才会被释放。引用计数:每个shared_ptr都有一个引用计数,用于记录有多少shared_ptr共享该内存块。场景:适用于需要在多个地方共享资源的情况。

3. std::weak_ptr

弱引用weak_ptr不会影响引用计数,不能直接访问资源,需要先提升为shared_ptr防止循环引用:常用于打破shared_ptr之间的循环引用,避免内存泄漏。场景:适用于需要观察但不拥有资源的场景,如缓存或观察者模式中。

智能指针的作用

自动内存管理:智能指针在离开作用域时自动释放资源,避免了手动释放内存的麻烦和错误。防止内存泄漏:通过智能指针的自动内存管理机制,减少了忘记释放内存导致的内存泄漏问题。简化代码:智能指针封装了内存管理细节,简化了资源管理的代码。

22.常量指针和指针常量有什么区别?

看const后面跟的什么,跟的int就是修饰数据,数据不可变,指针指向可变,称为指针变量const int *p。跟的p就是修饰指针,指针指向不可变,数据可变,称为变量指针int * const p。

23.define 和 const 的区别?

作用:用于记录程序中不可更改的数据

C++定义常量两种方式

1. #define 宏常量: #define 常量名 常量值

==通常在文件上方定义==,表示一个常量

2. const修饰的变量 const 数据类型 常量名 = 常量值

==通常在变量定义前加关键字const==,修饰该变量为常量,不可修改

24.static的作用

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员,可以通过对象和类名两种方式访问。静态成员分为:

静态成员变量:1.所有对象共享同一份数据;2.在编译阶段分配内存;3.类内声明,类外初始化;

静态成员函数:1.所有对象共享同一个函数;2.静态成员函数只能访问静态成员变量。(因为静态属于类,非静态属于对象 ,静态成员变量是由类调用的,不用区分是类中的哪个对象调用,但是非静态成员变量是由对象调用的,这时候用静态成员函数去调用非静态成员变量,无法区分到底是哪个对象调用的变量)

静态成员是指属于类本身而不是类的实例的成员。静态成员有以下意义:

共享性:静态成员被所有类的实例共享,可以在不创建类的实例的情况下访问和修改静态成员。

保存状态:静态成员可以用来保存类的状态信息,例如计数器、配置信息等,这些信息可被所有实例共享和访问。

简化访问:可以通过类名直接访问静态成员,无需创建类的实例。这样可以简化代码并提高代码的可读性。

与类相关联:静态成员通常与类本身相关联,而不是与类的实例相关联。这样可以更好地表达类的特性和行为。

3.虚函数

31.什么是虚函数?什么是纯虚函数?

虚函数 用于在基类中声明,并允许在派生类中重写,以实现多态性。纯虚函数 是没有实现的虚函数,存在于抽象类中,必须在派生类中实现,否则派生类也将成为抽象类。纯虚函数常用于定义接口,强制派生类提供特定的功能。

析构函数可以是虚函数吗?

4.vector容器

std::vector 是 C++ 标准库中的动态数组,它会根据需要自动调整其容量以容纳更多的元素。std::vector 会在内部维护一个存储空间的容量(capacity),这个容量通常会大于或等于当前元素的数量(size)。当向 std::vector 中添加元素并且元素的数量超过当前容量时,vector 会重新分配更大的内存空间来容纳这些元素。这个过程包括:

分配新的内存块vector 会分配一个更大的内存块以容纳更多的元素。复制现有元素:将旧内存块中的元素复制到新的内存块中。释放旧内存块:释放旧的内存块以释放内存。

std::vector 的初始容量和扩容策略取决于具体的实现,但一般来说,std::vector 的初始容量为 0。也就是说,当你创建一个空的 vector 对象时,它通常没有分配任何内存用于存储元素。

初次扩容

std::vector 的初次扩容通常发生在以下几种情况:

添加元素:当你向一个空的 vector 中添加第一个元素时,vector 会进行初次扩容。此时,它会分配一个足够容纳至少一个元素的内存块。许多实现将初始容量设置为较小的值,比如 1 或 4,以提高性能。

构造函数:如果你使用特定构造函数创建 vector,比如 vector(size_type count, const T& value)vector(std::initializer_list<T> init)vector 会根据提供的初始元素数量预分配内存,以减少后续的扩容次数。

扩容策略

在添加更多元素并超过当前容量时,std::vector 会进行扩容。扩容通常遵循以下策略:

容量增加:每次扩容时,vector 的容量通常会增加到当前容量的两倍,这样可以减少扩容的次数和内存分配的开销。(2倍策略通常在性能和内存利用之间提供了一个好的折衷)策略实现:不同的标准库实现可能会有不同的策略,但大多数都遵循类似的增量规则以确保高效的内存管理。

扩容过程中,std::vector 需要重新分配内存,复制现有元素到新的内存块中,并释放旧的内存块。这个过程是自动进行的,用户不需要手动处理。

5.介绍几种常用的排序算法

冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。