操作系统
阻塞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 file
- ps aux
- curl命令 访问一个网页
- df查看磁盘大小
- du查看目录大小
- free -h 查看内存大小和使用情况
- top查看系统的实时负载
- netstat -ta 查看监听的TCP
- stat 获取文件属性
- 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
- 同步非阻塞IO
- IO多路复用
- 信号驱动IO
- 异步IO
select与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,在子进程中返回0
exec族
在程序中调用另一个可执行程序,但是进程ID不改变。
网络编程IO
服务器通常需要处理三类事件:IO事件,信号及定时事件。
事件处理模式:reactor和proactor
同步IO模型通常用于实现reactor模式
异步IO则用于实现proactor模式
什么是reactor模式
它要求主线程只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程。读写数据,接受新的连接以及处理客户请求均在工作线程中完成。
什么是proactor模式
它将所有IO操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。
mmap存储映射
将磁盘空间映射到进程空间,使进程可以采用指针的方式操作这段内存,而不用调用read和write函数。提高了读写的效率,同时也可以实现进程间的通信。
异步IO原理
底层将数据准备好后,内核会给进程发送一个异步通知信号SIGIO29通知进程,然后进程调用信号处理函数去读数据,没准备好,数据就忙自己的事情。
select poll epoll
- select单个进程打开的文件描述符有上限,为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 trace
get和post的区别
都是http的请求方式。
区别在于:
- 用途上:get一般用于获取资源,post一般用于创建资源。
- 位置上:get请求的的数据会在地址栏上显示出来,以问号分割url与传输数据,多个参数用取地址符连接。而post的数据放在请求体中。
- 安全性上:优于get将信息显示在地址栏,所以对于用户密码等个人隐私信息很不安全,而post放在请求体中,在安全性上要稍微好点。
- 长度限制:get使用地址栏发送数据,而地址栏的长度是有限的。
- 幂等性上:get操作没有副作用,多次操作产生的副作用相同,所以get是幂等的,而post用于创建资源是会又副作用的,所以post不是幂等的。
cookie和session
cookie和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 ARP
- NAT用于实现从内部IP地址到外部IP地址的映射
- DNS提供域名到IP地址的映射或者反过来
- ARP提供IP到MAC地址的映射
DNS
基于UDP的协议
- 递归查询:父域名代替当前服务器递归查询,最后依次返回
- 迭代查询:父域名服务器告诉当前服务器下一次查询的位置
http状态码
- 100 continue 等待继续发送
- 200 ok 请求成功
- 206 patial content 部分资源
- 301 永久重定向
- 302 临时重定向 307
- 400 客户端请求报文语法错误
- 403 禁止访问
- 404 资源不存在
- 408 请求超时
- 500 服务器内部错误
- 503 服务器不可用
http 2.0 SPDY
- 二进制分帧
- 多路复用
- 首部压缩
- 服务器推送
http 3.0 QUIC+UDP
- 0 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库》陈硕
static
static的使用可以分为两类,一类是用在普通变量和函数上,另一类是用在类中。
- 普通变量分为全局变量和局部变量。声明为静态全局变量是在全局区分配内存,并且只在当前文件可见,在文件之外是不可见的。其他文件定义同名变量不会发生冲突。变量的值只在第一次执行时进行初始化。声明为静态局部变量时与全局变量类似,只是作用域为局部作用域。
- 静态普通函数,只在当前文件中可见,其他文件中定义同名函数不会发生冲突。
- static用在类中,首先是静态成员变量,在类中声明,类外初始化。所有对象共享一份数据。
- 然后是静态成员函数:所有对象共享同一个函数,静态成员函数只能访问静态成员变量。
多态
多态分类两大类:静态多态和动态多态。静态多态是重载和模板。动态多态:也叫运行时多态,是通过继承和虚函数实现的。在具有继承关系的子类中,子类重写父类的虚函数,通过父类引用或指针指向子类对象时,产生不同的行为叫做多态。
多态的核心在于虚函数表指针,每个对象都有一个虚函数表指针,虚函数表指针指向一张虚函数表,表中记录了虚函数的入口地址,如果子类重写虚函数后,这个地址就会替换掉。多态的好处在于更方便程序的扩展,坏处在于每个对象多了一个4字节的指针,同时每次查询虚函数表需要耗时。
智能指针
C++11有3个智能指针,分别是unique_ptr, shared_ptr和weak_ptr
- unique_ptr独享指针的所有权,无法进行拷贝构造赋值的操作,只能通过move函数进行所有权的转换。
- shared_ptr共享对象,它使用引用计数来保存当前有多少个智能指针在引用这个对象,当引用计数降为0时,对象会被销毁。
- weak_ptr称为弱引用,用于辅助shared_ptr正常工作,主要解决shared_ptr可能会产生的环形引用问题。weak_ptr不会增加对象的引用计数,共享指针可以直接赋值给弱指针,同时弱指针可以使用lock函数来获取shared_ptr对象
malloc和new
malloc和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修饰成员函数时,函数中的成员变量不可改变,除非该变量特别声明为mutable
- const可以用来修饰指针,称为常量指针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 使具有继承关系的基类转换为派生类,如果不可以转换则返回NULL
- reinterpret_cast 指针或引用的转换,风险较高
RTTI
run 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和innoDB
myisam不支持事务,不支持外键,支持表锁。查询速度较快。适合读多更新少的表。
innoDB支持事务,支持外键,支持行锁,因为锁的粒度比较小,所以适合高并发。
持久化
利用永久性存储介质将数据进行保存,在特定的时间将保存的数据进行恢复的机制叫做持久化。确保数据安全。
- 将当前数据状态进行保存,快照形式。RDB
- 将数据的操作过程进行保存,日志形式。AOF
基础知识
volatile
用来告诉编译器不要对该变量做任何优化,编译器每次操作该变量时,一定要从内存中取出,而不是使用寄存器中与存在的值,因为值可能已经发生了改变。
应用场景:
- 并行设备的硬件寄存器(如状态寄存器)。
- 多线程中共享变量。
const
用于限定变量,函数和指针不可改变。
- const限定全局变量时,变量放在静态区。const限定局部变量时,变量放在栈区。
- const修饰类中的成员函数时,表示不可对类的对象更改,如果需要更改类中的成员变量,可以使用mutable关键字。
内联函数和宏定义
- 宏定义在预编译时展开,内联函数在编译时展开。
- 宏定义只是简单的文本替换,不进行参数的有效性检查。内联函数会进行类型检查,然后将函数体嵌入到目标代码中。
- 宏定义不加括号容易出错。内联函数没有普通函数的栈操作,效率很高,但是不正确的时候内联函数会导致代码体积过大。
static
静态全局变量,静态局部变量,静态成员变量,静态成员函数
- 静态全局变量和静态局部变量都放在静态区,只在声明时初始化一次,仅在本文件中可见,变量在程序运行期间一直存在。静态局部变量的作用域范围仅在局部,下次调用紧接着上次调用的结果值改变。
- 静态成员函数和静态成员变量没有this指针,必须通过类名才能访问。
malloc 和 new
- malloc和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_ptr
shared_ptr
weak_ptr
四种强制类型转换
const_cast去掉const属性的转换,包括const指针和引用
static_cast用于低风险的转换,比如字符型转整形
dynamic_cast 用于具有继承关系的派生类和子类的转换,当基类转为子类时,如果转换失败会返回NULL
reinterpret_cast 任何类型都能转,风险较高
post和put的区别
post用来创建资源,put用来更新资源
post是非幂等的,put是幂等的。
8中请求方法:
http 1.0: get post head
http 1.1: put delete options connect trace
mmu内存管理器
主要作用:虚拟内存到物理内存的地址映射。 设置修改内存访问级别。
虚拟内存的作用
- 解决主存容量有限
- 分隔进程,保证进程空间彼此独立不受干扰
- 基于局部性原理进行页面替换
虚拟内存的大小由计算机的地址总线决定
cache名字和TLB命中没有必然联系,是两种独立的机制。
CPU和Cache之间交换的单位是字节,Cache和内存之间交换的单位是块。
coredump文件
gdb可以用于分析coredump文件。coredump文件含有进程被终止时内存/CPU寄存器和各种函数调用栈的信息。
产生coredump文件的原因:
- 内存访问越界
- 多线程使用了线程不安全的函数
- 多线程读写的数据未加锁保护
- 栈溢出
core文件没有符号表信息,必须结合可执行文件才可调试
模板特化
全特化:模板参数被指定未确定的类型
偏特化:模板参数没有被全部确定,需要编译器在编译时进行确定。只能偏特化类模板,不能偏特化函数模板。
别名模板和变量模板属于语法糖
元编程
在编译时计算出运行时需要的常数,类型和代码的方法。
右值引用
右值引用指向要被销毁的对象。右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
move函数将左值转换为右值,调用move函数后源对象只能赋值或销毁。
override
override在子类中标记某个函数,表示想要覆盖已有的虚函数,如果没有覆盖,编译器会报错。
加作用域运算符调用特定类的虚函数
内联函数的优劣
- 优点:减少函数调用的开销,包括寄存器值的保存和实参的拷贝等。
- 缺点:增加函数体积,可能导致cache装不下,从而减少了cache的命中率。
inline只是一个请求,编译器有权拒绝。
拷贝构造函数
调用场景:
- 一个对象以值传递传参
- 一个对象以值传递的方式从函数返回
- 一个对象通过另一个对象初始化
空类
占有一个字节
有构造,析构,拷贝,赋值运算符,取地址运算符。
构造函数可以被重载,析构函数不可以被重载且不能带参数。
explicit
explicit取消隐式转换,类中构造函数默认是implicit
explicit关键字的作用是防止类构造哈桑农户的隐式自动转换,只对有一个参数的构造函数有效。
堆和栈的区别
- 申请方式不同。栈由操作系统自动分配,堆需要程序员自己申请。
- 生长方向不同。栈由高地址向地址生长,是一块连续的内存区域。堆由地址向高地址生长,是不连续的内存区域。在一个链表中记录空间内存地址。
- 分配速度。栈由系统分配,速度较快。堆使用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修饰成员函数时,函数中的成员变量不可更改,如果要修改成员变量需要声明为mutable
- const修饰指针有两种,常量指针和指针常量,常量指针是指针的指向的值不可改变,而指针的指向可以改变。指针常量是指向不可变,而值可变。
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的默认443
- https需要验证服务器端的身份,如果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 head
http1.1增加了五种请求方法:put delete connect trace options
http1.1增加了许多状态码,比如100 continue 表示已经收到,等待后续的资源。206 partial content 部分资源
http1.1支持一个物理主机上可以有多个虚拟主机共用一个IP
评论已关闭