操作系统
- 进程和线程的区别:
- 进程是资源分配的最小单位,线程是CPU调度的最小单位
- 线程必须在进程中运行,一个进程可以有多个线程
- 不同进程互相不影响,进程的某个线程挂了整个进程崩溃
- 进程间通信较困难,线程间通信较容易
- 进程间切换开销大,线程间切换开销小
- 进程适合多核
- 用户态和核心态的区别
内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序
用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取 - 用户态和核心态来回切换的话为什么效率会低
因为进行切换需要保存上下文,还要切换使用用户栈或是内核栈,结束了还要恢复现场等等 - 为什么要有进程和线程
进程是对处理器的并发,线程是对进程的并发,为了更高效的利用CPU,比如有时进程会阻塞在输入,这时不依赖输入的线程可以独立执行 - 进程占用了哪些资源
虚拟地址空间
一个全局唯一的进程ID (PID)
一个可执行映像(image),也就是该进程的程序文件在内存中的表示
一个或多个线程
一个位于内核空间中的名为EPROCESS(executive process block,进程执行块)的数据结构,用以记录该进程的关键信息,包括进程的创建时间、映像文件名称等。
一个位于内核空间中的对象句柄表,用以记录和索引该进程所创建/打开的内核对象。操作系统根据该表格将用户模式下的句柄翻译为指向内核对象的指针。
一个位于描述内存目录表起始位置的基地址,简称页目录基地址(DirBase),当CPU切换到该进程/任务时,会将该地址加载到CR3寄存器,这样当前进程的虚拟地址才会被翻译为正确的物理地址。
一个位于用户空间中的进程环境块(Process Environment Block, PEB)。
一个访问权限令牌(access token),用于表示该进程的用户、安全组,以及优先级别。 - 线程间通信的方式
互斥锁:一个线程持有这个锁,其他线程不能同时持有
条件变量:阻塞某个线程,满足特定条件时再唤醒它
信号量:加强版互斥锁,增加了同时访问资源的线程数
读写锁:读时共享,写时独占 - 看CPU利用率和哪些进程应该怎么做?(top)
- 从代码到程序运行整个流程
预处理:将源文件根据预编译指令修改成.i文件
编译:将.i文件翻译成.s文件,也就是汇编程序
汇编:将汇编程序翻译成机器指令.o文件,叫做可重定向目标程序(二进制文件)
链接:将标准库函数的机器指令链接到上一步生成的文件中,得到可执行文件
加载:通过可执行文件的程序入口地址,将相应的段加载到一片虚拟内存中,最后映射到物理内存上 - 进程调度算法
先来先服务:FIFO
时间片轮转:周期性调度
最短进程优先
最短剩余时间优先
最高响应比优先:根据比率:R=(w+s)/s (R为响应比,w为等待处理的时间,s为预计的服务时间)
多级反馈队列:- 设置多个就绪队列,并为各个队列赋予不同的优先级。在优先权越高的队列中,为每个进程所规定的执行时间片就越小
- 当一个新进程进入内存后,首先放入第一队列的末尾,按照先来先去原则排队等候调度。如果他能在一个时间片中完成,便可撤离;如果未完成,就转入第二队列的末尾,同样等待调度…如此下去,当一个长作业(进程)从第一队列依次将到第n队列(最后队列)后,便按第n队列时间片轮转运行
- 仅当第一队列空闲的时候,调度程序才调度第二队列中的进程运行;仅当第1到(i-1)队列空时,才会调度第i队列中的进程运行,并执行相应的时间片轮转
- 如果处理机正在处理第i队列中某进程,又有新进程进入优先权较高的队列,则此新队列抢占正在运行的处理机,并把正在运行的进程放在第i队列的队尾
- 进程间通信的方法
管道
命名管道
消息队列
信号量:用于进程间互斥和同步
共享内存 - 分页、分段机制
分段:将内存分为大小不固定的段,程序只能访问固定段内的地址,代码中不再使用绝对地址,而是使用相对地址
分页:将地址分为大小固定的页(一般为4096字节),按页为单位进行映射。连续的线性地址可以映射到不连续的物理内存上,充分利用碎片空间 - 进程池,线程池
预先创建好一些进程/线程,由主进程/线程负责选择池中的进程/线程执行任务,减少创建和销毁的次数,提高效率 - 多线程的利弊
坏处:增加了调度和管理的开销,带来了一些不确定性,需要复杂的同步机制,避免死锁等等
好处:一定程度上提高响应速度,在多核的情况下更能充分利用CPU资源 - IO和多路复用
阻塞IO:内核准备数据和数据从内核拷贝到进程内存地址这两个过程都是阻塞的
非阻塞IO:用户进程在内核准备数据的阶段需要不断的主动询问数据好了没有
IO多路复用:用一个进程专门去处理,比如select是轮询,一般限制1024个socket,poll也是轮询,只是没有了个数限制,epoll则是当某个socket变化时通过回调告知用户进程
异步IO:完成后kernel主动告知用户进程 - 什么是Reactor模式
Accpetor注册事件,Reactor分发IO,Handler处理具体请求 - 快表
快表是一种特殊的高速缓冲存储器(Cache),内容是页表中的一部分或全部内容,通常快表处于MMU中
C++
- vector的扩容
当容量满后会扩展为原来的两倍,主要是时间和空间的权衡,平摊成本和空间都较小
C++11使用移动语义将原有的数据直接拷贝到新的空间 - 左值右值,右值引用,移动语义
能取地址的叫左值,不能的叫右值,右值引用可以将右值临时储存起来。std::move可以将左值转化成右值,节省拷贝时间 - 如何将vector清空:
直接赋值替换:清空元素,但不回收空间
clear:清空元素,但不回收空间
逐个erase:清空元素,但不回收空间
与空vector swap:清空元素并回收空间
- 虚函数
子类与父类调用相同函数时会有不同的行为,是动态多态的体现
函数重写,是子类成员函数覆盖父类的成员函数
虚函数,是将父类的成员函数定义为虚函数,方便父类向子类的向下转型 - 虚函数表指针以及虚函数表创建时机
虚函数表指针跟着对象走,所以对象创建时它才创建,因此是运行时生成
虚函数表在编译时就生成,用于确定每个类对应的虚函数,在运行时编译器将虚函数表的首地址赋值给虚函数指针 - 引用和指针的区别
引用必须初始化而且不能改变绑定对象,指针可以为空而且可以改变指向的地址
引用不需要分配内存,指针需要分配内存
引用是别名,指针是地址
引用的创建和销毁不会调用类的拷贝构造函数和析构函数 - 智能指针
shared_ptr:增加了引用计数,如果当前指针是唯一引用这块内存的指针,在它销毁时会自动释放相应的内存
weak_ptr:它的构造和析构不会引起引用计数的改变,只能用来观测。不仅可以解决循环引用的问题,还可以检测指针是否悬挂 - C++的map
unordered_map和map,前者无序,需要重载==运算符,后者有序,需要重载<运算符
unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的
map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来 - 哈希表冲突解决方法
线性探测法:往后找到第一个可用的
二次探测法:以平方的跨度找到可用的
开链法:把相同哈希值的元素储存在单链表中 - 哈希表扩容
负载因子到达0.75后就开辟原来空间的两倍大小的空间再拷贝过去 - inline作用,优缺点
让函数代码像宏一样直接被替换,效率很高,适用于短小简单的函数
缺点是需要复制替换产生额外的开销,如果函数体含有循环那么开销会比普通函数大
与宏的区别在于宏是预处理器处理的,而内联是编译器处理的,而且内联函数依然是函数,只是少了参数压栈的开销 - static的作用
作用于局部作用域:该静态变量的特点是当这个函数返回后,下一次再调用时,该变量还会保持上一回的值,函数内部的静态变量只开辟一次空间,且不会因为多次调用产生副本,也不会因为函数返回而失效(比如用于计数某个函数调用了多少次)
作用于类成员变量:解决多个对象间数据共享的问题,静态成员在每一个类中只有一个副本,由该类所有对象共同维护和使用
作用于类成员函数:将函数变为静态函数,可以不需要通过对象就进行调用 - extern "C"在C++中的作用
C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名 - 成员变量增多会影响类的sizeof吗?虚函数增多呢?
会。如果本来没有虚函数,增加虚函数后会变多(32位4字节,64位8字节),用于存放虚函数表指针 - 类的内存布局
最上方是虚函数表指针,然后是父类的成员变量,最后是子类成员变量,有多少个虚函数就有多少个虚函数表项
- 什么函数不可以为虚函数?为什么?
构造函数不能为虚,因为子类在构造时必须先调用父类的构造函数,而调用父类构造函数时子类的成员变量还未初始化,所以无法达到多态的效果 - unordered_map怎么解决哈希冲突?哈希冲突太多会导致什么?
使用开链法解决哈希冲突,当同位置上的元素结点大于8个的时候会自动转化成map - C++内存分布
- 设置固定精度的cout
cout<<setiosflags(ios::fixed)<<setprecision(2);// 需要头文件iomanip - decltype和auto的区别
把引用赋给auto时拿到的是变量的类型,而decltype拿到的是引用类型
auto忽略顶层const,而decltype保留所有const - string的默认容量 (15)
计算机网络
- TCP/IP协议
一族协议的统称,用来使不同网络下的计算机进行通信
重点记住,TCP是传输层协议,IP是网络层协议。TCP/IP模型包括应用层、传输层、网络层和链路层
- 三次握手和四次挥手
第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
为什么不能是两次:为了避免失效的请求突然又发送到了服务端,然后服务端以为建立了新的连接,浪费资源
第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。 - 丢包的话TCP如何保证可靠的传输?
经过2MSL后超时重传(MSL是数据包在网络中存活的最长时间) - 除了TCP外还有什么可靠传输的协议
FTP - TCP和UDP区别
TCP是可靠传输,UDP是不可靠传输
TCP基于流传输,UDP基于消息报传输
TCP基于连接,UDP无需建立连接
TCP是全双工可靠通道,UDP是不可靠通道
TCP首部开销20字节,UDP8字节 - 讲一下HTTP状态码500/502/504都是啥
500:内部错误
502:网关错误
504:网关超时 - TCP流量控制
通过滑动窗口进行流量控制,为了防止发送方发送数据过快,接收方来不及接受,接收方需要告知发送方一个滑动窗口的大小
通过拥塞窗口防止由于过多的报文进入网络,而造成路由器与链路过载。拥塞窗口是发送端根据网络拥塞情况确定的窗口值。发送端在真正确定发送窗口时,应该取“通知窗口”和“拥塞窗口”的最小值。四个算法:1)慢启动,2)拥塞避免,3)快速重传,4)快速恢复 - http长连接和短连接
短连接:客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接
长连接:当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接 - TCP包含什么协议,UDP包含什么协议
TCP:FTP文件传输、Telnet远程登录、SMTP邮件发送、POP3邮件接收、 HTTP超文本传输
UDP:DNS域名解析、SNMP简单网络管理 - socket的概念
一个Socket是一对IP地址和端口,用来区别不同的应用程序进程和连接
- 路由器、防火墙处于哪一层(网络层)
- DNS协议,ARP协议
DNS用来将域名解析为IP地址,ARP用来将IP地址转化成MAC地址
数据库
-
倒排索引
即根据值指向键的索引,多用于搜索引擎。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表 -
数据库常见索引结构
二叉搜索树
B-树:多路平衡搜索树
B+树
使用B-树而非二叉搜索树主要是出于减少磁盘IO次数的考虑,将树尽量“矮胖化”B树的定义:
- 根节点至少有两个孩子
- 每个中间结点有k-1个元素和k个孩子(m/2 <= k <= m,m为阶数,根据磁盘大小决定)
- 每个叶子结点有k-1个元素
- 每个结点的元素从小到大排列,正好划分了k个孩子的值域
B+树的定义:
- 有k个子树的中间结点包含k个元素,每个元素不保存数据,只作为索引,所有数据保存在叶子结点
- 所有的叶子结点包含全部的信息,及指向含这些元素信息的指针,且叶子结点本身按照关键字大小顺序连接
- 所有中间元素都存在于叶子结点的元素中,而且是该叶子结点的最大或最小元素
B+树的优点
- IO次数比B-树更少,因为中间结点不存信息
- 所有查询都会到叶子结点,查询性能稳定
- 所有叶子结点形成有序链表,方便范围查询
-
InnoDB默认采用的什么隔离级别?(可重复读)
-
什么是乐观锁悲观锁,数据库实现一个乐观锁怎么实现
乐观锁:认为数据发生冲突是小概率事件,尽可能执行,到提交才加锁。好处是不会产生死锁
悲观锁:认为数据发生冲突是大概率事件,在更新时直接对数据库进行加锁。好处是安全,但是降低了并发性,增加了死锁的机会
乐观锁的实现:通过版本号检测数据是否合法,因为版本号只增不减
悲观锁的实现:通过直接对数据库加锁来实现 -
数据完整性
实体完整性:实体完整性规定表的每一行在表中是唯一的实体。表中定义的PRIMARYKEY约束就是实体完整性的体现
域完整性:域完整性是指数据库表中的属性列必须满足某种特定的数据类型或约束。其中约束又包括取值范围、精度等规定。表中的UNIQUE、CHECK、FOREIGN KEY约束和DEFAULT、NOT NULL定义都属于域完整性的范畴
参照完整性:参照完整性是指两个表的主键和外键值的数据应对应一致。它确保了有主键的表中对应其他表的外键的数据行存在,即保证了表之间数据的一致性,防止了数据丢失或无意义的数据在数据库中扩散 -
索引的结构、概念
B+树,帮助数据库更高效的查询数据 -
主键,外键
主键:能够唯一标识一条记录的键
外键:A表的某个键是B表的主键,那么A表的这个键称为B表的外键 -
数据表设计的范式
第一范式:确保每一列不可再分割
第二范式:确保每一列都与主键相关联,如果没有关联应该放到新的表(消除部分依赖)
第三范式:确保每列都和主键列直接相关,而不是间接相关(消除传递依赖) -
ACID
数据库中事务具有的四个特征
A:原子性,语句不可再分,要么执行要么不执行
C:一致性,事务开始前和结束后,数据库完整性不被破坏
I:隔离性,事务的执行互不干扰,一个事务看不到另一个事务的中间状态
D:持久性 ,事务完成后更改永久存在 -
如何查看数据库表结构
describe 表名 或者 show columns from 表名 -
为什么使用自增主键效率高
如果主键为自增 id 的话,mysql 在写满一个数据页的时候,直接申请另一个新数据页接着写就可以了
如果主键是非自增 id,为了确保索引有序,mysql 就需要将每次插入的数据都放到合适的位置上 -
事务的四种隔离性
未提交读:没有提交就能读,会出现脏读
提交读:事务提交后才能读,可以解决脏读,但是会出现不可重复读,两次查询返回了不同数据
可重复读:开始读取后,不能再进行其他操作,解决了不可重复读的问题,可能出现幻读(后一次查询比前一次查询新增加了一条记录)
序列化:事务串行化顺序执行,效率低下 -
用什么实现了可重复读
MVCC:Multi-Version Concurrency Control,通过在每行记录后面保存两个隐藏的列来实现,这两个列,分别保存了这个行的创建时间和删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID
分布式
- 为什么用Hadoop实现负载?
主要是为了提高效率。如果不进行负载均衡,当单节点负担过重,其他节点都已经计算完毕了,而该节点还一直在计算,这样就很浪费时间 - Hadoop 配置伪分布式需要修改哪些配置文件,每个都需要配置哪些东西
文件名称 | 格式 | 描述 |
---|---|---|
hadoop-env.sh | Bash脚本 | 记录Hadoop要用的环境变量 |
core-site.xml | Hadoop配置XML | Hadoop Core的配置项,例如HDFS和MapReduce常用的I/O设置等 |
hdfs-site.xml | Hadoop配置XML | HDFS守护进程的配置项,包括NameNode、SecondaryNameNode、DataNode等 |
mapred-site.xml | Hadoop配置XML | MapReduce守护进程的配置项 |
masters | 纯文本 | 运行SecondaryNameNode的机器列表 |
slaves | 纯文本 | 运行DataNode和TaskTracker的机器列表(每行一个) |
hadoop-metrics.properties | Properties文件 | 控制metrics在Hadoop和上如何如何发布的属性 |
log4j.properties | Properties文件 | 系统日志文件、NameNode审计日志、TaskTracker子进程的任务日志的属性 |
数据结构
- 跳表
跳表全称为跳跃列表,它允许快速查询,插入和删除一个有序连续元素的数据链表。跳跃列表的平均查找和插入时间复杂度都是O(logn)。快速查询是通过维护一个多层次的链表,且每一层链表中的元素是前一层链表元素的子集(见右边的示意图)。一开始时,算法在最稀疏的层次进行搜索,直至需要查找的元素在该层两个相邻的元素中间。这时,算法将跳转到下一个层次,重复刚才的搜索,直到找到需要查找的元素为止
时间复杂度:O(logn),空间复杂度:O(n)