操作系统阻塞IO和非阻塞IO阻塞的文件描述符为阻塞IO非阻塞的文件描述符为非阻塞IO同步IO和异步IO同步IO向应用程序通知的是IO就绪事件。要求用户代码自行执行读写操作，将数据从内核缓冲区读入用户缓冲区。异步IO向应用程序通知的是IO完成事件 。由内核来执行IO读写操作。在linux环境下，aio.h头文件定义的函数提供了对异步IO的支持。事件处理模式reactor 同步IO模型通常用于实现reactor模式。要求主线程只负责监听文件描述符是否有事件发生，有的话就立即将该事件通知工作线程。proactor 异步IO模型通常用于实现proactor模式。也可以用同步IO模拟出proactor模式。proactor将所有IO操作都交给主线程和内核来处理，工作线程仅仅负责业务逻辑。Reactor模式的工作流程主线程往epoll内核事件表中注册socket上的就绪事件。主线程调用epoll_wait等待socket上有数据可读。当socket上有数据可读时，epoll_wait通知主线程。主线程将socket可读事件放入请求队列。睡眠在请求队列上的某个工作线程被唤醒，它从socket读取数据，并处理客户请求，然后往epoll内核事件表中注册该socket上的写就绪事件。主线程调用epoll_wait等待socket可写。当socket可写时，epoll_wait通知主线程。主线程将socket可写事件放入请求队列。睡眠在请求队列上的某个工作线程被唤醒，它往socket上写入服务器处理客户请求的结果。并发模式半同步半异步模式：同步线程用于处理客户逻辑，异步线程用于处理IO事件。异步线程监听到客户请求后，就将其封装成请求对象并插入到请求队列中，请求队列将通知某个工作在同步模式下的工作线程来读取并处理该请求对象。半同步半反应堆模式采用的事件处理模式是reactor模式：它要求工作线程自己从socket上读取客户请求和往socket写入服务器应答。半同步半反应堆也可以模拟proactor模式，即由主线程来完成数据的读写。在这种情况下，主线程会将应用程序数据，任务类型等信息封装为一个任务对象，然后将其插入请求队列。工作线程从请求对象取得任务对象以后，可直接处理无需执行读写操作。问题：主线程和工作线程共享请求队列需要加锁。工作线程较少时可能产生请求任务堆积。领导者追随者模式在IO模型中，同步和异步区分的是内核向应用程序通知的是何种事件，是就绪事件还是完成事件，以及该由谁来完成IO读写，是应用程序还是内核。在并发模式中，同步指的是程序完全按照代码序列的顺序执行。异步指的是程序的执行需要由系统事件来驱动。常见的系统事件包括中断和信号。虚拟地址空间虚拟地址是操作系统管理内存的一种方式。方便不同进程使用的虚拟地址彼此隔离。方便物理内存中不相邻的内存在虚拟地址上视为连续的来使用。虚拟地址和物理地址的映射是通过MMU页表进行的。虚拟内存对实际内存有保护作用。什么是进程进程是系统进行资源分配的基本单位，是程序加载到内存后的执行过程。进程一般由数据段，代码段和进程控制块三部分组成。系统通过进程控制块感知进程的存在并对进程进行控制。由于进程之间空间相互独立，多进程比多线程更安全，一个进程基本上不会影响另外一个进程。进程三种状态创建：创建PCB就绪运行阻塞终止: 归还PCB什么是线程线程是CPU调度的基本单位。一个进程可以包含多个线程，线程自己基本不拥有系统资源，但是它可以和同属于一个进程的其他线程共享进程所拥有的全部资源。多线程之间对内存共享，线程间通信可以直接基于共享内存来实现，比多进程之间通信更轻量。多线程之间切换不需要切换虚拟内存空间、文件描述符等，所以线程的上下文切换也比多进程轻量。进程fork以后，遵循读时共享写时复制的机制。父子进程长期共享：文件描述符和mmap建立的映射区。子进程的进程ID，定时器，未决信号集和父进程不同。多进程和多线程的应用场景一般不同任务间需要大量的通信，使用多线程的场景比多进程多。IO密集型。但是多进程有更高的容错性，一个进程的崩溃不会导致整个系统的崩溃，在任务安全性较高的情况下，采用多进程。CPU密集型。## 进程线程的本质区别进程更安全，一个进程完全不会影响另外的进程。进程间通信比线程间通信的性能差很多。线程切换开销更低。IPC进程间通信55555555555无名管道pipe（血缘关系的进程）有名管道fifo （无血缘关系的进程）共享内存信号(开销小)消息队列信号量套接字进程间同步文件锁信号量线程间同步互斥锁读写锁(读时共享，写时互斥)条件变量信号量(互斥锁的升级版)自旋锁(可以避免进程或线程上下文的开销)线程共享资源文件描述符表（打开的文件）进程用户ID和进程组ID进程的内存地址空间.text代码段 .data数据段 .bss heap堆区 全局变量 静态变量每种信号的处理方式进程的当前目录线程独享资源线程栈寄存器组的值线程ID错误返回码errno变量线程信号屏蔽字线程优先级进程调度方式抢占式：立马停止。非抢占式：时间片用完或者等待资源时，再调用另一个进程。进程调度算法先来先服务短作业优先优先级调度时间片轮转高响应比优先管道管道是一种伪文件，实质为内核缓冲区 大小为4K 内核借用环形队列实现管道是半双工的，数据只能单向流动，不可重复读取，只能用于有血缘关系的进程Linux命令find命令，用来查找文件。常用的按照名字查找-name，按照文件类型查找-type，linux常用的文件类型有七种，普通文件，目录文件，管道，套接字，软链接，块设备，字符设备。还可以按照文件大小查询-size。grep命令，按照文件内容来查找。使用规则是grep option pattern fileps auxcurl命令 访问一个网页df查看磁盘大小du查看目录大小free -h 查看内存大小和使用情况top查看系统的实时负载netstat -ta 查看监听的TCPstat 获取文件属性file 查看文件类型sudo iptables -L 查看防火墙状态sudo vim etc/sysctl.conf 查看TCP属性大端字节序和小端字节序大端字节序：网络字节序（高位存低位）小端字节序：主机字节序，现代PC机采用小端字节序（低位存低位，高位存高位）比如0x1f3f5f7f 地址0x1000 0x1001 0x1002 0x1003 大端法：7f存在0x1003 5f存0x1002 3f存0x1001 1f存0x1000 低存高 小端法：7f存在0x1000 5f存0x1001 3f存0x1002 1f存0x1003 低存低socket服务器端所用函数socket 创建socket文件描述符 bind 绑定IP和端口号 listen 监听 accept 接受连接 处理客户端的业务socket客户端所用函数socket 创建套接字文件描述符 bind 绑定IP和端口号（也可以隐式绑定） connect 尝试连接服务器 处理服务器端的业务五种网络IO模型同步阻塞IO同步非阻塞IOIO多路复用信号驱动IO异步IOselect与poll、epoll的各自的优缺点和区别select是跨平台的，windows、linux、unix系统下都有poll在linux和unix下有 epoll是linux特有，epoll的要义就是高效的监视多个socket多路IO监听时没有动静，监听会休眠监听。讲讲epoll的边沿触发和水平触发水平触发：如果epoll_wait缓冲区有数据则直接返回。边沿触发：如果一次没有读完epoll_wait缓冲区中的数据，则只有当另外有数据再写入时，才返回。 使用边沿触发和非阻塞IO来达到水平触发的效果，减少了epoll_wait的调用次数，提高了效率。 边沿触发可以只读取缓存区中前面的部分信息，进而分析后面的信息是否有用，如果无用则直接丢弃。Libevent库是一个开源的库，封装了socket和IO多路转接，用于高并发服务器的开发。跨平台可移植性好。跨平台，线程安全，基于reactor模式实现的高效网络库。协程协程是一种用户态的轻量级线程。协程的开销远远小于线程的开销。 协程是一种比线程更加轻量级的存在，一个线程可以拥有多个协程。 无论是进程还是线程，都是由操作系统所管理的。而协程不是被OS所管理，而完全是由程序所控制(也就是在用户态执行)。信号信号是一种不精确通信。常用的信号有SIGKILL 9 无条件终止信号，SIGSEGV 11 无效存储访问 SIGPOLL 8 轮询事件信号。信号有三种处理方式：忽略，捕获，默认。kill命令向进程发送信号什么是死锁因为资源调度的方式不合理或者资源的稀缺性，导致进程间的相互等待。死锁的四个必要条件：互斥条件，请求和保持条件，环路等待条件，不可剥夺条件。死锁的预防只要破坏死锁产生的四个必要条件。通常采用预先静态分配方法，可以破坏请求和保持条件。死锁的避免：采用银行家算法，只要系统处于安全状态，系统便可避免死锁。死锁的解决：撤销进程，剥夺资源。僵尸进程和孤儿进程僵尸进程：子进程死亡，而父进程没有进行回收 waitpid回收指定进程孤儿进程：父进程死亡，而子进程仍然存活，但是系统会让init进程领养孤儿进程。fork函数fork函数用来创建子进程 一次调用，两次返回。在父进程中返回子进程的PID，在子进程中返回0exec族在程序中调用另一个可执行程序，但是进程ID不改变。网络编程IO服务器通常需要处理三类事件：IO事件，信号及定时事件。事件处理模式：reactor和proactor同步IO模型通常用于实现reactor模式异步IO则用于实现proactor模式什么是reactor模式它要求主线程只负责监听文件描述符上是否有事件发生，有的话立即将该事件通知工作线程。读写数据，接受新的连接以及处理客户请求均在工作线程中完成。什么是proactor模式它将所有IO操作都交给主线程和内核来处理，工作线程仅仅负责业务逻辑。mmap存储映射将磁盘空间映射到进程空间，使进程可以采用指针的方式操作这段内存，而不用调用read和write函数。提高了读写的效率，同时也可以实现进程间的通信。异步IO原理底层将数据准备好后，内核会给进程发送一个异步通知信号SIGIO29通知进程，然后进程调用信号处理函数去读数据，没准备好，数据就忙自己的事情。select poll epollselect单个进程打开的文件描述符有上限，为1024或者2048。select对于有响应的事件需要轮询来查找满足要求的事件。每次调用select都需要把文件描述符集合从用户态拷贝到内核态。poll描述fd的集合是链式的，解决了打开文件描述符数量的限制。同样需要轮询满足事件的文件描述符。也需要进行用户态和内核态的文件描述符拷贝。poll是水平触发。epoll使用了mmap内存映射技术和红黑树的数据结构。通过三个函数来监听多个文件描述符，同时不随数量的上升效率呈线性的下降。mmap内存读写快于IO读写，及时共享映射内存的改变。计算机网络TCP头部16位源端口16位目的端口32位序号32位确认序号4位TCP头部长度 单位为4字节6位标志位16位滑动窗口16位校验和16位紧急指针注意：TCP的包没有IP地址，只有源端口和目的端口。一个TCP连接需要4个元组来表示一个连接。（源端口，目的端口，源IP，目的IP）为什么建立连接需要3次握手主要是初始化序列号和协商最大报文段长度。粘包问题udp不存在粘包的问题，因为udp是个数据包协议，也就是两段数据间有界限的。要么收不到，要么全收。产生粘包的原因：nagle算法为了改善网络传输效率，延迟发送数据。应用层由于某些原因不能及时取出TCP的数据，导致TCP缓冲区存放了多段数据。解决方式：封包和拆包。包头存放一个变量记录包体的长度。在所发送的内容前，加上发送内容的长度。HTTP原理http协议是应用层协议，通过请求响应的方式在客户端和服务器端进行通信。http协议是以明文的方式进行传输，并且是无状态的通信协议。http与https的区别传输方式：http是明文传输，极易被监听和篡改。而https加入了ssl层，数据经过了加密，从而保护了传输数据的隐私和完整性。身份认证： http没有身份认证，而https经过证书颁发机构的多重认证。连接端口：http为80 https为443实现成本：http基本没有成本，https需要申请证书，同时在加密解密上需要消耗更多的CPU资源，访问速度有可能降低。加锁的图标显示 谷歌和百度搜索的排名会对非https的排名有影响私钥能解密，但是不能确认是哪个客户端发送的消息，任何人都可以抵赖。为了防止抵赖，可以使用数字签名。https是http的安全版，在http的基础上增加了SSL安全层。基于性能的考虑，https一般使用非对称加密算法获得密钥，再用对称加密算法对消息内容进行加密。https发送请求的过程：第一步，客户端和服务器端交换SSL版本和加密组件列表，同时服务器端将密钥和签名证书发给客户端。第二步：客户端根据证书和密钥进行验证，通过以后协商传输的密钥。这一步使用非对称加密算法。第三步：当双方都获得密钥，且校验码没有问题。则进行TCP三次握手，此时采用对称加密算法，提高效率。http1.0 与 http1.1的区别长连接：HTTP 1.1支持长连接和请求的流水线处理，在一个TCP连接上可以传送多个HTTP请求和响应，减少了建立和关闭连接的消耗和延迟。带宽优化： HTTP/1.1中在请求消息中引入了range头域，它允许只请求资源的某个部分。新增响应状态码：100 continue 已经收到第一部分，正等待剩余部分。 101 switch protocols 服务器已确认切换协议。host头域：允许物理主机上多个虚拟主机共享一个IP缓存机制更灵活，新增control-cache头域增加了5个请求方法：put delete connect options traceget和post的区别都是http的请求方式。区别在于：用途上：get一般用于获取资源，post一般用于创建资源。位置上：get请求的的数据会在地址栏上显示出来，以问号分割url与传输数据，多个参数用取地址符连接。而post的数据放在请求体中。安全性上：优于get将信息显示在地址栏，所以对于用户密码等个人隐私信息很不安全，而post放在请求体中，在安全性上要稍微好点。长度限制：get使用地址栏发送数据，而地址栏的长度是有限的。幂等性上：get操作没有副作用，多次操作产生的副作用相同，所以get是幂等的，而post用于创建资源是会又副作用的，所以post不是幂等的。cookie和sessioncookie和session都是跟踪会话的机制。存储位置：cookie保存在客户端用来记录信息和确定用户身份，session保存在服务端同样用来记录和确定身份。安全性：cookie放在客户端很容易被查看或者破解，没有session安全。关联性：session的运行依赖于session id 而session id 存在cookie中。如果浏览器禁止了cookie，可以使用url地址重写来传递session id性能上：session会在有效期内存在于服务器的数据库或者文件，当请求过多时，服务器性能会下降。大小上：单个cookie保存的大小不能超过4k使用cookie来管理session以弥补http中无状态特性。通过对set-cookie头域写入session ID可以免登录，提高访问的效率。握手优化：session缓存， session key 放在内存，有内存消耗， 负载均衡后找不session key。session ticket 集群可以共享。TCP和UDP的区别TCP: 面向连接的安全的流式协议，连接的时候进行三次握手，数据发送的时候会进行数据确认，数据丢失之后，会进行数据重传。 确认和重传机制。UDP: 面向无连接的不安全的报文传输，发出去就不管了，收则全收，丢则全丢。3次握手和4次挥手TCP三次握手：客户端向服务器端：发送SYN=1和序号seq 服务器端向客户端：回应确定信号同意连接ACK=1以及自己的连接请求SYN=1还有序号seq 客户端回应服务器端：ACK=1告诉对方它已经知道了服务器端同意，连接成功。TCP四次挥手：主动关闭方发送关闭信号，被动关闭方收到信号。然后进入半关闭状态，关闭的一方能接收数据但是不能发送数据。 等到另一个未关闭的一方，发起关闭信号以后，进入TIME_WAIT状态，等待对方2MSL之后，彻底关闭。3次握手55555555客户端发起连接，也就是C语言中的connect函数，发送一个SYN=1的标志位，同时携带一个序号。服务器端有一个accept函数，用于响应连接。服务器端响应连接后回复一个ACK=1的标志位，并且也发送一个SYN=1的标志位建立连接。客户端收到服务器端的ACK应答以后，说明建立成功。两者都同时进入established状态。同时accept和connect函数调用成功，并返回1。4次挥手55555555主动关闭方向被动关闭放发送FIN标志位，表示要断开连接。被动关闭方同意关闭，并回发ACK标志位。此时主动关闭放进入FIN_WAIT_2状态。以后主动关闭方仍然可以接收数据，但是不可以再发送数据。当另一方也决定关闭时，会发送FIN标志位，接收方回复ACK同意关闭，并且自身进入Time_wait状态，等待2MSL时长后关闭。发送方如果收到ACK应答后，就直接关闭，如果没有收到会一直发FIN标志位。滑动窗口流量控制：防止发送方发的太快，耗尽接收方的资源。控制机制：滑动窗口在TCP报文的头部有一个16位的窗口大小，用于告诉发送方接收方可用的缓冲区大小。拥塞窗口拥塞控制：防止发送方发的太快，使网络来不及处理，从而导致网络拥塞。控制机制：拥塞窗口慢启动 拥塞避免 快重传 快恢复慢启动：为了防止大量数据瞬间注入网络，引起网络阻塞。慢启动算法设定，最开始窗口为1个最大报文长度。一个传输轮次增加一倍的窗口大小。当达到慢开始门限后，执行拥塞避免算法。拥塞避免： 每个传输轮次将窗口增加一个单位，即加法增长。快重传： 当收到3个重复确认以后，执行快恢复算法。慢开始门限和发送窗口减半，然后发缺失的数据，进行加法增长，重新进入拥塞避免阶段。快恢复：慢开始门限减半，发送拥塞窗口设定为门限加3。如果后面依旧收到重复的ACK则进行加法增长窗口，如果收到新的ACK，则拥塞窗口设定为慢开始门限的值，并重新进入拥塞避免阶段。超时进入的是慢启动，重复确认才进入快恢复。选择性重传在options中left edge和right edge告诉发送方已经收到的报文序号TCP粘包问题udp不会出现粘包。发送方发送的若干包数据到接收方接收时，包粘在了一起。造成粘包的原因时因为发送端延迟发送或者接收方没有及时接收缓冲区中的数据。通常可以使用以下三种方式来解决编程时设定立即发送的操作指令把数据长度与消息一起发送。使用特殊标记来区分消息的间隔HTTP和HTTPS有什么不同HTTP协议是一种使用明文数据传输的网络协议。HTTPS协议可以理解为HTTP协议的升级，就是在HTTP的基础上增加了数据加密。在数据进行传输之前，对数据进行加密，然后再发送到服务器。这样，就算数据被第三者所截获，但是由于数据是加密的，所以你的个人信息让然是安全的。这就是HTTP和HTTPS的最大区别。在浏览器地址栏键入URL，按下回车之后会经历以下流程浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;（递归式和迭代式）解析出 IP 地址后，根据该 IP 地址和默认端口 80，和服务器建立TCP连接;浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求，该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;服务器对浏览器请求作出响应，并把对应的 html 文本发送给浏览器;释放 TCP连接;浏览器将该 html 文本并显示内容; UDP如何做到可靠想要做到可靠，必须要做到无重复，无丢失，无错误，无失序。借鉴TCP的可靠机制发送时进行编号接收方收到数据发出应答信号（超时重传，3次确认重传）增加校验位NAT DNS ARPNAT用于实现从内部IP地址到外部IP地址的映射DNS提供域名到IP地址的映射或者反过来ARP提供IP到MAC地址的映射DNS基于UDP的协议递归查询：父域名代替当前服务器递归查询，最后依次返回迭代查询：父域名服务器告诉当前服务器下一次查询的位置http状态码100 continue 等待继续发送200 ok 请求成功206 patial content 部分资源301 永久重定向302 临时重定向 307400 客户端请求报文语法错误403 禁止访问404 资源不存在408 请求超时500 服务器内部错误503 服务器不可用http 2.0 SPDY二进制分帧多路复用首部压缩服务器推送http 3.0 QUIC+UDP0 RTT没有队头阻塞的多路复用前向纠错网络安全sql注入：用户提交一段数据库查询代码，根据程序返回的结果获得它想得知的数据。dos攻击： 让运行的服务器呈停止状态。集中请求造成资源过载，攻击安全漏洞使服务停止。TCP原理面向连接，可靠的，基于字节流的传输层协议七层模型物数网传会表应ping是从应用层直接使用网络层的ICMP协议的，不经过传输层。原始套接字直接使用网络层的IP。两台电脑通信：网线+不同的IP地址和子网掩码，即处于同一网段。TCP和UDP可以同时使用相同的端口。应用程序可以同时使用TCP和UDP两个协议。hub集线器可以实现多个IP主机通信，但是hub的实现方式是广播，容易产生拥堵。switch交换机是集线器的升级版，可以广播可单播。ARP不知道对方MAC地址时，先广播6个ff的MAC地址，所有网卡都会接收，但是只有目的IP会单播回应，其他的都会丢弃。然后发送方收到正确的MAC后再单播传输数据。ARP攻击给两个MAC地址响应ARP广播的目的IP，经由中间人，窃取信息后再转发到正确的地址。默认网关在同一个交换机连接的网络中，属于同一网段，用不到默认网关。网关用来传递两个不同网段的通信，默认网关通常是路由器。当通信的数据不在当前网段时，即发给默认网关。路由器就是用来连接不同网段的，用来构建一个更大的网络。在传输不同的网段信息时，源IP和目的IP是不变的，源MAC和目的MAC是改变的，每经过一个路由器修改一次，记录的是下一次的目的，和这一次的发送MAC。延迟确认ack会随着响应数据发送给对方，如果没有响应的数据就会等待200ms左右，在这期间如果有对方确认到达则立即发送。如果200ms后仍然没有数据需要发送则单独发送ACK。目的是节省带宽。Nagle算法没有已发送未确认报文段时，立即发送数据。存在未确认报文段时，达到mss时再发。同时有nagle算法和延迟确认存在时会导致网络效率下降，通常会关闭延迟确认和nagle算法。setsockopt(s,IPPROTO_TCP,TCP_QUICKACK,(int*){1}, sizeof(int)); //关闭延迟确认 setsockopt(client_fd, SOL_TCP, TCP_NODELAY,(int[]){1}, sizeof(int)); //关闭nagle算法忽略SIGPIPE信号客户端和服务器端连接建立后，若某一端关闭连接，而另一端仍然向它写数据，第一次写数据后会收到RST响应，第二次写数据时，内核会向进程发送一个SIGPIPE信号，通知进程此连接已断开，而这个信号的默认处理方式是终止进程，服务器直接关闭。signal(SIGPIPE, SIG_IGN)SIGSEGV 11 访问地址无效 SIGIO 29异步通知信号 SIGKILL 9 无条件终止传输层与网络层的区别传输层位于网络层之上，为不同主机上的应用进程提供逻辑通信。端到端传输。网络层负责ip数据报的产生以及ip数据包在网络中的路由转发。状态码499服务器端处理的时间过长，客户端主动关闭了连接。分块编码transfer-encoding:chunked 响应头域 它允许服务器发送给客户端的数据分成多个部分，并且不需要预先直到发送数据的总大小。close_wait基本的思想就是要检测出对方已经关闭的socket，然后关闭它。维持一个心跳包或者设置一个超时时间。C++语言逻辑用语千万条，第一首先往里套。首先，其次，然后，最后。第一，第二，第三，第四。书籍《C++ primer》 第五版《后台开发》 徐晓鑫《linux高性能服务器编程》 游双《redis设计与实现》黄健宏《muduo库》陈硕staticstatic的使用可以分为两类，一类是用在普通变量和函数上，另一类是用在类中。普通变量分为全局变量和局部变量。声明为静态全局变量是在全局区分配内存，并且只在当前文件可见，在文件之外是不可见的。其他文件定义同名变量不会发生冲突。变量的值只在第一次执行时进行初始化。声明为静态局部变量时与全局变量类似，只是作用域为局部作用域。静态普通函数，只在当前文件中可见，其他文件中定义同名函数不会发生冲突。static用在类中，首先是静态成员变量，在类中声明，类外初始化。所有对象共享一份数据。然后是静态成员函数：所有对象共享同一个函数，静态成员函数只能访问静态成员变量。多态多态分类两大类：静态多态和动态多态。静态多态是重载和模板。动态多态：也叫运行时多态，是通过继承和虚函数实现的。在具有继承关系的子类中，子类重写父类的虚函数，通过父类引用或指针指向子类对象时，产生不同的行为叫做多态。多态的核心在于虚函数表指针，每个对象都有一个虚函数表指针，虚函数表指针指向一张虚函数表，表中记录了虚函数的入口地址，如果子类重写虚函数后，这个地址就会替换掉。多态的好处在于更方便程序的扩展，坏处在于每个对象多了一个4字节的指针，同时每次查询虚函数表需要耗时。智能指针C++11有3个智能指针，分别是unique_ptr, shared_ptr和weak_ptrunique_ptr独享指针的所有权，无法进行拷贝构造赋值的操作，只能通过move函数进行所有权的转换。shared_ptr共享对象，它使用引用计数来保存当前有多少个智能指针在引用这个对象，当引用计数降为0时，对象会被销毁。weak_ptr称为弱引用，用于辅助shared_ptr正常工作，主要解决shared_ptr可能会产生的环形引用问题。weak_ptr不会增加对象的引用计数，共享指针可以直接赋值给弱指针，同时弱指针可以使用lock函数来获取shared_ptr对象malloc和newmalloc和new都是在堆上开辟内存，进行动态的管理。new是操作符，malloc是库函数malloc只负责开辟内存，没有初始化功能。new不但能开辟内存，还可以初始化。malloc必须指定开辟内存的大小，并且返回类型为void*，因此malloc的返回值一般都需要进行类型转换。new可以自动计算出所需内存的大小，并且返回指定类型的指针。malloc和new如果操作是内置数据类型两者基本类似，不同在于申请失败时。malloc申请失败时返回NULL。new申请失败抛出异常。自定义类型时，new先调用operator new函数申请空间，然后在申请的空间上执行构造函数。C++内存模型从高地址到低地址环境变量和命令行参数栈区共享区堆区未初始化数据段.bss初始化数据段.data代码段.text指针和引用的区别指针保存的是所指对象的地址，而引用是所指对象的别名。指针需要通过解引用间接访问对象的值，引用可以直接访问。指针可以有多级指针，而引用最多两级。并且两个取地址符是右值引用。右值引用是为了减少深拷贝的次数。指针可以不初始化，即使初始化以后也可以改变。而引用必须初始化，同时初始化以后不许改变。引用的本质是指针常量。指针常量不可以修改指向，但是可以修改指向的值。常量指针刚好与之相反。vector底层原理首先，vector的基类是三根指针，分别是start/finish/end_of_storage用来指示当前分配到的空间所用的起始位置，终止位置和容量尾部。然后，当finish指针到达end_of_storage的位置时，操作系统会寻找当前容量大小2倍的连续内存空间，并且将旧内存中的数据拷贝到新内存，然后释放旧内存。其次，如果重新分配了内存，原来的迭代器就会失效。频繁的开辟新内存比较耗时。如果可以预知使用的大小，可以使用reserve函数，预先开辟足够大的空间。或者使用swap函数收缩内存空间。代码生成可执行文件的过程主要分为四个步骤预编译阶段：对g++编译器指定-E参数，生成.i文件。这个阶段的主要工作是将所有的宏展开，去掉所有的条件预编译指令，将所有的头文件包含进来，删除注释等。编译阶段：对g++编译器指定-S参数，生成.s汇编文件。这个阶段的主要工作是对代码的语法，语义和词法等进行分析。汇编阶段: 对g++编译器指定-c参数，生成.o二进制文件。链接阶段：将各个模块之间的相互引用处理好。把所有的静态库用到的目标文件装入程序中，并进行统一编址，然后进行重定位，即逻辑地址到物理地址的转换。静态库与动态库静态库：命名方式为lib开头加上自定义的静态库名，然后以.a结尾。静态库实际上是一组目标文件的集合，再链接阶段与调用的程序生成可执行文件。静态库的优点在于：代码加载速度快，发布程序时，不需要提供对应的库；缺点时：可执行文件体积大，同时如果静态库有修改，调用的程序需要重新编译，而编译的耗时比较久。动态库：命名方式为lib开头加上自定义的动态库名，然后以.so结尾。动态库首先生成与位置无关的目标文件，然后再运行时加载到内存。优点是：动态库可以共享，节省了系统资源，动态库进行修改后，无需重新编译。缺点是加载速度比静态链接慢，发布程序时，需要提供动态库。符号表每个目标文件除了拥有自己的数据和二进制代码外，还提供了3个表：未解决符号表：提供了所有在该编译单元里引用但是定义并不是在本编译单元的符号及其出现的地址。【引用无定义】将extern声明的变量置入未解决符号表。【外部链接】导出符号表：提供了本编译单元具有定义，并且愿意提供给其他单元使用的符号及地址。【有定义肯让外用】普通变量及其函数被置入导出符号表。地址重定向表：提供了本编译单元所有对自身地址的引用的记录。static声明的全局变量放入地址重定位表中。【内部链接】指针常量int* const p = &a指针常量必须初始化，一旦初始化完成，就不能再修改它的值，即指针的指向不可变。引用的本质是指针常量声明和定义的区别声明是告诉编译器有这个变量和函数的存在，但是需要到其它地方去寻找。定义包含了声明，但是声明不包含定义。定义时才分配存储空间。C和C++的区别设计思想上： C是面向过程的结构化语言，CPP是面向对象的语言语法上： CPP具有三大特性，封装继承多态 CPP相对于C增加了许多类型安全的功能，比如四种强制类型转换 CPP支持范式编程，如模板类，函数模板等struct和class的区别共同点：C++中，可以用struct和class定义类，都可以继承。不同点：struct默认继承权限和默认访问权限时public class类的默认继承权限和访问权限时private。volatile关键字对类型额外修饰的作用，类似于const。告诉编译器不要对这样的对象进行优化，因为该对象的值可能在程序的控制或检测之外被改变。const关键字const 可以用于限定变量，指针和函数不可改变，同时明确制定了类型，可以方便编译器做类型检查，也增加了代码的可读性。const修饰变量必须初始化。如果是全局的const变量，通常放在静态区。在局部声明的const变量放在栈区。const修饰成员函数时，函数中的成员变量不可改变，除非该变量特别声明为mutableconst可以用来修饰指针，称为常量指针const int *p 指针的指向可以改变，但是不能改变指针指向的值。const修饰常量的指针叫做指针常量，int* const p 指针的指向不可以修改，指针指向的值可以修改。指针常量必须初始化。const可以明确指定类型，而宏定义没有数据类型。define宏是在预处理阶段展开。const常量是编译运行阶段使用。宏定义不分配内存，变量定义分配内存。extern关键字引入同一模块在其他文件中定义的全局变量和函数。如果在C++里调用了C库定义函数，那么需要使用extern "C" 标识这个函数，告诉编译器使用C的方式进行编译，防止C++的编译方式导致命名重整，无法找到对应的C函数。命名重整的原因在于C++支持函数重载，而C不支持。所以C++编译时增加了函数参数的标识符。extern通常放在为解决符号表中，表示定义不在本文件而引用的变量。this关键字解决同名冲突返回对象本身this指针的本质是指针常量，指针的指向不可以修改。move函数将左值强制转换为右值引用，右值引用可以减少一次对象的析构和对象的构造。右值引用可以减少深拷贝的次数。段错误段错误通常发生在访问非法内存地址的时候。系统会发送一个SIGSEGV11号信号告诉当前进程，进程采取默认的捕获方式，即终止进程。野指针试图修改字符串常量的内容auto关键字让编译器能够根据初始值的类型推断变量的类型。当处理复杂类型，比如STL中的类型时，优势最明显。auto p = vt.begin()四种强制类型转换static_cast 低风险的转换，比如整数转浮点数，字符型转整形const_cast 去掉const关键字的转换，可以去掉带const的指针和引用dynamic_cast 使具有继承关系的基类转换为派生类，如果不可以转换则返回NULLreinterpret_cast 指针或引用的转换，风险较高RTTIrun time type identification 运行时类型识别。常常结合typeid()和dynamic_cast实现。可以根据当前调用的指针是何种类型，经过dynamic_cast转换后，调用非虚函数。dynamic_cast只能用于指针和引用的转换，要转换的类型中必须包含虚函数，转换成功返回子类的地址，失败返回NULL。typeid返回一个type_info对象的引用。构造函数不能是虚函数虚函数是通过虚函数表指针来调用的，而虚函数表指针存在对象内存空间。当一个对象调用构造函数时，该对象还没有实例化，即没有分配内存空间，所以虚函数表指针无法找到。析构函数尽量是虚函数析构函数不是虚函数容易引起内存泄漏。为了实现多态的动态绑定，通常将基类指针指向派生类对象，当指针销毁时，如果析构函数不是虚函数，根据析构函数在继承中的调用顺序，则派生类对象将不会被析构，造成内存泄漏。析构函数不能抛出异常析构函数抛异常，则异常点之后的的程序不会执行，如果异常点之后有释放资源的操作，则这部分资源无法释放，导致内存泄漏。noexcept内存泄漏不再需要使用的内存单元，没有及时释放。memcheck和valgrind检测内存泄漏的工具。使用RAII资源获取就是初始化和智能指针。野指针一些内存的单元已被释放，之前指向它的指针还在被使用。vector和list的区别vector是动态数组，在内存中分配一块连续的内存空间，因此可以使用下标进行快速的随机访问。但是删除和插入需要移动大量的元素。list是双向链表，在内存中是不连续的空间，由指针将不同的地址连接在一起。list的插入和删除操作都是O(1)的。数组必须事先设定固定的长度，不能动态的增减，可能会造成资源浪费。链表可以动态的增减。浅拷贝由于编译器默认的拷贝构造函数只是简单的位拷贝，可能会导致内存的重复释放。解决浅拷贝的办法通常使用深拷贝，即自己实现拷贝构造函数，在堆上重新分配内存。内存对齐union最大成员所占的整数倍，同时能容纳其他的成员。union中变量共用内存，应以最长的为准。 struct按照成员的声明顺序，依次安排内存，偏移量为成员大小的整数倍，最后结构体的大小为最大成员所占大小的整数倍。在C++中，空结构体和空类的内存所占大小为1个字节。C中空结构体所占大小为0。为什么要有内存对齐：1. 硬件原因：加速CPU的访问速度。因为CPU和内存数据交换的基本单位是块，块的大小为2的n次方字节。内存未对齐可能需要多次访问内存。2. 平台原因：不是所有的平台都支持任意地址的数据访问。#include <iostream> using namespace std; typedef union{ long long i; //8 bytes int k[5]; //4 bytes 最长的成员不是20 char c; // 1 byte }UDATE; //联合体共用内存 最长成员为8字节 结果要为8的倍数 同时要能容纳其他成员，即大于等于20字节 所以为24字节 struct data{ int cat; // 4 bytes UDATE cow; //24 bytes 但是需要先拆开来 最长成员为8字节 double dog; //8 bytes }too; //结构体顺序考虑，结果为最大成员的整数倍，如果后一个成员的长度的开始位置不是整数倍需要填充字节 //cat占4个字节 填充4个字节 //起始位置为8 满足整数倍 cow占用24字节 //起始位置为32 满足整数倍 doule占用4字节 //所以结构体总共占用40字节，同时40也是8的倍数。 UDATE temp; int main(){ cout<<sizeof(temp)<<" "<< sizeof(struct data)<<endl; //24 40 return 0; } gdb调试gdb可以用于分析coredump文件，coredump文件中含有当进程被终止时内存，cpu寄存器和各种函数堆栈信息等。设置断点 b 120运行 r打印遍历p number查看堆栈bt查看循环中的变量 i单步运行n数据库B+树具有n个关键字的节点含有n个分支。而在B树中，具有n个关键字的节点含有n+1个节点。B+树的叶子节点包含了所有关键字，并且包含信息。B+树非叶子节点只起索引作用，不存数据。B+数在叶子节点使用指针将前后磁盘块中的索引连接起来，形成一个线性链表，方便范围查询。什么是索引索引是数据表中对字段进行排序的一种数据结构。常用的索引有B树 哈希索引 全文索引 Rtree索引AVL树，B树，B+树，红黑树，哈希表。哈希表和B树不利于范围查找。红黑树在数据量大的时候性能会下降。聚集索引：数据和索引在一起的。非聚集索引：索引文件和数据文件是分离的。联合索引：对多个字段同时建立的索引。Mysql从左到右的使用索引中的字段，一个查询可以只使用索引中的一部份，但只能是最左侧部分，跳跃索引查询就会导致索引失效。B树和B+树的区别B树：叶节点具有相同的深度，所有索引元素不重复，节点中的数据索引从左到右递增排列。B+树：非叶子节点不存储数据，只存储索引，叶子节点包含了全部的关键字且存储数据。叶子节点用指针连接从左往右递增排序，提高区间访问的性能。mysql存储引擎myISAM和InnoDB存储引擎是基于表的，而不是数据库。InnoDB引擎：支持事务，行锁（适合高并发），支持外键。mysql5.5版本后的默认存储引擎myISAM引擎：不支持事务，不支持外键，支持表锁。优势是访问的速度快。mysql5.5版本前的默认存储引擎。myISAM索引文件和数据文件是分离的。查询较多的更新较少的情况下使用myisam存储引擎。数据库三大范式数据库中的所有字段都是不可分割的原子值满足第一范式的前提下，除主键外的每一列都必须完全依赖于主键。如果不完全依赖，只能发生在联合主键下。满足第二范式的前提下，除开主键列的其他列之间不能有传递依赖关系。事务的四个特性ACID事务是作为单个逻辑工作单元执行的一系列操作。要么完全执行，要么完全不执行。原子性 ：同一事务中多个操作不能分割，必须是一个整体。一致性 ：事务操作前后的总量保持一致。隔离性 ：多个事务之间的操作互不干扰。持久性：事务提交以后，对数据库中数据的改变是永久的。事务隔离级别事务分为四个隔离级别：分别是读未提交，读提交，可重复读，串行化。从前往后隔离级别依次递增，同时执行效率依次下降。mysql的默认隔离级别是可重复读。读未提交：会产生脏读，即一个事务读取了另一个事务未提交的内容。读提交：可以解决脏读问题，但是会产生不可重复读，即一个事务在执行的过程中得到的结果不一致。可重复读：可以解决不可重复读的问题，但会产生幻读，即一个事务无法看到另一个事务已经提交的内容。串行化：可以解决幻读问题。当同一张表被另一个事务操作时，其他事务的写操作是不可以进行的，进入串行化排队，直到另一个结束，同时还为超时的情况下才可以继续执行。set global transaction isolation level read committed # 修改隔离级别 select @@x_isolation # 查看隔离级别主键和外键约束主键：唯一且非空。一个表有且只能由一个主键约束。创建主键会自动创建对应的索引，同样删除主键，对应的索引也会被删除。外键约束：如果定义了外键约束，主表中没有的数据在子表中是不可以被使用的。主表中的记录被子表引用，是不可以被删除的。inner join 和 left join等值联接：inner join返回两个表中联接字段相等的行左联接：left join 返回左表所有记录和右表中联接字段相等的记录，如果左表中的记录在右边不存在，则对应字段设为null。redis是C语言编写的基于内存可持久化的key-value内存数据库，使用的是epoll单线程模型。常用的数据结构有set， list，string, hash, sorted set。查询分组查询：count() sum() max() min() avg()聚合查询：7种 A B A∪B A∩B A - A∩B B - A∩B A∪B - A∩B左连接： A - A∩B 右连接：B - A∩B 内连接：A∩B悲观锁和乐观锁悲观锁： 每次去拿数据时都认为别人会修改，所以每次在拿数据的时候都会上锁。悲观锁由数据库自己实现，共享锁和排他锁是悲观锁的不同实现。悲观锁的缺点：效率低，并行差，增加死锁的概率。乐观锁：每次去拿数据都认为别人不会修改，所以不会上锁。乐观锁适用于读多，写少的场景。乐观锁常见的实现方式：版本号机制和CAS自旋算法。乐观锁的缺点：ABA问题，循环时间长开销大，只能保证一个共享变量的原子操作。explain 查看执行计划使用explain关键字可以模拟优化器执行sql查询语句，从而知道mysql是如何处理sql语句的。分析查询语句或表结构的性能瓶颈。explain + sql语句show profile用来分析当前会话中语句执行的资源消耗情况。什么时候应该创建索引频繁查询字段什么时候不应该创建索引where条件里用不到的字段频繁更新的字段表记录太少<300W重复且平均的表字段B+树B+树是B树的变形，B+树非叶子节点只存索引，不存数据，叶子节点存储所有的索引和数据。B+树三层可以存储上百万条数据，磁盘IO最多三次，每次读取磁盘块到内存后，使用二分查找找到索引的位置。B+树是聚集索引，数据和索引在一起。B+树使用指针将叶子节点的连接起来。B树是一种多路平衡二叉树，m阶B树，每个节点最多可以有m-1关键字。所有叶子节点在同一层。每个节点保存索引和数据。索引索引是按照某个字段排序的数据结构。ACID事务时单个逻辑工作单元的一系列操作。原子性：事务的操作是不可分割的，要么完全成功，要么完全失败。一致性：事务操作前后的总量保持不变。隔离性：事务之间的操作应该相互隔离。持久性：事务一旦提交后，数据库中的值不可更改。事务的隔离级别读未提交：产生脏读，即一个事务可以读取另一个事务未提交的内容。读提交：解决脏读，产生不可重复读，即事务的两次读取结果不一致。可重复读：解决不可重复读，但是会产生幻读，即一个事务明明提交了，但是在另一个事务中没有更新。串行化：当两个事务都在写入内容时，必须排队，当两个事务提交后，才可以执行后续事务。数据库三大范式数据库中的所有字段都是不可分割的原子值在满足第一范式的前提下，除开主键外的其他列都必须完全依赖于主键。在满足第二范式的前提下，除开主键外的其他列之间不能有传递依赖关系。myisam和innoDBmyisam不支持事务，不支持外键，支持表锁。查询速度较快。适合读多更新少的表。innoDB支持事务，支持外键，支持行锁，因为锁的粒度比较小，所以适合高并发。持久化利用永久性存储介质将数据进行保存，在特定的时间将保存的数据进行恢复的机制叫做持久化。确保数据安全。将当前数据状态进行保存，快照形式。RDB将数据的操作过程进行保存，日志形式。AOF基础知识volatile用来告诉编译器不要对该变量做任何优化，编译器每次操作该变量时，一定要从内存中取出，而不是使用寄存器中与存在的值，因为值可能已经发生了改变。应用场景：并行设备的硬件寄存器（如状态寄存器）。多线程中共享变量。const用于限定变量，函数和指针不可改变。const限定全局变量时，变量放在静态区。const限定局部变量时，变量放在栈区。const修饰类中的成员函数时，表示不可对类的对象更改，如果需要更改类中的成员变量，可以使用mutable关键字。内联函数和宏定义宏定义在预编译时展开，内联函数在编译时展开。宏定义只是简单的文本替换，不进行参数的有效性检查。内联函数会进行类型检查，然后将函数体嵌入到目标代码中。宏定义不加括号容易出错。内联函数没有普通函数的栈操作，效率很高，但是不正确的时候内联函数会导致代码体积过大。static静态全局变量，静态局部变量，静态成员变量，静态成员函数静态全局变量和静态局部变量都放在静态区，只在声明时初始化一次，仅在本文件中可见，变量在程序运行期间一直存在。静态局部变量的作用域范围仅在局部，下次调用紧接着上次调用的结果值改变。静态成员函数和静态成员变量没有this指针，必须通过类名才能访问。malloc 和 newmalloc和free是库函数，new和delete是运算符，可以重载。malloc申请内存时需要指定内存大小，返回一个void*类型，通常需要强转。new申请内存时无需指定内存大小，返回一个对应类型的指针。malloc不进行初始化。new在创建非内部类型的对象时，调用构造函数初始化，消亡时会调用析构函数。malloc申请失败时返回null指针，new申请失败时抛出异常。指针和引用引用创建时必须初始化，初始化后不可以改变。指针创建时可以不初始化，初始化时需要分配内存，初始化后也可以改变。引用比指针安全。引用不存在空值引用，指针可以指向空值引用的本质是指针常量，由编译器完成转换。extern可以被其他源文件调用。告诉编译器，extern限定的代码使用C语言的编译和连接方式。因为C++支持重载，而C不支持，所以可能导致函数编译后的名称不一致，从而找不到对应的函数。多态的原理多态分为静态多态和动态多态。静态多态是通过重载和模板实现的，动态多态是通过继承和虚函数实现的。动态多态是让基类的指针或引用指向派生类的对象实现的。定义为虚函数的基类，编译器会自动创建一个虚函数表，派生类在继承时会继承这个虚函数表，每个创建的对象拥有一个虚函数表指针，这个指针指向虚函数表，如果派生类重写了基类的虚函数，则对应的虚函数表入口地址发生了更新。虚函数表为所有对象所共享，通常放在代码段。什么是进程进程是分配资源的基本单位，是程序运行的实例。进程通常分为数据段，代码段和进程控制块。进程的地址空间相互独立，一个进程不可访问另一个进程的数据。进程切换时的开销相对于线程来说比较大，需要保存寄存器的值和刷新块表，切换地址空间等。进程间的通信方式有名管道pipe无名管道fifo消息队列共享内存信号量信号套接字线程是什么线程是CPU调度的基本单位。线程是进程的一个执行流程。一个进程中可以由多个线程。线程共享进程的地址空间。线程自身几乎不拥有系统资源，除了栈和寄存器。线程间的通信较为容易。但是需要同步。常用的同步机制有互斥锁，读写锁，信号量，条件变量。线程共享进程打开的文件描述符表进程ID和组ID信号的处理方式打开的工作目录进程的地址空间(堆区，栈区，共享区)线程独享线程ID线程的寄存器线程的栈信号屏蔽字线程优先级错误返回码errno信号信号是一种不精确的通信方式。常用的信号有15号信号，杀死进程 29号进程 11号段错误http和https的区别https是安全版的http协议。get和post的区别get用来获取资源，post用来创建资源。get将数据放在url地址栏中，使用问号与url分割，数据间用取地址符分割。post将数据放在请求体中。安全性上get放在url容易暴露隐私信息，而post放在请求体中可以适当的避免。get在传输的数据受url地址栏的限制，post不受这种限制get的操作是幂等的，多次操作产生的影响相同，而post是非幂等的B树和B+树的区别B树是多叉平衡树，M阶的B树，每个节点最多有M-1个关键字，每个节点的关键字都按照从小到大的顺序排列，因此查询时可以使用二分查找法。B树中所有叶子节点都位于同一层，每个节点都存有索引和数据。B树的优点在于查询单个数据时，由于每个key都存有对应的date，查询到后可以直接取回。B+树非叶子节点只存索引不存数据，每个叶子节点增加一个指向相邻接叶子节点的指针，所有的节点都存在叶子节点。B+树的优点在于范围查询时，可以利用相邻的指针获得指定范围内的数据。网络拥塞慢启动拥塞避免快速重传快恢复TCP和udp的区别tcp面向连接的可靠的流式协议，具有超时重传和确认等机制。udp面向无连接的报文协议，发出去就不管了，收则全收，丢则全丢。智能指针unique_ptrshared_ptrweak_ptr四种强制类型转换const_cast去掉const属性的转换，包括const指针和引用static_cast用于低风险的转换，比如字符型转整形dynamic_cast 用于具有继承关系的派生类和子类的转换，当基类转为子类时，如果转换失败会返回NULLreinterpret_cast 任何类型都能转，风险较高post和put的区别post用来创建资源，put用来更新资源post是非幂等的，put是幂等的。8中请求方法：http 1.0: get post headhttp 1.1: put delete options connect tracemmu内存管理器主要作用：虚拟内存到物理内存的地址映射。 设置修改内存访问级别。虚拟内存的作用解决主存容量有限分隔进程，保证进程空间彼此独立不受干扰基于局部性原理进行页面替换虚拟内存的大小由计算机的地址总线决定cache名字和TLB命中没有必然联系，是两种独立的机制。CPU和Cache之间交换的单位是字节，Cache和内存之间交换的单位是块。coredump文件gdb可以用于分析coredump文件。coredump文件含有进程被终止时内存/CPU寄存器和各种函数调用栈的信息。产生coredump文件的原因：内存访问越界多线程使用了线程不安全的函数多线程读写的数据未加锁保护栈溢出core文件没有符号表信息，必须结合可执行文件才可调试模板特化全特化：模板参数被指定未确定的类型偏特化：模板参数没有被全部确定，需要编译器在编译时进行确定。只能偏特化类模板，不能偏特化函数模板。别名模板和变量模板属于语法糖元编程在编译时计算出运行时需要的常数，类型和代码的方法。右值引用右值引用指向要被销毁的对象。右值要么是字面常量，要么是在表达式求值过程中创建的临时对象。move函数将左值转换为右值，调用move函数后源对象只能赋值或销毁。overrideoverride在子类中标记某个函数，表示想要覆盖已有的虚函数，如果没有覆盖，编译器会报错。加作用域运算符调用特定类的虚函数内联函数的优劣优点：减少函数调用的开销，包括寄存器值的保存和实参的拷贝等。缺点：增加函数体积，可能导致cache装不下，从而减少了cache的命中率。inline只是一个请求，编译器有权拒绝。拷贝构造函数调用场景：一个对象以值传递传参一个对象以值传递的方式从函数返回一个对象通过另一个对象初始化空类占有一个字节有构造，析构，拷贝，赋值运算符，取地址运算符。构造函数可以被重载，析构函数不可以被重载且不能带参数。explicitexplicit取消隐式转换，类中构造函数默认是implicitexplicit关键字的作用是防止类构造哈桑农户的隐式自动转换，只对有一个参数的构造函数有效。堆和栈的区别申请方式不同。栈由操作系统自动分配，堆需要程序员自己申请。生长方向不同。栈由高地址向地址生长，是一块连续的内存区域。堆由地址向高地址生长，是不连续的内存区域。在一个链表中记录空间内存地址。分配速度。栈由系统分配，速度较快。堆使用new分配，速度较慢，且容易产生内部碎片。C++ 和python的区别python是解析性语言，无需编译，方便快捷，跨平台性很好。C++是编译型语言，先编译后执行，编译后通常不能跨平台。python使用严格的缩进来表示不同级别的代码块，在C++中使用花括号。索引select语句调用函数后就不会用到索引static的作用static可以用来修饰函数和变量。修饰全局变量和局部变量时都是放在静态区，static变量只初始化一次，在程序结束时销毁，全局和局部的区别在于作用域不同。static可以修饰普通成员函数，表明这个函数只在本文件中有效。static修饰类成员变量是，这些变量为这个类所共享，static修饰类成员函数时，也是所有对象共享这个函数，该函数中没有this指针。同时static类成员函数中只能调用static修饰的函数。静态存储区存放的static修饰的全局变量和局部变量，const修饰的变量以及字符串。数据段和静态区的区别数据段存放的是代码的二进制指令。静态区是变量。虚函数的实现机制每个含有虚函数的类都有一个虚函数表，类创建的对象都由编译器自动生成一个虚函数表指针来指向虚函数表。子类继承时会继承这个虚函数表，在子类中如果重写了父类定义的虚函数，这个虚函数中的对应内容会替换为重写的内容。当父类指针或引用指向子类对象时，调用对用对应的虚函数时会根据虚函数表指针找到虚函数表，然后从表中找到重写的虚函数入口地址，然后实现对应的行为。虚函数使代码更具有扩展性。python的多态子类继承父类，子类重写父类的函数，调用时就自动调用了子类的函数。C++和python的区别C++是编译型语言，需要先编译再执行，编译后通常不可以跨平台。python是解释型语言，直接运行，跨平台好。上手快，容易浮于表面不够深入。python带来的收益快速上手机器学习和深度学习，直接调用大量的库函数。STL底层容器和实现原理vector是动态数组，分配连续的内存，2倍扩容。list双向表，插入删除效率高。map和set红黑树，有序的容器。stack和queue底层可能都是数组实现unordered_map和unordered_set哈希表。array栈上分配的数组执行效率快。tuple元组多数据类型的集合。快排基于交换和分治的算法， 平均nlogn 最坏n平方四次挥手主动关闭方发送FIN标志位，自身状态进入FIN_WAIT_1被动关闭方收到FIN后，发送ACK确认，自身进入CLOSE_WAIT状态，当主动关闭方收到ACK后，进入FIN_WAIT_2状态，此时主动关闭方只能接收数据，不能发送数据，因为TCP是全双工的，所以要等待被动关闭方关闭后才结束。此时进行了两次挥手，双方进入半关闭状态。当被动关闭方需要关闭时，发送FIN标志位，发出后自身状态进入LAST_ACK状态，如果对方收到FIN标志位后，发送ACK应答，自身进入TIME_WAIT状态，等待2MSL后关闭连接。等待的2MSL是报文在网络传输中一个来回的长度。确保最后一个ACK能被对方收到。static关键字static可以修饰普通函数变量和类成员函数和变量。static修饰普通变量时，分为全局变量和局部变量，两者都保存在静态区，并且只初始化一次，在整个程序运行期间一直存在。全局变量和局部变量的区别是作用域不同。static修饰普通函数时，说明此函数只在本文件中可见，防止多个文件的同名冲突。static修饰类中的成员变量时，必须在类中声明，在类外初始化，初始化的时候分配内存，所有的static成员变量为所有对象共享。static修饰类中的成员函数时，只能调用static的变量和函数，没有this指针，所有对象共享这个函数，可以使用类名直接调用。变量分为全局变量和局部变量，static修饰全局变量时，表示这个变量只在本文件中可见const关键字const用于限定变量指针和函数不可改变，方便编译器做类型检查。cons修饰变量时必须初始化。const全局变量通常放在静态区，const局部变量放在栈区。cosnt修饰成员函数时，函数中的成员变量不可更改，如果要修改成员变量需要声明为mutableconst修饰指针有两种，常量指针和指针常量，常量指针是指针的指向的值不可改变，而指针的指向可以改变。指针常量是指向不可变，而值可变。const和define的区别const明确指定类型，编译器对类型做检查，而define没有类型也不 检查。const分配内存，而define不分配。const在编译期处理，而define在预编译期进行宏替换。define的宏替换不加括号会产生严重的影响。指针和引用的区别指针保存的是所指对象的地址，而引用是所指对象的别名。指针通过解引用间接访问所指的对象，而引用直接访问。指针可以有多级，而引用最多两级。当有两个取地址符时，是右值引用，右值引用可以减少深拷贝的次数。指针定义时可以不初始化，即使初始化后也可以改变。而引用定义时必须初始化，初始化后不可以改变。引用的本质是指针常量，编译器帮助转换。指针常量的指向不可以改变，值可以变。define与内联函数的区别内联函数是一个函数，在编译期插入到调用的地方，而define在预处理期进行替换。内联函数避免了函数调用时的压栈和参数拷贝等操作，提高了性能。内联函数对参数有类型检查。define不加括号容易出错。new和malloc的区别malloc是库函数，new是运算符malloc只分配内存不初始化，而new不仅分配内存也初始化。new分配内存以后自动调用构造函数。malloc分配内存时必须指定内存大小，而new可以自动计算。malloc分配完成后返回的是void*类型，需要强转，而new返回的是对应类型的指针。malloc分配内存失败时返回NULL，而new分配内存失败时抛出bad_alloc异常。http和https的区别两者都是用于客户端和服务器端通信。https是http的安全版。主要区别在于：http是明文传输，https是密文传输。http默认端口是80， https的默认443https需要验证服务器端的身份，如果CA证书不正确则会中断通信。CA证书需要成本，加密解密的过程增加CPU和内存的开销。https增加了ssl层，用于确保传输的安全性。通信前先进行ssl层的握手，首先客户端ssl版本号和加密组件发送给服务器端。服务器端筛选出可用的ssl版本号和加密算法同时加上CA证书发送给客户端。客户端验证CA证书的有效性，如果无效则中断通信。若有效客户端发送加密的pre-master secret随机密码串，这一步使用的是非对称加密，用于协商后面对称加密的密钥，所以这一步不能被篡改和截获。客户端得到服务器端的响应后，且验证通过后，后续就使用对称加密加密算法进行加密。然后进行TCP三次握手。http1.0和http1.1的区别http1.0只支持短连接，即一次通信完成后就立即断开http1.1支持长连接，一次TCP建立以后，可以进行多次请求。http1.0只提供了三种请求方法：get post headhttp1.1增加了五种请求方法：put delete connect trace optionshttp1.1增加了许多状态码，比如100 continue 表示已经收到，等待后续的资源。206 partial content 部分资源http1.1支持一个物理主机上可以有多个虚拟主机共用一个IP​
C/C++基础1. 智能指针实现原理智能指针通过 RAII 原则，利用 C++ 栈的生命周期来管理堆内存的动态分配和释放，避免手动 delete 操作。unique_ptr：管理独占资源，仅支持移动语义，不能复制。shared_ptr：通过引用计数实现多个对象共享同一资源。weak_ptr：辅助 shared_ptr，解决循环引用问题。2. 智能指针的计数器何时改变shared_ptr 的引用计数在：新的 shared_ptr 指向同一资源时增加。shared_ptr 被销毁或赋值为其他对象时减少。当引用计数为 0 时，释放资源。3. 智能指针和管理对象分别在哪个区智能指针本身存储在栈区。它所管理的资源存储在堆区。4. 面向对象的特性：多态原理多态通过虚函数实现，允许不同类型的对象使用相同的接口。底层实现：虚函数通过虚表（vtable）实现。每个包含虚函数的类都有一个虚表指针（vptr），在对象创建时初始化。调用虚函数时，依据 vptr 定位到具体类的虚表，执行相应的函数。5. 介绍虚函数及其实现虚函数是通过 vtable 实现的。vtable 存储了虚函数的地址。每个对象都有一个指向其类的 vtable 的指针 vptr。运行时通过 vptr 查找函数地址，实现动态绑定。6. 多态和继承在什么情况下使用多态：当需要根据不同子类行为实现动态调用时使用。继承：当需要复用父类代码，且子类与父类有 "is-a" 关系时使用。7. 除了多态和继承还有什么面向对象方法封装：通过访问修饰符（public、protected、private）隐藏数据细节。抽象：定义抽象类或接口，强调对象的行为而非实现。8. C++ 内存分布栈区：局部变量、函数调用信息。堆区：动态分配的内存。全局区：全局变量、静态变量。常量区：字符串字面值、常量。代码区：存储程序的二进制代码。9. C++ 内存管理（RAII）RAII 是通过构造函数获取资源，通过析构函数释放资源的设计模式。常用类包括：std::unique_ptr 和 std::shared_ptr。文件流（如 std::fstream）。10. C++ 从源程序到可执行程序的过程预处理：处理宏定义和头文件。编译：将 C++ 源代码转换为汇编代码。汇编：将汇编代码转换为机器代码。链接：将多个目标文件和库合并为可执行程序。11. 一个对象 = 另一个对象会发生什么调用赋值运算符（operator=），默认行为是逐成员赋值。可以重载该运算符以实现深拷贝。12. new 出错直接 return 会导致内存泄漏，怎么办使用智能指针（如 std::unique_ptr）来管理动态资源。利用 RAII 确保异常时资源自动释放。13. C++11 的智能指针及循环引用智能指针：unique_ptr：独占资源。shared_ptr：共享资源。weak_ptr：避免循环引用。循环引用：两个 shared_ptr 相互引用，引用计数无法归零。通过 weak_ptr 打破循环。14. 多进程 fork 后的共享资源共享资源：程序代码段。打开的文件描述符。不共享：堆、栈。15. 多线程同步方式互斥锁（std::mutex）读写锁（std::shared_mutex）条件变量（std::condition_variable）原子变量（std::atomic）16. sizeof 是在编译期还是运行期确定编译期：sizeof 是一个编译期操作符，确定类型或对象的大小。17. 函数重载机制重载基于函数签名（函数名+参数类型）。通过编译期的名称修饰（name mangling）实现。18. 指针常量与常量指针指针常量（T* const）：指针地址不可变，但指向的内容可变。常量指针（const T*）：指针指向的内容不可变，但地址可变。19. vector 的原理与扩容原理：基于动态数组。扩容：当容量不足时，分配更大的内存块并迁移数据。20. const 的作用修饰变量：只读变量。修饰函数：保护成员变量不被修改。修饰指针：见问题 18。21. 引用与指针的区别引用必须初始化，指针可以为空。引用不可改变绑定的对象，指针可以。22. C++11 新特性智能指针（unique_ptr、shared_ptr）。右值引用与移动语义。auto、decltype。Lambda 表达式。继续解答 C/C++ 基础部分的问题：23. 类型转换C++ 提供以下类型转换方式：C 风格强制转换：(T)value。C++ 风格强制转换：static_cast：常规转换，如基本类型间转换、子类到父类的转换。dynamic_cast：用于多态类型，运行时检查安全性。const_cast：用于去除或添加 const 修饰。reinterpret_cast：直接改变类型的位解释，例如指针转换为整数。24. RAII 基于什么实现RAII 基于对象的 生命周期 和 作用域：资源在对象构造时获取，在对象析构时释放。自动调用构造函数和析构函数确保资源安全。25. 手撕代码：unique_ptr 实现以下是简化版 unique_ptr 的实现：template <typename T> class UniquePtr { private: T* ptr; public: explicit UniquePtr(T* p = nullptr) : ptr(p) {} ~UniquePtr() { delete ptr; } // 禁止拷贝 UniquePtr(const UniquePtr&) = delete; UniquePtr& operator=(const UniquePtr&) = delete; // 支持移动 UniquePtr(UniquePtr&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; } UniquePtr& operator=(UniquePtr&& other) noexcept { if (this != &other) { delete ptr; ptr = other.ptr; other.ptr = nullptr; } return *this; } T* get() const { return ptr; } T& operator*() const { return *ptr; } T* operator->() const { return ptr; } };26. unique_ptr 和 shared_ptr 区别unique_ptr：独占所有权，不支持拷贝，支持移动。shared_ptr：共享所有权，支持多个指针共享同一资源，使用引用计数管理资源。27. 右值引用右值引用（T&&）是 C++11 引入的新特性，用于绑定右值（如临时对象）。主要用于：移动语义：避免拷贝，提高性能。完美转发：结合 std::forward 实现函数模板的参数转发。28. 函数参数可不可以传右值可以通过右值引用（T&&）传递右值参数：void func(int&& x) { // 使用 x } func(10); // 10 是右值29. 参考 C/C++ 堆栈实现自己的堆栈以下是一个简单的基于数组实现的堆栈：template <typename T> class Stack { private: T* data; size_t capacity; size_t topIndex; public: explicit Stack(size_t size) : capacity(size), topIndex(0) { data = new T[capacity]; } ~Stack() { delete[] data; } void push(const T& value) { if (topIndex == capacity) throw std::overflow_error("Stack overflow"); data[topIndex++] = value; } void pop() { if (topIndex == 0) throw std::underflow_error("Stack underflow"); --topIndex; } T top() const { if (topIndex == 0) throw std::underflow_error("Stack underflow"); return data[topIndex - 1]; } bool empty() const { return topIndex == 0; } };30. STL 容器底层实现vector：动态数组，扩容倍增，线性地址存储。map：红黑树，支持有序键值对存储。unordered_map：哈希表，快速键值对查找。list：双向链表，适合频繁插入和删除。31. 完美转发完美转发通过右值引用和 std::forward 实现：template <typename T> void wrapper(T&& arg) { process(std::forward<T>(arg)); // 保持参数的值类别 }去掉 std::forward：可能导致参数的值类别改变，右值被传为左值。32. unique_lock 和 lock_guard 区别lock_guard：简单易用，RAII 机制，构造时加锁，析构时解锁。unique_lock：灵活控制锁，支持延迟加锁、解锁和重新加锁。33. C 代码调用 C++ 代码报错原因名称修饰（name mangling）：C++ 编译器对函数名进行修饰，而 C 使用未修饰的名字。解决方法：用 extern "C" 声明：extern "C" void func();34. 静态多态静态多态在编译期决定，如函数重载和模板。虚函数原理：虚表（vtable）和虚表指针（vptr）。析构函数为何要虚函数：确保基类指针释放派生类对象时调用正确的析构函数。35. map 用红黑树而非 AVL 树原因红黑树旋转次数少于 AVL 树，插入和删除效率更高。AVL 树更适合频繁查询，红黑树更适合综合操作。36. inline 失效场景函数体太大或递归函数。虚函数的动态调用。编译器决定不内联。37. struct 和 class 区别默认访问权限：struct：默认 public。class：默认 private。其他特性相同。38. 防止头文件多次 #include使用 include guard：#ifndef HEADER_FILE #define HEADER_FILE // 头文件内容 #endif或者使用 #pragma once。39. Lambda 表达式捕获类型按值捕获（[=]）：捕获外部变量的副本。按引用捕获（[&]）：捕获外部变量的引用。混合捕获：指定具体捕获方式，如 [=, &x]。40. 友元 (friend) 的作用允许非成员函数或其他类访问类的私有和保护成员。41. std::move用于显式将左值转为右值引用，触发移动语义，避免不必要的拷贝。42. 模板类的作用提供通用实现，支持不同数据类型复用代码。43. 模板与泛型的区别模板是 C++ 提供的特性，实例化时生成代码。泛型是语言无关的概念，注重类型参数化。44. new 和 malloc 的区别new：调用构造函数，支持类型安全。malloc：仅分配内存，不调用构造函数。45. new 可以重载吗？可以改写 new 函数吗？可以重载 new 运算符：用户可以为特定类重载 operator new 和 operator delete 以自定义内存分配逻辑。示例：class MyClass { public: void* operator new(size_t size) { std::cout << "Custom new" << std::endl; return malloc(size); } void operator delete(void* ptr) { std::cout << "Custom delete" << std::endl; free(ptr); } }; int main() { MyClass* obj = new MyClass; delete obj; }46. map 和 unordered_map 的区别与使用场景实现原理：map：基于红黑树，支持有序键值对。unordered_map：基于哈希表，键值对无序。时间复杂度：map：O(log n)。unordered_map：O(1) 平均，O(n) 最坏。使用场景：如果需要键的有序性，使用 map。如果只需要快速查找，无需排序，使用 unordered_map。47. map 和 unordered_map 是线程安全的吗？都不是线程安全的。如果需要多线程访问，需显式加锁（如 std::mutex）。48. 优先队列在 C++ 标准库中的实现实现原理：基于最大堆（默认）。使用 std::vector 存储数据，通过堆排序保证最大堆性质。常用方法：push：插入元素。pop：移除最大元素。top：访问最大元素。49. GCC 编译过程四个阶段：预处理：展开宏定义，处理头文件（gcc -E）。编译：生成汇编代码（gcc -S）。汇编：生成机器代码（gcc -c）。链接：生成可执行文件（gcc）。示例：gcc -o program program.c50. C++ Coroutine（协程）协程是轻量级线程，通过状态保存和恢复实现非阻塞异步操作。C++20 引入协程，通过 co_await、co_yield 和 co_return 实现。51. extern "C" 的作用防止 C++ 编译器对函数名进行名称修饰（name mangling）。用于在 C++ 中调用 C 函数或让 C 调用 C++ 函数：extern "C" void func();52. ELF 文件格式ELF（Executable and Linkable Format） 是可执行文件格式。包含：头部（header）、段表（section table）、程序头表（program header table）。常见段：.text（代码段）、.data（初始化全局变量）、.bss（未初始化全局变量）。53. 符号表符号表包含程序中所有标识符（函数、变量）的名称和地址信息。使用场景：编译器生成目标文件。调试器用于解析符号。54. 单元测试在 C++ 中的实现常用框架：Google Test（GTest）。Catch2。测试内容：功能性测试。边界条件测试。性能测试。数据结构与算法接下来开始解答 数据结构与算法 部分的问题。以下是逐一解答：1. 数组和链表的区别与优缺点数组：优点：随机访问快，空间连续。缺点：插入和删除需要移动元素，扩容耗时。链表：优点：插入和删除效率高（O(1)）。缺点：随机访问慢，需要遍历。2. 快速排序原理基于分治思想：选择一个基准值。分区：将小于基准值的元素放左边，大于的放右边。对子序列递归排序。时间复杂度：平均：O(n log n)。最坏：O(n²)（当选取的基准值极端时）。3. 堆排序的实现使用二叉堆（最大堆）实现。构建最大堆。交换堆顶与最后一个元素。调整剩余元素为最大堆。时间复杂度：O(n log n)。4. 冒泡排序相邻元素比较并交换，大的逐渐冒泡到最后。时间复杂度：平均：O(n²)。最优（完全有序）：O(n)。5. 二分查找及其复杂度前提：数组有序。原理：比较目标值与中间值。根据大小决定查找左半部分或右半部分。时间复杂度：O(log n)。6. 哈希表数据太大导致 rehash 成本高，怎么办？使用分布式哈希表（DHT）分散存储。延迟 rehash：逐步迁移旧桶数据，避免集中操作。7. 二叉树的前序遍历（非递归）void preorderTraversal(TreeNode* root) { if (!root) return; stack<TreeNode*> s; s.push(root); while (!s.empty()) { TreeNode* node = s.top(); s.pop(); cout << node->val << " "; if (node->right) s.push(node->right); if (node->left) s.push(node->left); } }8. 链表反转ListNode* reverseList(ListNode* head) { ListNode* prev = nullptr; ListNode* curr = head; while (curr) { ListNode* next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; }9. 二叉树输出每一层最右边的节点使用层序遍历，每层最后一个节点加入结果。vector<int> rightSideView(TreeNode* root) { if (!root) return {}; vector<int> result; queue<TreeNode*> q; q.push(root); while (!q.empty()) { int size = q.size(); for (int i = 0; i < size; ++i) { TreeNode* node = q.front(); q.pop(); if (i == size - 1) result.push_back(node->val); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } } return result; }10. 求数组中最大的 k 个数使用最小堆：vector<int> topK(vector<int>& nums, int k) { priority_queue<int, vector<int>, greater<int>> minHeap; for (int num : nums) { minHeap.push(num); if (minHeap.size() > k) minHeap.pop(); } vector<int> result; while (!minHeap.empty()) { result.push_back(minHeap.top()); minHeap.pop(); } return result; }11. 二叉树层高计算使用递归：int maxDepth(TreeNode* root) { if (!root) return 0; return 1 + max(maxDepth(root->left), maxDepth(root->right)); }12. 连续子数组中乘积最大的值使用动态规划：int maxProduct(vector<int>& nums) { int maxProd = nums[0], minProd = nums[0], result = nums[0]; for (int i = 1; i < nums.size(); ++i) { if (nums[i] < 0) swap(maxProd, minProd); maxProd = max(nums[i], nums[i] * maxProd); minProd = min(nums[i], nums[i] * minProd); result = max(result, maxProd); } return result; }13. 排序算法的稳定性稳定排序：冒泡排序、归并排序、插入排序。不稳定排序：快速排序、选择排序、堆排序。14. 树的深度和高度深度：从根节点到当前节点的路径长度。高度：从当前节点到叶节点的最长路径。15. 布隆过滤器原理：使用位数组和多个哈希函数来快速判断一个元素是否存在。优点：内存高效。缺点：可能产生误判（不存在的元素被认为存在）。16. 为什么不用布隆过滤器误判率：布隆过滤器可能会误判元素存在，但不能确定元素一定不存在。适用场景：适用于大数据量、查找操作频繁且可以容忍误判的情况（例如缓存、搜索引擎等）。不适用场景：需要高精度的数据结构，且需要频繁删除元素时。17. 图的种类、表示方法与经典算法图的种类：有向图：边有方向。无向图：边没有方向。带权图：每条边有权重。非带权图：边没有权重。图的表示方法：邻接矩阵：二维数组表示图的边关系，适合稠密图。邻接表：链表数组表示图的边，适合稀疏图。经典算法：深度优先搜索（DFS）：从一个节点开始，沿着图的分支尽可能深入。广度优先搜索（BFS）：从一个节点开始，访问所有邻接节点，逐层进行。Dijkstra 算法：求解单源最短路径，适用于带权图。Floyd-Warshall 算法：求解所有节点对之间的最短路径。18. 求最大 K 个数使用优先队列（堆）或快速选择算法：堆法：使用最小堆保留 K 个最大元素，时间复杂度 O(n log k)。快速选择法：基于快速排序思想，时间复杂度 O(n)。19. 大数问题：转换为字符串处理常用方法：将大数转为字符串处理，可以通过字符串操作进行加减乘除等运算。20. 哈希解决冲突开放定址法：如果哈希桶已被占用，依次检查下一个位置，直到找到空桶。线性探测：检查下一个位置。二次探测：按二次函数的方式跳跃查找。链地址法：每个桶存储一个链表，冲突的元素通过链表链接。再哈希法：当负载因子达到某个阈值时，重新哈希并将数据插入新的哈希表。21. 链地址法与再哈希法的区别与应用场景链地址法：将所有哈希冲突的元素存储在链表中。空间开销较大，但查找过程较为简单。再哈希法：当哈希表填充度过高时，重新哈希并增加表的大小。比较适用于较为密集的数据集，能够减少冲突。22. 链表和数组的底层结构设计、关联、区别、应用场景链表：存储在非连续内存位置，指针连接每个元素。适合动态插入和删除，但随机访问慢。数组：存储在连续的内存块中，支持高效的随机访问。适合频繁的读取操作，插入和删除操作较慢。23. 死锁的概念与进程调度算法如何解决死锁死锁：多个进程因相互等待对方释放资源而陷入无限等待状态。死锁的四个必要条件：互斥：至少一个资源被独占。请求与保持：进程已持有一个资源，并请求另一个资源。不可抢占：资源不可从进程中强制抢占。循环等待：存在一种进程等待链。解决方法：资源分配图。检测和恢复：通过定期检查死锁情况。防止死锁：破坏死锁四个条件中的一个。24. 数据结构相关：图的种类，表示方法，图的经典算法图的表示方法：邻接矩阵（适合稠密图）。邻接表（适合稀疏图）。图的经典算法：DFS：深度优先搜索，适合查找路径、连通分量等。BFS：广度优先搜索，适合寻找最短路径（无权图）。Dijkstra：求解单源最短路径，适用于带权图。Kruskal：最小生成树算法，使用并查集优化。Prim：最小生成树算法，适用于稠密图。25. 如何优化一个大数据集的排序使用外部排序方法：归并排序：将数据分块存储在外部存储中，然后使用归并算法进行排序。分布式排序：将数据分发到不同机器处理，最终合并结果。26. 排序算法的稳定性和适用场景稳定排序：冒泡排序、插入排序、归并排序。稳定性：若两个相等的元素在排序后相对位置不变，则为稳定排序。不稳定排序：快速排序、堆排序。适用于不关心元素相对位置的情况。GDB/GCC/G++1. 怎么 debug，怎么看内存泄漏调试：使用 gdb 调试器进行代码调试，设置断点、单步执行、查看变量值等。常用命令：gdb ./program 启动调试器。break <line_number> 设置断点。run 启动程序。next 单步执行。print <variable_name> 查看变量值。查看内存泄漏：使用 Valgrind 工具检查内存泄漏：valgrind --leak-check=full ./program它会显示内存泄漏的具体信息，如泄漏的内存地址和大小。2. GDB 使用 -> 多线程程序切换到某线程栈帧 -> 如何查看寄存器值切换到指定线程： 在 GDB 中使用命令：thread <thread_id>这会切换到指定线程的栈帧。查看寄存器值：info registers或者：info register <register_name>这样可以查看线程的寄存器状态。3. 怎么分析 C++ 的 core 文件生成 core 文件：使用 ulimit -c unlimited 打开 core 文件生成。在程序崩溃时，系统会生成 core 文件，包含崩溃时的堆栈信息。使用 GDB 分析：gdb ./program core然后使用 bt（backtrace）查看崩溃时的调用栈。4. GDB 有哪些命令run：启动程序。break 或 b：设置断点。next 或 n：执行下一行。step 或 s：进入函数内逐行执行。continue 或 c：继续执行直到下一个断点。print <variable>：打印变量值。bt：显示调用栈。info locals：查看局部变量。info threads：查看当前所有线程。5. GCC 和 G++ 的区别GCC：GNU 编译器集合（GNU Compiler Collection），包括 C、C++、Fortran 等多种编译器。主要用于 C 语言的编译。G++：GNU C++ 编译器，专门用于编译 C++ 程序，它实际上是 GCC 的一部分，默认启用 C++ 编译选项。6. Linux 下程序有问题，如何调试？（答 GDB 打开，打上 Breakpoint 进行调试）使用 GDB 调试：编译时使用 -g 选项生成调试信息。通过 gdb ./program 启动调试器。设置断点：break <function_name> 或 break <line_number>。启动程序：run。步进执行：next、step、continue。查看变量：print <variable>。7. GDB 中多线程调试查看当前线程：info threads。切换线程：thread <thread_id>。查看线程栈帧：backtrace。查看寄存器：info registers。操作系统1. 线程和进程的区别、应用场景进程：操作系统中资源分配的基本单位。进程拥有独立的地址空间、代码、数据和文件描述符。应用场景：适合需要高隔离和独立资源的任务，如运行独立的应用程序。线程：是进程中的执行单元，多个线程共享进程的资源（如地址空间）。线程之间的切换比进程切换更轻量。应用场景：适合需要并发执行的任务，如 Web 服务器、并行计算等。2. 多线程中各种锁，读写锁，互斥锁互斥锁（Mutex）：用来保证同一时间只有一个线程能访问共享资源。通常用于保护临界区。读写锁（Read-Write Lock）：允许多个线程同时读共享数据，但写操作是独占的。适合读多写少的场景，例如数据库缓存。自旋锁（Spinlock）：当线程无法获取锁时，它会在原地循环等待，而不是被阻塞。适合锁持有时间非常短的情况。3. 内存池内存池是一种预分配固定大小内存块的方式，用于减少频繁的内存分配和释放操作，避免内存碎片化。典型应用：在嵌入式系统中优化内存管理。高性能的数据库引擎和游戏引擎常常使用内存池。4. 内存管理内存分配策略：动态分配：操作系统通过堆和栈来管理内存，动态分配内存。静态分配：程序编译时分配内存，通常用于全局变量和静态变量。内存回收：垃圾回收：自动管理内存的回收（如 Java 中的垃圾回收器）。手动回收：如 C 和 C++ 中的 free() 和 delete。5. 内存写漏内存写漏（Memory Leak）：程序在运行时分配了内存，但未释放，导致无法再使用这些内存。解决方案：定期进行内存泄漏检查，如使用 Valgrind。使用智能指针（如 std::unique_ptr 和 std::shared_ptr）来自动管理内存。6. 如果频繁进行内存的分配释放会有什么问题吗？内存碎片：频繁的内存分配和释放会导致内存碎片，浪费内存空间，导致性能下降。解决方法：使用内存池技术预先分配一块大内存，避免频繁分配。优化数据结构，尽量减少动态内存分配。7. 如果频繁分配释放的内存很大（>128K），怎么处理？内存池管理：为较大块内存分配一个专门的内存池，避免频繁的操作系统调用 malloc 和 free。内存分配器优化：使用定制的内存分配器（如 tcmalloc）来优化大内存块的分配和释放。8. 虚拟内存以及堆栈溢出相关的问题，堆栈溢出怎么处理虚拟内存：每个进程都有自己的虚拟地址空间，操作系统通过虚拟内存将物理内存和磁盘交换空间结合，提供隔离和内存管理。堆栈溢出：由于递归或栈空间分配过大，导致栈空间溢出。解决方法：减少递归深度，增加栈空间，使用堆代替栈。9. 分段和分页的区别分段：将程序分成逻辑上独立的段（如代码段、数据段），每个段可以有不同的大小。分页：将内存分成固定大小的块（页），并通过页表来管理。分页可以避免内存碎片，适合进程间隔离。10. 进程间通信原理和方式进程间通信（IPC）：用于不同进程之间交换数据，防止进程独立运行。常见方式：管道（Pipe）：父子进程之间单向通信。消息队列（Message Queue）：进程之间的消息传递。共享内存（Shared Memory）：多个进程共享内存区域。信号量（Semaphore）：用于进程同步。11. fork() 读时共享写时拷贝fork() 创建一个子进程，父子进程共享代码段和只读数据，使用 写时拷贝（Copy-on-Write, COW） 技术优化内存分配。读时共享：父子进程共享代码、只读数据。写时拷贝：当子进程写入时，操作系统复制内存页，避免父进程被修改。12. 互斥锁 + 条件变量互斥锁（Mutex）：保证同一时间只有一个线程访问共享资源。条件变量：允许线程在特定条件成立时等待，通常与互斥锁配合使用来解决线程间的同步问题。13. 如果非堆内存一直在增长，可能哪个区域的内存出了问题（Java）堆内存通常用于动态分配对象。栈内存用于局部变量和函数调用信息。如果非堆内存增长，可能是由于 栈溢出 或 全局/静态变量泄漏。14. 堆和栈的区别。什么情况下会往堆里放栈：用于局部变量、函数调用和返回地址。由操作系统自动管理。堆：用于动态分配内存（如 malloc 或 new）。由程序员管理，需要手动释放。什么时候往堆里放：存储生命周期不确定的对象，或需要在多个函数间共享的对象。15. fork() 函数返回值是怎么实现的父进程：fork() 返回子进程的 PID（进程 ID）。子进程：fork() 返回 0。16. 用户级线程和内核级线程的区别用户级线程：由用户空间的线程库管理，操作系统不直接调度用户级线程。线程调度更灵活，但缺乏内核支持。内核级线程：由操作系统内核管理，每个线程都由内核调度。优点是支持多核处理器，但上下文切换和调度开销较大。17. 线程池和线程开销线程池：维护一组线程，任务提交时直接交给空闲线程处理。减少线程创建和销毁的开销，提高性能。线程开销：线程创建、上下文切换和同步操作都需要一定的开销，线程池通过复用线程减少这些开销。18. 线程切换的到底是什么线程切换：指操作系统将 CPU 的控制权从一个线程转移到另一个线程。包括：保存当前线程的状态（如寄存器、栈指针等）。加载新线程的状态，恢复执行。由于线程切换需要保存和恢复大量的上下文信息，因此开销较大。19. 线程同步共享怎么实现线程同步：通过互斥锁、信号量、条件变量等机制来确保多个线程访问共享资源时不会发生冲突。互斥锁（Mutex）：确保同一时刻只有一个线程访问共享资源。读写锁（Read-Write Lock）：允许多个线程同时读取数据，但写入时需要独占锁。20. 互斥同步的方法互斥锁：通过 std::mutex 或 pthread_mutex_t 来保证临界区内只有一个线程能执行。读写锁：通过 std::shared_mutex 或 pthread_rwlock_t 来优化读取操作，支持多个线程并发读，但写时需要独占锁。21. 信号量和自旋锁的区别信号量：可以用于多线程同步和互斥，通常涉及线程阻塞和唤醒。应用场景：线程间的资源管理、生产者消费者问题。自旋锁：当一个线程无法获取锁时，它会在原地循环等待，不会阻塞线程。应用场景：锁持有时间较短时，避免线程阻塞的开销。22. 查看磁盘、CPU 占用、内存占用命令磁盘占用：df、du。CPU 占用：top、htop、ps。内存占用：free、top、vmstat。23. Linux 虚拟地址空间结构/动态库地址无关代码虚拟地址空间：每个进程有独立的虚拟地址空间，包括堆、栈、代码段、数据段等。动态库地址无关：程序和动态库的地址在进程中通过加载器分配，不受物理内存地址影响，支持地址空间布局随机化（ASLR）提高安全性。24. top 命令排查高占有率进程，top 命令的占用率怎么算的top 命令通过周期性地更新进程信息来显示系统的资源使用情况。占用率计算：top 命令显示的 CPU 和内存占用率是每个进程相对于系统总资源的使用比例。25. 谈谈进程创建后在 Linux 中的内存分布？（回答内存四区，虚拟地址空间，栈内存堆内存）在 Linux 中，每个进程都有独立的虚拟地址空间。虚拟地址空间通常分为以下四个部分：代码段（Text Segment）：存放程序的可执行代码，通常是只读的。数据段（Data Segment）：存放程序的已初始化的全局变量和静态变量。BSS段（Block Started by Symbol）：存放程序中未初始化的全局变量和静态变量。堆（Heap）：存放动态分配的内存（通过 malloc 或 new）。堆的增长方向是向上（地址增大）。栈（Stack）：存放函数的局部变量和调用信息。栈的增长方向是向下（地址减小）。虚拟地址空间：每个进程都有独立的虚拟地址空间，虚拟地址空间通过页表映射到物理内存。操作系统使用 内存管理单元（MMU） 将虚拟地址转换为物理地址。26. 在 Linux 系统下，使用 for 循环，一直进行 new 操作，会发生 heap-overflow 吗？如果不会，原因呢？（答应该不会，Linux 系统可能会对此情况进行处理，面试官追问如果不用 C++ 而用 Java 呢，答 Java 虚拟机等，胡扯了一些）C++ 中 new 操作：在 C++ 中使用 new 分配内存时，内存是从堆上分配的。堆内存的大小通常是由操作系统设定的，当堆内存用尽时，会抛出 std::bad_alloc 异常，而不是发生堆溢出。Linux 系统：操作系统通过 虚拟内存管理 和 分页机制 来处理堆内存的使用。即使堆内存达到最大限制，操作系统也能通过 交换空间（Swap Space） 将部分数据转移到磁盘，避免内存溢出。Java 中的内存分配：Java 通过 JVM 管理内存，类似于 C++ 中的堆，但是 Java 堆内存的管理更加自动化。JVM 会使用垃圾回收机制自动清理不再使用的对象，防止内存泄漏。27. 死锁的概念，进程调度算法怎么解决死锁死锁：发生在多个进程或线程相互等待对方释放资源，而导致无法继续执行的情况。死锁的四个必要条件：互斥条件：至少有一个资源被独占。请求与保持条件：一个进程已经持有至少一个资源，但请求更多资源并等待。不可抢占条件：已分配的资源不能被抢占。循环等待条件：存在一组进程，进程间互相等待对方释放资源。解决死锁的方法：死锁预防：通过破坏死锁的四个条件来避免死锁。例如，通过确保资源请求时所有资源都可用来避免请求与保持条件。死锁避免：使用银行家算法来动态分配资源，确保系统始终处于安全状态。死锁检测与恢复：通过定期检查系统状态是否进入死锁，并采取相应的措施（如回滚进程、终止进程等）。28. 讲讲进程管理进程管理：操作系统对进程的创建、调度、执行、终止等进行管理。进程的状态：进程在执行过程中会经历多个状态：就绪（Ready）：进程已加载到内存，等待 CPU 时间片。执行（Running）：进程正在 CPU 上运行。阻塞（Blocked）：进程在等待某些资源（如 I/O 操作），无法继续执行。终止（Terminated）：进程执行完成或被强制终止。进程调度：操作系统通过进程调度算法选择哪个进程使用 CPU。常见的调度算法有：先来先服务（FCFS）：按照进程到达的顺序调度。最短作业优先（SJF）：优先调度执行时间短的进程。时间片轮转（RR）：每个进程分配固定时间片，时间片耗尽后轮换。优先级调度：根据进程的优先级来决定调度顺序。进程控制块（PCB）：操作系统为每个进程维护一个数据结构（PCB），用于存储进程的状态、程序计数器、CPU 寄存器、内存管理信息等。系统编程1. 除了 MQ 和 WebSocket，异步通信的其他方法管道（Pipe）：提供父子进程之间的单向通信。共享内存（Shared Memory）：不同进程可以访问同一块内存区域，实现高效的异步通信。消息队列（Message Queue）：在不同进程间传递数据，支持优先级。信号量（Semaphore）：通过信号量同步进程的操作。2. 为什么要用多线程？多进程可以吗（WebServer的）多线程：适用于 Web 服务器等高并发场景，可以减少进程切换的开销，多个线程共享同一进程的内存和资源，通信和同步更简单。多进程：每个进程有独立的资源，适用于需要高度隔离的应用。多进程虽然隔离性好，但代价较高，进程间通信更复杂且需要额外资源。3. 为什么要用线程池，线程池中的线程是怎么运作的？线程池：通过维护一组线程来避免频繁创建和销毁线程的开销，提高性能。线程池中的线程在空闲时会等待任务，任务到来时会从任务队列中取出并执行，执行完后返回线程池中，等待下一个任务。4. 生产者消费者，信号量的使用生产者消费者：通过一个共享缓冲区来实现数据的生产和消费，使用信号量控制生产者和消费者的操作。生产者：如果缓冲区满，等待空信号量；如果缓冲区未满，则生产数据并释放满信号量。消费者：如果缓冲区为空，等待满信号量；如果缓冲区有数据，则消费数据并释放空信号量。5. 队列空时，消费者和生产者会发生什么？线程池请求队列是用什么实现的？（链表）消费者与生产者：当队列为空时，消费者将等待，直到生产者将数据放入队列。如果队列满，则生产者等待。线程池请求队列：通常使用链表实现，能够动态地增加和删除任务。6. C++ 多线程并发问题（千万级数量级怎么处理）使用线程池来处理大量的并发任务，避免频繁创建销毁线程带来的性能损失。使用无锁数据结构和原子操作（如 std::atomic）来保证线程安全，减少锁的竞争。对于大量数据，使用分布式系统来分担计算负载。7. 哪几种常见的 signal? SIGSEGV... -> 正常终止程序的信号？-> kill 进程，几号信号？常见信号：SIGSEGV：段错误，非法内存访问。SIGTERM：正常终止程序。SIGKILL：强制终止进程。SIGINT：由用户发出的中断（Ctrl+C）。正常终止程序的信号：SIGTERM。kill 进程信号：kill -9 <pid> 发送 SIGKILL 信号。8. 什么情况下会使用静态变量静态变量：在函数或类中声明，生命周期贯穿程序的整个运行过程，适用于需要在多个函数调用间共享状态的场景。示例：用静态变量统计函数被调用的次数。9. 多线程读写同一个静态变量你是怎么解决的？使用互斥锁（mutex）来同步访问静态变量，确保在同一时刻只有一个线程能够读写静态变量。示例：std::mutex mtx; std::lock_guard<std::mutex> lock(mtx);10. 用过无锁编程吗，知道原子操作吗？无锁编程：通过原子操作来避免线程加锁，从而提高性能，常用于多线程共享数据时的同步。原子操作：如 std::atomic 提供的操作，可以确保某些数据操作是原子的，即使在多线程环境中也不会被中断。定时器1. 小根堆定时器是怎么弄的。如果一次 pop 一个的话，高并发情况下会不会有问题小根堆定时器：小根堆是一个优先队列，其中根节点始终是最小的定时任务。定时器通常是通过维护一个小根堆来实现的。每个定时任务都由一个时间戳和任务本身组成，堆的根节点始终是最近到期的任务。高并发问题：如果定时器使用 pop 从堆中取出任务，一次性 pop 一个任务，可能会存在并发访问问题。在高并发场景下，可能会有多个线程同时访问堆，导致竞争条件或数据不一致。为了避免这个问题，可以使用线程安全的堆实现，例如使用互斥锁（mutex）保护堆操作，或者使用无锁的并发数据结构（如 std::atomic 和 std::shared_lock）。2. 心跳检测如何实现心跳检测：用于保证客户端与服务器之间的连接处于活动状态。心跳机制通过定期发送心跳消息来检测连接是否有效。实现方式：客户端和服务器约定一个时间间隔，例如每隔 30 秒，客户端向服务器发送一个心跳包。如果服务器在预定时间内没有接收到心跳包，可以认为客户端已经断开连接。可以使用 定时器 在客户端定期触发心跳包的发送，并在服务器端设定接收心跳的超时时间。3. 为什么用小根堆实现定时器小根堆实现定时器的优势：高效：小根堆可以在对数时间内找到最小的定时任务（即最早到期的任务），这对于定时任务的管理是非常高效的。操作简单：插入和删除任务的时间复杂度为 O(log n)，能够快速处理任务的添加和移除。自动排序：使用堆可以自动对任务进行排序，无需手动维护任务队列。应用场景：适用于任务定时执行的场景，如定时任务调度、延迟任务等。网络原理1. 为什么握手是三次而挥手需要四次三次握手：客户端 -> 服务器：客户端发送 SYN 请求建立连接。服务器 -> 客户端：服务器确认 SYN 请求，发送 SYN+ACK 包。客户端 -> 服务器：客户端确认 ACK 包，连接建立成功。四次挥手：客户端 -> 服务器：客户端发送 FIN 请求关闭连接。服务器 -> 客户端：服务器确认客户端的 FIN 包，发送 ACK。服务器 -> 客户端：服务器发送 FIN 包，要求关闭连接。客户端 -> 服务器：客户端确认服务器的 FIN 包，连接关闭。三次握手是为了确保双方的连接都可以建立，而四次挥手是为了确保双方的连接都能干净地断开。2. TCP 和 UDP 的原理、区别、应用场景TCP：连接导向：可靠的连接，数据传输前需要建立连接（三次握手）。可靠性：通过校验和、序列号、确认号等机制确保数据的可靠传输。流量控制和拥塞控制：保证网络拥塞时传输的稳定性。应用场景：如网页浏览、电子邮件、文件传输等需要可靠性保证的应用。UDP：无连接：数据包发送后无需等待确认，适合实时性要求高的场景。不保证可靠性：数据可能丢失或顺序错乱，但其速度更快。应用场景：如视频流、实时游戏、DNS 查询等。3. TCP 慢启动，拥塞控制实现慢启动：当连接开始时，TCP 将拥塞窗口设置为 1，随着每个确认包的到来，拥塞窗口指数增长，直到达到阈值。拥塞避免：当达到阈值时，增长变为线性。快速重传：当丢包时，接收方发送重复的确认包，发送方会在收到三个重复确认包后立即重传丢失的数据包。4. HTTP 是在 OSI 模型的哪一层HTTP 是应用层协议，位于 OSI 模型的第七层（应用层），主要用于客户端和服务器之间的请求和响应。5. HTTPS 用到的是对称加密还是非对称加密？分别体现在哪里？非对称加密：用于安全握手阶段，服务器使用公钥加密，客户端使用私钥解密。对称加密：用于传输数据阶段，双方通过密钥加密数据，保证传输内容的机密性。6. HTTP2 和 HTTP1 的区别HTTP/1：每个请求响应都需要建立新的连接（除非使用持久连接）。存在队头阻塞问题。HTTP/2：支持多路复用，允许多个请求和响应共享一个连接。使用二进制协议，提高了传输效率。支持头压缩，减少数据传输的开销。7. HTTP1.0 / 1.1 / 2 / 3HTTP/1.0：每个请求和响应使用独立的 TCP 连接，效率较低。HTTP/1.1：支持持久连接，减少了重复连接的开销，但仍存在队头阻塞问题。HTTP/2：解决了 HTTP/1.x 的问题，支持多路复用和流优先级，减少延迟。HTTP/3：基于 QUIC 协议，使用 UDP 传输，进一步减少延迟，改善网络环境下的性能。8. GET 和 POST 区别GET：请求数据的方式，数据附加在 URL 后，适合获取资源，不建议传输敏感数据。POST：提交数据的方式，数据放在请求体中，适合提交表单数据，传输大量数据时更加安全。9. WebSocketWebSocket 是一种全双工通信协议，建立在 TCP 连接之上，允许客户端和服务器之间进行实时双向通信，适合即时消息、在线游戏等应用。10. TCP/IP 五层模型应用层：处理高层协议如 HTTP、FTP、SMTP 等。传输层：负责数据传输协议（TCP、UDP）的处理。网络层：负责 IP 路由和地址分配。数据链路层：处理物理地址和帧的传输。物理层：实际的硬件传输。11. DNS 服务器用的是什么协议DNS 使用的是 UDP 协议，因为 DNS 查询通常要求快速响应，不需要可靠传输。12. Ping 命令用的是什么协议？在哪一层Ping 使用的是 ICMP（Internet Control Message Protocol） 协议，位于 网络层。用于测试网络连接性，检查目标主机是否可达。13. 如果解析 HTTP 请求的时候，用户一次性没传完数据，（如果头部都没传完，请求报文长度字段都没传完，怎么办）解决方法：在接收 HTTP 请求时，通常使用 流式解析 方法。在接收到部分数据时，服务器将等待更多的数据直到完整的报文被接收为止。HTTP 请求报文 是通过数据流传输的，在传输过程中如果数据不完整，服务器会暂停解析，直到接收到完整的请求头和请求体。如果头部信息不完整，服务器会继续等待，直到头部字段完全接收。14. 路由表说一下路由表 是操作系统中存储路由信息的数据结构。它用于根据目标 IP 地址决定数据包的转发路径。每个路由表条目通常包括：目标网络：要到达的目的地 IP 地址或网络。子网掩码：用于与目标 IP 地址匹配的掩码。网关：下一跳路由器的 IP 地址。接口：通过哪个网络接口发送数据包。15. 路由表为空怎么找到下一跳如果路由表为空，无法找到明确的下一跳。通常，操作系统会使用 默认路由（默认网关）。如果没有设置默认路由或网络路径不可达，数据包将无法被传递。下一跳的查找：通过操作系统的网络配置文件（如 Linux 上的 /etc/network/interfaces 或 ip route 命令）来设置默认路由。默认路由一般是指向本地网络外的网关。16. 粘包拆包是什么，发生在哪一层粘包与拆包：这是 TCP 层面常见的问题，发生在数据传输过程中。粘包：多个小包被合并成一个大包，接收方无法知道数据的边界。拆包：一个大包被拆分成多个小包，接收方无法知道数据完整性。解决方案：在应用层通过 定长消息包 或 特殊分隔符（如 HTTP 协议中的换行符）来识别数据边界。TCP 协议本身 并不保证消息边界的完整性，因此这类问题通常发生在 应用层。17. TCP 在什么情况下会出现大量 TIME_WAIT，哪个阶段出现TIME_WAIT 状态：TCP 连接关闭后，客户端进入 TIME_WAIT 状态，等待足够长的时间以确保对方收到关闭连接的请求。发生阶段：在 连接终止 阶段（由 FIN 包发起的关闭过程）。原因：TIME_WAIT 状态的存在是为了确保在对方没有收到 FIN 包时，能重新发送。它确保即使出现网络延迟，关闭连接的请求也能完全传递。问题：大量的 TIME_WAIT 状态会消耗系统资源，尤其在高并发场景中，可能导致端口耗尽。18. TCP 包头字段，标志位 -> 建立连接过程，终止连接过程 -> TIME_WAIT, CLOSE_WAIT 分析，属于哪一方？SYN、ACK 标志位：SYN：同步标志位，用于连接请求。ACK：确认标志位，用于确认接收到的数据包。建立连接过程（SYN, SYN-ACK, ACK）：客户端发送 SYN 包请求建立连接。服务器响应 SYN-ACK 包确认。客户端再发送 ACK 包，连接建立。终止连接过程（FIN, ACK）：任何一方发起连接关闭时，发送 FIN 包表示关闭连接，另一方回应 ACK 确认。TIME_WAIT 状态：通常由客户端进入，确保连接关闭后不会遗留数据。CLOSE_WAIT 状态：通常由服务器进入，表示服务器已收到关闭请求并正在准备关闭连接。19. TCP 建立连接过程 -> SYN + ACK 包能不能拆开来发不能拆开发：TCP 的连接建立过程严格按照顺序进行。SYN 包：用于发起连接请求。SYN + ACK 包：用于确认连接请求并返回给客户端。如果将这两个包拆开发送，会导致连接建立失败，因为 TCP 规定客户端收到 SYN 后，必须发送带有 SYN 和 ACK 的响应包。20. 讲讲 QUIC / 听说过哪些快速重传算法 / TIME_WAIT 状态干啥用的QUIC（Quick UDP Internet Connections）：是基于 UDP 协议的传输协议，由 Google 提出。相比 TCP，QUIC 具有以下优点：更低的连接建立延迟，因为它结合了 TLS 和传输层功能。多路复用和流量控制，避免了 TCP 的队头阻塞。快速重传算法：当发送方收到三个重复的 ACK 包时，它会立即重传丢失的数据包，而不等待超时。TIME_WAIT 状态：如前所述，TIME_WAIT 状态用于确保连接的关闭消息能可靠传递，防止遗留的老数据影响新连接。21. 提到了 TCP，粘包怎么解决？（固定包头接收，指定内存长度）粘包问题：在 TCP 协议中，数据流是连续的，接收方无法知道消息的边界，导致多个包的数据被合并成一个包。解决方法：固定包头长度：通过约定每个消息的包头包含消息的总长度或数据块的长度，接收方可以根据包头信息确定数据包的边界。分隔符：使用特定的字符（例如换行符、特殊符号）作为消息的结束标志，接收方根据这个分隔符来判断消息的边界。定长消息：所有消息的长度是固定的，接收方每次读取固定长度的数据。22. 查看网络状况（以为是 netstat，其实是 ping、traceroute，紧张忘记说了）查看网络状况的常用命令：netstat：查看网络连接、路由表、接口统计等。常用于检查网络状态和排除故障。示例：netstat -tuln（查看所有监听的 TCP/UDP 端口）ping：检查目标主机是否可达，发送 ICMP Echo 请求并接收响应。示例：ping <hostname/IP>traceroute：显示数据包从源主机到目标主机经过的所有路由节点。示例：traceroute <hostname/IP>ifconfig / ip：查看和配置网络接口。示例：ip addr show（查看网络接口的 IP 地址信息）23. 抓包工具？（wireshark，紧张又给忘了靠）Wireshark 是一款流行的网络抓包工具，用于分析网络数据包、诊断网络问题。Wireshark 可以捕获通过网络传输的所有数据包，并提供详细的协议分析。功能：实时抓包和分析。支持多种协议，如 TCP、UDP、HTTP、DNS 等。可以显示包的详细信息，帮助开发者调试网络应用。用法：安装 Wireshark 后，选择网卡开始抓包。可以设置过滤器，仅显示感兴趣的数据包。抓包完成后，可以保存文件，以便后续分析。24. TCP 2MSL 说一下，为什么2MSL（Maximum Segment Lifetime）是 TCP 协议中的一个概念，表示 TCP 连接在 TIME_WAIT 状态下持续的时间。MSL（Maximum Segment Lifetime）：指一个数据包在网络中可能存在的最大时间，通常为 2 分钟。2MSL：在连接关闭后，端点需要保持 TIME_WAIT 状态至少为 2 倍 MSL，确保所有的数据包都已从网络中消失，并且不会干扰未来的连接。原因：防止因网络延迟或数据包未及时到达，导致重复的老数据干扰到新连接。确保所有的 TCP 数据包都能够被正确传送并确认，防止因数据重传导致问题。25. 网络层中如何确保路由的选择、处理数据包的转发网络层的主要任务是路由选择和数据包转发。它通过使用 路由表 来决定数据包的转发路径。路由表 中包含目标地址和下一跳地址。当数据包到达某个路由器时，路由器根据目标地址查找路由表，选择下一个转发节点。路由协议（如 RIP、OSPF、BGP）用于动态更新路由表，确保最优路径的选择。数据包转发：数据包根据目的地址经过每个路由器时，都会通过路由表查找下一跳的地址。在最终目标到达时，数据包会通过 目的地网络接口 送达接收方。26. DNS 中的负载均衡DNS 负载均衡：通过 DNS 服务器提供多个 IP 地址（同一域名对应多个 IP），分配客户端请求到不同的服务器上，从而实现负载均衡。方法：轮询法（Round-robin DNS）：DNS 服务器返回不同的 IP 地址，每次返回的顺序不同。基于权重的负载均衡：为每个 IP 地址分配权重，较高权重的服务器会承担更多的请求。地理位置负载均衡：根据客户端的地理位置，将请求引导到距离最近的服务器。优点：实现简单，无需额外的硬件设备。支持横向扩展，通过添加更多的 IP 地址进行负载均衡。网络编程1. 为什么要用 epollepoll 是 Linux 提供的高效 I/O 多路复用机制。它与 select 和 poll 相比，在高并发场景中具有显著优势，主要体现在以下几个方面：性能：epoll 在文件描述符数量较多时，不会像 select 和 poll 一样每次遍历所有文件描述符，避免了 O(n) 的性能瓶颈。事件驱动：epoll 是基于事件驱动的，只有文件描述符发生变化时，才会通知应用程序，避免了无谓的资源消耗。适用于大规模并发：epoll 特别适合于需要同时处理大量并发连接的场景，如高性能的 Web 服务器。2. epoll 实现原理，epoll 使用的哪种模式，除了 epoll，了解 select/poll 吗epoll 实现原理：epoll 基于内核和用户空间共享内存的机制。应用程序通过 epoll_ctl 操作来注册、删除和修改感兴趣的文件描述符。内核会根据事件通知用户空间，用户空间再通过 epoll_wait 获取发生的事件。模式：水平触发（LT，Level Triggered）：当文件描述符的状态满足条件时，epoll 会通知用户。边缘触发（ET，Edge Triggered）：只有文件描述符的状态发生变化时，epoll 才会通知用户。select/poll：select 和 poll 都是较早的 I/O 多路复用机制，但它们的性能在处理大量并发连接时较差。select 有最大文件描述符限制，poll 没有限制，但它们在高并发环境中会花费更多时间遍历文件描述符。3. 怎么理解多路复用机制多路复用机制：是指通过一个线程或进程处理多个 I/O 操作。在网络编程中，常见的多路复用技术如 select、poll 和 epoll。I/O 多路复用：允许一个线程同时监听多个文件描述符（如网络连接）。当文件描述符的状态发生变化时，操作系统通知应用程序。非阻塞 I/O：当没有数据可用时，程序不会被阻塞，而是可以继续执行其他任务，提高资源利用率。事件驱动：在多路复用中，当文件描述符准备好进行 I/O 操作时，会触发事件进行处理，而无需轮询所有文件描述符。4. Reactor 和 Proactor 的好处和坏处，为什么要用 Reactor 而不用 ProactorReactor 模型：工作原理：Reactor 模型是事件驱动的，事件处理通过回调函数来实现。应用程序通过注册感兴趣的事件，Reactor 负责监听并通知。优点：适用于 I/O 密集型应用，能够高效处理多个并发连接。非阻塞，避免了线程被长时间占用。缺点：需要开发者手动管理事件循环和回调函数，较为复杂。Proactor 模型：工作原理：Proactor 模型将 I/O 操作的处理交给操作系统，应用程序只需要处理 I/O 完成事件。优点：不需要处理 I/O 操作，减少了程序的复杂性。缺点：操作系统对 I/O 操作的支持有限，且 Proactor 在部分操作系统上可能不被广泛支持。为什么使用 Reactor 而不用 Proactor：Reactor 模型更加灵活，并且可以在所有平台上工作。Proactor 模型虽然适合某些场景，但它对操作系统的支持要求较高，且不适用于所有平台。5. select 怎么用，底层原理select 使用方法：select 用于监听多个文件描述符的状态变化（是否可读、可写、异常）。使用 FD_SET 来设置感兴趣的文件描述符集合。select 调用会阻塞，直到至少一个文件描述符就绪，或者超时。示例：fd_set readfds; FD_ZERO(&readfds); FD_SET(socket_fd, &readfds); int n = select(socket_fd + 1, &readfds, NULL, NULL, NULL); if (n > 0) { // 处理已就绪的文件描述符 }底层原理：select 通过遍历所有文件描述符集合，检查哪些文件描述符的状态满足条件。这会导致较高的性能开销，尤其在高并发场景中。6. select 为什么只能支持 1024 个，poll 和 epoll 是怎么解决这个问题的select 的文件描述符限制：早期的 UNIX 系统对 select 函数的文件描述符集合大小有一个限制，通常为 1024。这个限制是由 FD_SETSIZE 定义的。poll：poll 不再有文件描述符数量的硬性限制，它使用 pollfd 结构来代替 select 中的 fd_set，并且没有 FD_SETSIZE 限制。但它依然需要遍历所有的文件描述符，性能在高并发场景中下降。epoll：epoll 通过内核与用户空间共享内存的方式，高效地处理大量文件描述符，避免了 select 和 poll 的性能瓶颈。epoll 没有文件描述符数量的限制，且只返回已就绪的文件描述符，具有 O(1) 的性能。7. epoll 底层为什么用红黑树不用 hash红黑树：epoll 使用红黑树来管理文件描述符，主要因为红黑树是自平衡的二叉搜索树，能保证插入、删除和查找的时间复杂度为 O(log n)。优势：红黑树能够保持有序性，并且能高效处理文件描述符的添加和删除。哈希表：哈希表查找效率高，但可能会产生哈希冲突，需要额外的处理，因此相比红黑树，哈希表可能会增加额外的管理开销。8. ET 和 LT 的区别，I/O 多路复用ET（Edge Triggered）：只会在文件描述符的状态从不可用变为可用时触发一次事件，之后不会再触发，直到事件状态再次发生变化。适用于高性能的场景，因为事件只在状态变化时通知一次，减少了不必要的调用。使用时需要确保数据完全读取或写入，否则可能会丢失数据。LT（Level Triggered）：每次检测到文件描述符的状态满足条件时，都会通知应用程序。这意味着，文件描述符处于可读状态时，应用程序会不断被通知直到数据被完全读取。适用于相对简单的场景，但在高并发时，可能会有多次无意义的通知。I/O 多路复用：I/O 多路复用是指通过单个线程或进程来监听多个 I/O 通道（如多个网络连接）。当某个连接的 I/O 状态发生变化时，应用程序被通知，可以进行数据读取或写入操作。常见的 I/O 多路复用技术有 select、poll、epoll，它们的优点是能高效地处理大量并发连接，避免了为每个连接创建一个线程或进程带来的性能开销。9. 游戏中数据传输用啥协议（有没有改进的协议，基于 UDP 的可靠传输）游戏中数据传输协议：游戏通常需要高效的实时数据传输，因此 UDP 是游戏中的常用协议。UDP 提供了低延迟和高吞吐量，但不保证数据可靠性。基于 UDP 的可靠传输：RUDP（Reliable UDP）：为了弥补 UDP 的不可靠性，开发了 RUDP 协议。RUDP 在 UDP 的基础上增加了确认机制和重传机制，使得 UDP 在保持低延迟的同时，能够实现可靠的数据传输。QUIC：是一个基于 UDP 的传输协议，由 Google 提出。QUIC 提供了类似于 TCP 的可靠性，同时优化了连接建立时间、减少了延迟。QUIC 采用了多路复用和加密，广泛应用于 HTTPS 的加速。其他协议：TCP：虽然 TCP 提供了可靠的数据传输，但在游戏中，由于其较高的延迟和拥塞控制机制，通常不会用于实时数据传输。WebRTC：对于实时通信应用（如多人游戏中的语音聊天），WebRTC 也采用了基于 UDP 的协议，并且增加了可靠性和加密保障。10. 项目架构（Web Server）两种高并发模式（问的很细）高并发模式：在设计高并发的 Web 服务器时，通常采用以下两种架构模式：多进程模型：每个请求由独立的进程来处理。进程之间相互独立，互不干扰。优点：高度隔离，每个进程的崩溃不会影响其他进程。适合 CPU 密集型任务，因为可以在多核 CPU 上并行执行。缺点：进程的创建和销毁开销较大。进程间通信（IPC）复杂且效率较低。多线程模型：使用多个线程来处理并发请求，线程共享进程的资源（如内存）。优点：线程创建和销毁比进程更轻量，效率较高。线程间通信通过共享内存实现，开销较小。缺点：需要解决线程同步问题（如死锁、资源竞争等）。单个线程的崩溃可能影响整个进程。高并发 Web 服务器：事件驱动模型（Reactor 模型）：单线程或少量线程通过多路复用机制（如 epoll）监听多个连接，避免为每个连接创建新的线程或进程。适用于 I/O 密集型场景。线程池/进程池模型：对于 CPU 密集型任务，使用线程池或进程池处理请求，避免频繁创建和销毁线程/进程带来的性能损耗。11. 除了 Reactor 模型，还有什么模型Reactor 模型 是一种事件驱动的编程模式，广泛应用于高并发服务器中，特别是 I/O 密集型的应用。除了 Reactor 模型，常见的还有以下几种模型：Proactor 模型：在 Proactor 模型中，操作系统负责处理 I/O 操作，应用程序不需要直接管理 I/O 事件的触发。应用程序发起 I/O 操作后，操作系统会异步地处理这些操作，并通过回调函数通知应用程序。优点：操作系统直接处理 I/O，简化了应用程序的编程。没有事件循环的开销，适用于需要大量异步 I/O 的系统。缺点：操作系统对异步 I/O 的支持不如同步 I/O 稳定，且在某些平台（如 Linux）上，Proactor 模型的实现较为复杂。Thread-per-Connection 模型：每个客户端连接由一个独立的线程处理。每个请求都由新的线程处理，这种方式简单直观。优点：实现简单，易于理解。适合连接数较少的应用。缺点：随着并发连接数增加，线程的创建和上下文切换开销变得很大。资源消耗较多，可能导致系统崩溃。Worker 模型：使用固定数量的线程池来处理连接请求，任务由线程池中的线程处理。优点：减少了线程创建和销毁的开销。可以限制线程的数量，避免过多线程占用系统资源。缺点：线程池的大小需要合理配置，过大或过小都会影响性能。Asynchronous I/O 模型：应用程序提交 I/O 请求后，继续处理其他任务。当 I/O 完成时，系统通知应用程序。通过回调机制或事件通知机制来处理数据。优点：非阻塞，能高效地处理大量并发请求。不需要手动轮询或创建过多线程。缺点：编程模型复杂，需要合理的事件驱动或回调机制。数据库1. MySQL 有哪些引擎MySQL 提供了多种存储引擎，最常见的有：InnoDB：支持事务、外键、行级锁，是默认的存储引擎，适用于高并发和事务需求的场景。MyISAM：不支持事务，表锁，适合于读取操作多的应用，但对于高并发和事务处理较弱。Memory：数据存储在内存中，速度很快，适合临时数据存储。CSV：将表格数据存储为 CSV 格式文件，适用于导出和导入操作。Archive：用于存储大量的归档数据，支持只写入且不修改的场景。2. 数据库的架构数据库架构通常包括以下组件：存储引擎：决定数据的存储、管理和访问方式（如 InnoDB、MyISAM）。数据库服务器：提供与数据库的连接，负责数据的处理和查询。客户端：与数据库进行交互的用户端应用程序，通常通过 SQL 查询与数据库服务器通信。数据库管理系统（DBMS）：控制和管理数据库的操作，确保数据的一致性、完整性和安全性。3. 不同引擎对索引的支持InnoDB：支持 主键索引 和 二级索引。主键索引是聚集索引，数据按主键顺序存储。二级索引存储的是指向主键的指针。支持 全文索引（从 MySQL 5.6 开始支持）。支持 空间索引（适用于地理信息数据）。MyISAM：只支持 非聚集索引，即所有索引都在单独的索引文件中，数据存储与索引分离。支持 全文索引。4. InnoDB 和 MyISAM 的区别InnoDB：支持事务，ACID 特性。支持外键约束。行级锁，适用于高并发写操作。数据存储和索引都采用 B+ 树结构。MyISAM：不支持事务。不支持外键约束。表级锁，适用于高并发读操作。数据存储和索引采用 B 树结构。5. 隔离级别数据库的隔离级别决定了事务之间如何互相影响，主要有以下四种：读未提交（Read Uncommitted）：允许脏读，一个事务可以读取另一个事务未提交的数据。读已提交（Read Committed）：只允许读取已提交的数据，防止脏读，但可能会发生不可重复读。可重复读（Repeatable Read）：确保事务中多次读取的数据一致，防止脏读和不可重复读，但可能会出现幻读。串行化（Serializable）：最高的隔离级别，强制事务串行执行，避免所有并发问题，但性能较差。6. 最左前缀原则最左前缀原则：在使用联合索引时，查询条件需要从左到右顺序匹配索引列。如果查询条件不按索引顺序给出，索引可能不能有效使用。例如，如果索引是 (a, b, c)，查询条件 WHERE a=1 AND b=2 会使用该索引，但 WHERE b=2 AND a=1 可能无法利用该索引。7. MySQL 的集群是用什么方式去增加并发量读写分离：将读请求和写请求分配到不同的数据库实例上，通常通过 主从复制 来实现，主库负责写操作，从库负责读操作。分片（Sharding）：将数据分布到多个数据库实例上，减少每个实例的数据量，从而提高并发能力。可以通过水平分片和垂直分片实现。负载均衡：通过负载均衡器将请求分发到多个数据库服务器，避免单点压力。8. 除了读写分离还有吗？读写分离是常见的提高并发量的方案，但还有以下方式：分库分表：将数据按某种规则划分到多个数据库或多个表中，减少单一表或库的负载。缓存层：通过 Redis 等缓存技术，将热点数据缓存到内存中，减少对数据库的访问频率，提升性能。数据库索引优化：通过合理的索引设计，减少查询的时间，提高数据库的响应速度。9. mysql的隔离级别和锁锁：MySQL 提供了多种锁来控制并发事务访问：共享锁（S锁）：允许其他事务进行读取，但不能修改数据。排他锁（X锁）：只允许当前事务操作数据，其他事务无法读取或修改数据。意向锁：主要用于表级锁的管理，在执行实际的行级锁时，使用意向锁来表明当前事务对某些行进行锁定。10. 数据库 delete 和 truncate 区别DELETE：删除数据时，会逐行删除，每删除一行都会记录日志，因此比较慢。可以添加 WHERE 子句来删除特定数据，支持回滚。会触发触发器。TRUNCATE：删除表中所有数据，不逐行删除，而是通过删除整个数据页来清空表，速度更快。不支持回滚，且不能添加 WHERE 子句。不会触发触发器。11. MySQL 索引（B+ 树）B+ 树是 MySQL 中最常用的索引数据结构，它是 B 树的变种，所有的数据都存储在叶子节点，内部节点仅存储索引信息。B+ 树支持 范围查询 和 高效查找，广泛用于 主键索引 和 二级索引。12. B 树和 B+ 树的区别B 树：内部节点和叶子节点都存储数据。支持查找、插入、删除等操作，但效率较低。B+ 树：所有数据都存储在叶子节点，内部节点只存储索引，增加了存储密度。适用于范围查询，因为叶子节点按顺序连接，支持顺序遍历。13. B+ 树树高怎么算？树高为 4 能支持多少数据量树高的计算：树高是指从根节点到叶子节点的最大路径长度。假设每个节点的最大子节点数为 m，那么树高 h 可以通过以下公式计算：最多可以存储 m^h 个数据。如果树高为 4，每个节点有 m 个子节点，则最多存储 m^4 个数据。14. 数据库范式（1NF、2NF、3NF、BCNF）1NF（第一范式）：数据表中的每个字段都必须是原子性的，即不能有多个值。每个字段只存储一个数据项，不能是集合或数组。2NF（第二范式）：满足 1NF 的基础上，所有非主键字段完全依赖于主键。避免了部分依赖，即所有非主键属性必须依赖于主键的全部组成。3NF（第三范式）：满足 2NF 的基础上，所有非主键字段必须直接依赖于主键，而不能传递依赖。这意味着如果有一个非主键字段依赖于另一个非主键字段（间接依赖），则需要拆分成不同的表。BCNF（博茨-科德范式）：满足 3NF 的基础上，所有的决定因素都是候选键。即表中的每个决定因素（非主键字段决定其他字段的值）必须是候选键，消除了所有类型的依赖。15. 数据库设计的目标数据一致性：确保数据库中的数据是准确、完整的，避免出现不一致或错误的数据。数据冗余最小化：通过范式化和合理设计表结构，减少数据冗余，降低维护复杂度。数据安全性：保护数据不被非法访问或篡改，可以通过访问控制和加密等手段来实现。高效性：确保数据库查询、插入、更新和删除操作能够高效执行，通常通过索引、优化查询等方式来实现。可扩展性：数据库设计应考虑未来数据量的增长，确保能够灵活扩展。16. 数据库事务（ACID）ACID 是确保数据库事务可靠性的四个基本属性：原子性（Atomicity）：事务中的操作要么全部完成，要么全部不做，不能只做部分操作。一致性（Consistency）：事务开始前和结束后，数据库必须从一个一致的状态转变到另一个一致的状态。隔离性（Isolation）：多个事务并发执行时，必须保证它们之间的操作互不干扰，每个事务都应该像在独立运行一样。持久性（Durability）：一旦事务提交，它对数据库的修改就是永久的，即使系统崩溃，也应该能够恢复。17. 数据库索引的类型和原理索引类型：B+ 树索引：MySQL 默认的索引类型，数据按顺序排列，支持范围查询，效率高。哈希索引：通过哈希算法计算数据项的地址，适用于等值查询，但不支持范围查询。全文索引：专门用于处理文本字段，支持对文本内容的快速查找，适用于搜索引擎等场景。空间索引：用于处理空间数据（如地图上的地理位置数据），常用 R 树或 R+ 树实现。索引原理：索引通过构建数据结构（如 B+ 树、哈希表等）来加速查询过程，减少全表扫描的时间。索引通过将数据按特定的规则排序或映射，使得查询操作能够更高效地查找目标数据。18. 什么是 SQL 注入，如何防止SQL 注入：一种通过在 SQL 查询中插入恶意 SQL 代码的攻击方式，攻击者通过操控 SQL 查询语句获取、修改数据库中的数据。防止 SQL 注入：使用预编译语句（Prepared Statements）：使用数据库驱动提供的预编译语句机制，避免直接拼接 SQL 查询，防止恶意代码注入。使用 ORM（对象关系映射）：ORM 框架通常会自动对查询进行参数化处理，减少 SQL 注入的风险。输入验证：对所有用户输入进行严格验证，确保输入数据符合预期格式。最小化权限：数据库用户权限最小化，避免攻击者通过注入获取过多权限。19. 数据库连接池的作用数据库连接池：是为了管理数据库连接资源而设计的一种技术，允许多个应用线程共享固定数量的数据库连接。作用：提高性能：避免了频繁地创建和销毁数据库连接的开销，通过连接池复用连接，提高性能。资源管理：通过连接池统一管理连接的生命周期，防止连接泄漏或资源浪费。负载均衡：连接池可以根据负载情况动态调整连接的数量。20. MySQL 数据库的备份与恢复备份方式：物理备份：直接复制数据库的数据文件和日志文件（如通过 mysqldump 或复制数据文件的方式）。逻辑备份：导出数据库的结构和数据，通常使用 mysqldump 工具。逻辑备份生成的 SQL 文件可以用于恢复数据。恢复方式：从物理备份恢复：直接使用备份的数据文件恢复。从逻辑备份恢复：使用 mysqldump 生成的 SQL 文件来恢复数据库结构和数据。增量备份与恢复：通过二进制日志（binlog）进行增量备份，只有变化的部分被备份，从而节省空间和时间。21. 如何优化数据库查询优化查询：使用索引：对查询条件字段建立合适的索引，避免全表扫描。避免使用 SELECT * 查询：尽量只查询所需的字段，减少数据传输量。查询缓存：启用查询缓存，将常用查询的结果缓存起来，减少重复计算。SQL 语句优化：避免不必要的子查询，使用联合查询（JOIN）而非多个独立查询。数据分区：通过分区表来管理大量数据，将数据分割成多个物理文件，提高查询效率。22. 数据库分区（Partitioning）分区：将大表分成更小、更易于管理的部分，减少查询的数据量，提高性能。分区类型：范围分区（Range Partitioning）：基于某一字段的范围将数据分割成多个分区。哈希分区（Hash Partitioning）：使用哈希算法将数据均匀分布到不同的分区中。列表分区（List Partitioning）：根据具体的值将数据分配到不同的分区。组合分区（Composite Partitioning）：将两种或多种分区方式结合起来使用。23. 如何加快数据检索的效率加快数据检索效率的常见方法包括：创建合适的索引：通过索引减少全表扫描的时间。可以根据查询条件中的字段创建索引。避免使用 SELECT * 查询：只查询所需的字段，减少不必要的数据传输。查询缓存：将常用查询的结果缓存起来，减少数据库的计算压力。优化查询语句：避免不必要的子查询和 JOIN 操作，合理使用查询条件。分区表：对于大型表，通过分区技术将数据拆分为多个更小的部分，提高查询效率。24. 注册登录的用户名和密码存在哪里？用户名和密码通常存储在数据库中。为了保证安全，密码应该进行加密（如使用哈希算法），并且不要直接存储明文密码。常用的做法：哈希加密：使用如 SHA-256、bcrypt 等哈希算法将密码加密，防止泄漏明文密码。盐值：为了防止使用相同密码的用户生成相同的哈希值，可以为每个密码添加一个随机的盐值（Salt），增加安全性。25. 面试官灵魂4连问：乐观锁与悲观锁的概念、实现方式、场景、优缺点乐观锁：概念：假设不会发生冲突，在操作数据时不加锁。只有在提交时检测数据是否被其他事务修改，如果修改则回滚或重试。实现方式：通常通过 版本号 或 时间戳 来标记数据的修改，提交时检查数据是否被修改。场景：适用于冲突较少的场景，如并发读取较多的环境。优缺点：优点：减少锁的竞争，提高性能。缺点：如果发生冲突，需要重试，可能会导致资源浪费。悲观锁：概念：假设会发生冲突，在操作数据时对数据加锁，直到操作完成才释放锁。实现方式：使用 排他锁 或 共享锁 来控制对数据的访问。场景：适用于冲突较多的场景，如金融交易、库存管理等需要保证一致性的环境。优缺点：优点：能够确保数据的一致性。缺点：可能会导致死锁，影响性能，尤其在高并发环境下。26. 一千五百万行数据如何快速找到某一行数据，给出方案，设计数据库表结构方案：索引：为要查询的字段（如用户ID、订单号等）创建索引，通过索引来快速定位数据，避免全表扫描。分区表：将数据按照某种规则（如日期、区域等）进行分区，减少查询的数据量。查询优化：合理设计 SQL 查询语句，避免不必要的 JOIN 和子查询，使用 LIMIT 限制返回的行数。数据库表设计：假设需要存储用户信息：CREATE TABLE users ( user_id INT NOT NULL AUTO_INCREMENT, username VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (user_id), INDEX (email) -- 为常用查询字段创建索引 );27. SQL 优化常见优化手段：索引优化：根据查询条件创建合适的索引，减少扫描的行数。查询重写：避免使用 SELECT *，只查询所需的字段。避免复杂的子查询和不必要的 JOIN 操作。避免全表扫描：确保查询条件使用索引，避免全表扫描。使用连接池：避免频繁建立和关闭数据库连接，提高性能。避免频繁的更新操作：对于频繁更新的数据，可以使用缓存系统（如 Redis）进行缓存，减少数据库的负载。28. 事务事务 是一组数据库操作的集合，保证其要么全部执行成功，要么全部失败回滚，保证数据的一致性。四个特性（ACID）：原子性：事务中的操作要么全部完成，要么全部回滚。一致性：事务执行前后，数据库必须从一个一致的状态变成另一个一致的状态。隔离性：事务执行时，不受其他事务的干扰。持久性：事务提交后，对数据库的修改会永久生效，即使系统崩溃也能恢复。29. 什么情况下使用读已提交读已提交（Read Committed） 隔离级别保证一个事务只能读取其他事务已提交的数据。适用于应用程序对数据一致性要求不高的场景。使用场景：当对数据一致性要求不是特别高时，可以使用读已提交，例如在读操作频繁的应用中，减少锁的竞争。对于某些无需多次读取相同数据的应用，如简单的数据查询操作，使用读已提交可以提高性能。30. 对于脏读的理解脏读（Dirty Read） 是指一个事务读取了另一个事务尚未提交的数据。如果另一个事务回滚，这些数据将不再有效，导致读取的数据不一致。在 读已提交（Read Committed） 隔离级别下，脏读被避免，因为只能读取已提交的数据。31. 慢查询怎么看，怎么优化慢查询：MySQL 提供了慢查询日志来记录执行时间较长的 SQL 查询。可以通过开启慢查询日志来监控哪些查询耗时较长。查看慢查询：启用慢查询日志：在 MySQL 配置文件中启用慢查询日志，设置 long_query_time 参数来定义查询超过多少秒会被记录。使用工具分析慢查询日志，如 mysqldumpslow 或 pt-query-digest。优化慢查询：创建索引：为查询条件字段创建索引，减少查询的扫描范围。优化查询语句：避免使用复杂的 JOIN 和子查询，尽量简化查询。避免使用 SELECT *：只选择需要的字段，减少数据传输和计算量。查询缓存：对于频繁执行的查询，可以启用查询缓存。32. 联合索引(a,b,c)，where a, b, c 和 where b, a, c 区别联合索引：如果索引是 (a, b, c)，MySQL 会根据索引列的顺序来建立索引。WHERE a, b, c：可以利用索引的最左前缀原则，首先使用 a 索引，接着使用 b 和 c。WHERE b, a, c：不能有效使用索引，因为 b 和 a 的顺序与索引列顺序不匹配，导致无法使用最左前缀原则。33. 是否了解数据库底层MySQL 的底层实现：MySQL 存储引擎通过 InnoDB（默认引擎）和 MyISAM 等不同的存储方式来管理数据。InnoDB：采用 B+ 树索引，支持事务、行级锁、外键等，数据存储在聚集索引中。MyISAM：采用 B 树索引，不支持事务和外键，适用于大量读操作的场景。文件存储：数据库中的数据存储在不同的文件中（如 .ibd 文件、.frm 文件等），并且通过 binlog 记录数据更改，用于恢复和复制。Redis1. Redis 的数据结构Redis 提供了多种数据结构，用于处理不同的数据类型：String：最基本的键值对类型，支持设置、获取、加法、减法等操作。List：双向链表，支持在两端进行推送和弹出操作，适合实现队列、栈等结构。Set：无序集合，支持添加、删除和判断元素是否存在的操作，适用于去重。Sorted Set (Zset)：有序集合，每个元素有一个分数，通过分数进行排序，支持范围查询。Hash：存储多个字段值的映射，适合存储对象。Bitmap：位图，常用于大规模的数据标记、状态存储等。HyperLogLog：用于基数统计的近似算法，适合处理海量数据。Geospatial：支持地理空间数据存储和操作，适用于地理位置相关的应用。2. Redis 基本数据结构的底层实现Sorted Set（Zset）：Redis 的 Zset 使用 跳表（SkipList） 来实现。跳表是一种多层链表，通过增加多个索引层，提高查询和插入操作的效率。跳表与红黑树对比：跳表：使用多层链表，允许跳过多个节点，提供较高的查询效率。红黑树：是一种自平衡的二叉搜索树，支持快速查找、插入和删除操作，常用于实现有序集合，但插入和删除的性能略低于跳表。3. Redis 的高性能原因内存存储：Redis 将数据存储在内存中，避免了磁盘 I/O 操作，显著提升了数据访问速度。单线程架构：Redis 采用单线程处理所有客户端请求，避免了多线程上下文切换的开销。单线程的模型在高并发下表现优秀，因为它避免了锁竞争和上下文切换。无锁操作：Redis 内部通过单线程执行命令，避免了在并发执行时的锁竞争。4. Redis 的使用场景缓存：Redis 被广泛用作缓存系统，尤其是在 Web 应用中，用于缓存热点数据，减轻数据库压力。消息队列：Redis 的 List 和 Pub/Sub 机制非常适合实现消息队列系统，支持异步消息传递。排行榜：使用 Redis 的 Zset 存储并排序用户得分，适用于实时排行榜应用。会话管理：Redis 用于存储用户会话信息，如 session 数据，能够提供快速的存取速度。5. 为什么 Redis 读取速度快完全基于内存：Redis 是内存数据库，数据的存取完全在内存中完成，比传统的磁盘数据库要快得多。避免不必要的上下文切换：Redis 采用单线程模型，避免了多线程编程中的上下文切换和加锁问题，这样就能高效地处理并发请求。简化的操作：Redis 提供高效的底层数据结构，如哈希表、跳表等，能提供高效的查询和修改操作。6. Redis 单线程为什么会快Redis 使用单线程模型来处理所有请求，这样可以避免由于多线程上下文切换而带来的性能损失。避免 CPU 消耗和锁竞争：多线程在处理并发请求时通常需要加锁，而 Redis 的单线程模型避免了加锁带来的复杂性和性能开销。服务器开发1. 用户认证和鉴权（JWT）JWT（JSON Web Token） 是一种轻量级的认证和授权方式，通常用于分布式应用中传递信息。JWT 主要由三部分组成：头部（Header）、载荷（Payload）和签名（Signature）。头部：指定签名算法（如 HMAC SHA256 或 RSA）。载荷：包含声明（Claims），如用户 ID、权限等。签名：为了确保 JWT 的完整性，通过密钥对头部和载荷进行加密生成签名。应用场景：认证：客户端登录后，服务器生成 JWT 并返回，客户端在每次请求时附带 JWT，服务器验证其合法性，从而进行认证。授权：在 JWT 中可以包含权限信息，服务器根据这些信息进行授权。2. 从 URL 下载 10000 张图片，10 台机器并行下载思路：主线程：负责分配任务，获取图片 URL 列表。子线程：每个子线程负责下载一部分图片。使用线程池来管理这些子线程，避免线程创建和销毁的开销。并行下载：将 URL 列表分成 10 份，每台机器并行下载一部分。代码：import threading from queue import Queue import requests def download_image(url, queue): try: response = requests.get(url) # 假设文件名是 URL 的最后一部分 filename = url.split("/")[-1] with open(filename, "wb") as file: file.write(response.content) except Exception as e: print(f"Error downloading {url}: {e}") finally: queue.task_done() def main(): urls = ["http://example.com/image1.jpg", "http://example.com/image2.jpg", ...] queue = Queue() for url in urls: thread = threading.Thread(target=download_image, args=(url, queue)) thread.start() queue.join()3. 雪花算法原理雪花算法（Snowflake Algorithm）是 Twitter 提出的分布式唯一 ID 生成算法。它使用 64 位二进制表示 ID，具体结构为：1 位：符号位（通常为 0）。41 位：时间戳，表示自某个时间点（如当前时间戳）以来的毫秒数。10 位：工作机器 ID，用于区分不同机器。12 位：序列号，用于同一毫秒内生成多个 ID。优点：高效生成全局唯一的 ID。时间顺序生成 ID，适合排序和分布式系统。4. 分布式锁分布式锁 是一种用于在分布式系统中确保同一时刻只有一个客户端能够访问共享资源的机制。实现方式：基于数据库：通过数据库的行锁来实现分布式锁，但可能存在性能瓶颈。基于 Redis：利用 Redis 的 SETNX 命令实现锁的创建，设置过期时间避免死锁。Zookeeper：通过 Zookeeper 的临时节点实现分布式锁，适用于需要高可靠性的场景。5. Redis 和 MySQL 数据一致性设计Redis 和 MySQL 数据一致性问题：读写一致性：为了确保 Redis 缓存和 MySQL 数据库中的数据一致性，可以通过以下方法：缓存穿透：通过使用布隆过滤器等方法，避免频繁访问不存在的数据。缓存更新策略：每当数据在 MySQL 中更新时，及时更新 Redis 缓存，避免读取过期数据。双写策略：在写入数据时，确保同时更新 MySQL 和 Redis，保证数据一致性。方案：异步更新：通过消息队列（如 Kafka）异步更新缓存，减少数据库的负担。事件驱动：通过数据库触发器或应用逻辑触发缓存更新。6. 长连接与短连接的区别与应用场景短连接：每次请求建立一个新的连接，处理完请求后立即关闭连接。优点：实现简单，适用于负载较小、请求数量少的场景。缺点：频繁建立和销毁连接，带来较大的性能开销。长连接：连接在多个请求之间保持开放，避免了频繁的连接建立和销毁。优点：减少连接建立和销毁的开销，适用于请求频繁的场景。缺点：需要管理连接的生命周期和资源，可能导致连接池耗尽。应用场景：短连接：适用于 HTTP 请求、API 调用等。长连接：适用于 WebSocket、数据库连接池、消息队列等。7. RAII 实现数据库连接池RAII（Resource Acquisition Is Initialization）是资源管理的一种方式，利用对象的生命周期自动管理资源。对于数据库连接池，可以通过 RAII 原则自动管理连接的创建和销毁。实现步骤：封装数据库连接：创建一个数据库连接类，构造函数负责获取连接，析构函数负责关闭连接。连接池：实现一个连接池类，内部管理一定数量的数据库连接。借用连接：从连接池中获取连接，并确保使用完毕后归还连接。8. HTTP 服务器目标及实现方式目标：HTTP 服务器的主要目标是接收 HTTP 请求、解析请求内容、生成响应并返回给客户端。实现方式：监听端口：服务器监听指定端口，等待客户端的连接请求。解析请求：接收到请求后，解析 HTTP 请求头、请求体等信息。处理请求：根据请求的路径、方法等信息，选择合适的处理逻辑（如查询数据库、生成文件等）。返回响应：构造 HTTP 响应并发送给客户端。9. 负载均衡的场景问题负载均衡 是通过将请求分配到多个服务器，来提高服务的可用性和处理能力。常见的负载均衡方法包括：轮询：每次请求轮流分配到各个服务器。加权轮询：根据服务器的处理能力进行权重分配。最少连接：将请求分配给连接数最少的服务器。IP 哈希：根据客户端的 IP 地址进行分配。应用场景：负载均衡常用于高并发、大流量的应用场景，如 Web 服务、API 服务等。rpc1. gRPC怎么用的gRPC 是 Google 开发的高性能、开源和通用的远程过程调用（RPC）框架，基于 HTTP/2 协议实现，支持多种编程语言。使用 gRPC 时，可以通过以下步骤：定义服务：使用 Protocol Buffers（proto）文件定义服务接口和消息格式。生成代码：通过 gRPC 提供的工具生成客户端和服务器端的代码。实现服务：在服务器端实现服务定义的方法。客户端调用：客户端通过 gRPC 客户端库发起 RPC 请求，服务器响应。示例：service Greeter { rpc SayHello (HelloRequest) returns (HelloResponse); } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }2. 为什么 gRPC 速度快HTTP/2：gRPC 使用 HTTP/2 协议，提供了多路复用、二进制帧、头部压缩等特性，使得传输更高效。Protocol Buffers：gRPC 使用 Protocol Buffers（Protobuf）作为序列化协议，比传统的 JSON 或 XML 更紧凑、更高效。流式传输：支持双向流式传输，可以高效地处理大量数据。低延迟：通过高效的连接管理和消息压缩，减少了延迟和带宽消耗。3. RPC 调用流程和机制，RPC 超时计时器在什么位置，如何处理超时RPC 调用流程：客户端发起 RPC 调用，传递请求数据。客户端通过代理（stub）与服务器通信。服务器收到请求后，执行相应的方法并返回响应数据。客户端接收到响应并处理结果。RPC 超时处理：RPC 调用的超时通常通过客户端指定的超时参数来控制，客户端设置请求超时后，RPC 客户端会在超时后终止请求。超时计时器的位置：通常由客户端设置，当客户端发起请求时，超时计时器开始计时。如果服务器没有在规定的时间内返回结果，客户端会处理超时并可以选择重试或报告错误。超时后连接的使用：超时并不意味着当前连接不可用，连接可以继续使用，除非出现其他网络错误。