版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/caoxiaohong1005/article/details/82989194
头条面试问题整理
- 自我介绍
- 项目详细介绍
- 算法题
- LeetCode上一个题,给定一个二维数组和目标值,该二维数组每一行和每一列都是非递减的,问二维数组中的等于目标值的坐标。
- 实现一个排序树,能插入,能删除,能平衡
- 输入一个数组,要求输出数组中每个数字后面第一个比他大的数字,没有比他大的输出-1,时间复杂度O(n)。输入:5,1,9,6,7 输出:9,9,-1,7,-1
- 二维数组中,只能向右和向下,找到从左上角到右下角路径中和最大的值
- 堆排序
- 快排思想
- 两个栈实现队列
- 单链表排序
- 数据库
- MySQL的索引原理、数据库索引类型
- 索引原理
- 本质
通过不断地缩小想要获取数据的范围来 筛选出 最终想要的结果,同时把随机的事件变成顺序的事件。 - 从数据结构角度考虑,为什么使用B树而不用AVL树or红黑树?
- 定义:AVL树是严格的二叉排序树(保持平衡),红黑树是非严格的二叉排序树(不用保存平衡)
- 显然,AVL树和红黑树都比B树的高度h高,因为B树的节点度d更大(B树高度O(logdN))
- I/O次数越多:一个节点大小占用OS中的一个逻辑页,对应了一次I/O,因此树越高一次查找次数(即为树高)越多。
- 无法运用程序局部性原理:以为树太高导致逻辑上很近的节点(父子节点)物理上可能很远。
- 因此,B树的I/O次数明显低于AVL树和红黑树,故选择B树作为索引结构。
- 从计算机组成原理角度考虑,大多数数据库系统为什么使用B+树(对B+树结构做了优化,添加了叶子节点的顺序指针)而不使用B-树?
- 利用磁盘预读原理(根据局部性原理),DB的设计者将1个节点大小设置为1页,故1个节点只需1个I/O就能完全载入。
- B树一次查找最多查找次数=h-1(高度为h,根节点常驻内存)
- 树高度h=O(logdN)。实际应用中,出读d是非常大的数字,通常超过100,因此h非常小。
- B-树高度>B+树高度
因为d(B+)>d(B),d的计算公式为:d=节点大小(1页,4或8kB)/(keysize+datasize+pointsize)。而B+树的非叶子节点没有data域,故datasize值为0,故其d更大。 - 优化后的B+树能干嘛?
提高区间访问的性能。
eg:如果要查询key为从18到49的所有数据记录,则从第一个叶子节点开始查找,当找到key=18后,只需顺着结点和指针顺序遍历就可以一次性访问到所有数据结点,极大提到了区间查询效率。
- 补充常识
目前大部分数据库系统和文件系统都采用B-树或者B+树作为索引结构。
- 本质
- 索引原理
- 类型
- 普通索引index :加速查找
- 唯一索引
- 主键索引:primary key :加速查找+约束(不为空且唯一)
- 唯一索引:unique:加速查找+约束 (唯一)
- 联合索引
- primary key(id,name):联合主键索引
- unique(id,name):联合唯一索引
- index(id,name):联合普通索引
- 全文索引fulltext :用于搜索很长一篇文章的时候,效果最好。
db表message_queue_history,包括字段- 创建语法
alter table message_queue_history add fulltext index idx_message (message); - 查找语法
select * from message_queue_history where match(message) against(‘10086’);
- 创建语法
- 空间索引spatial :了解就好,几乎不用
- B+树
- 索引字段要尽量的小
- 因为IO次数取决于b+树的高度h ,而索引字段影响了树的高度
- 如何影响?
假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而 m = 磁盘块的大小/数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。
- 索引的最左匹配特性(即从左往右匹配)
- 索引字段要尽量的小
- MySQL的索引原理、数据库索引类型
- 网络通信
- 画出tcp断开连接的图
- 过程
- Client端收到App的关闭信号后,发送FIN给Server端。此后Client处于Fin_Wait1状态。
- Sever端收到来自Client端的Fin信号后,发送Ack信号给Client端,告诉App可以关闭连接了。此后Server进入Close_Wait状态。
- Client端收到来自Server端的Ack信号。此后Client进入Fin_Wait2状态,等待Server发送Fin信号。
- Server端的App准备关闭连接时,发送Fin信号给Client端。此后Server进入Last_Ack状态。
- Client端收到来自Server端的Fin信号后,发送Ack信号给Server端,并等待2msl时长再关闭本地内核连接。此后Client进入Time_Wait状态。
- 图[画图]
- 过程
- tcp四次挥手 和 状态 以及 为什么等待2msl,如果服务器一直没收到一直发断开连接请求的话,它会一直重置这个时间吗
- 套接字状态
- Client端依次为:Fin_Wait1、Fin_Wait2、Time_Wait
- Server端依次为:Close_Wait、Last_Ack
- 状态说明
- Fin_Wait1
- 连接处于半段开状态(可以接受、应答数据,当不能发送数据),并将连接的控制权托管给 Kernel,程序就不再进行处理。
- 一般情况下,连接处理 FIN_WAIT1 的状态只是持续很短的一段时间
- Fin_Wait2
- 在等待对方的 FIN 数据报
- 当 TCP 一直保持这个状态时,对方就有可能永远都不断开连接,导致该连接一直保持着。
- 不属于任何应用的孤儿连接保持Fin_Wait2状态的最长时间默认为:60s。超过这个时间,就会被本地直接关闭,不会进入Time_Wait状态。
- 处于 Fin_Wait2 状态的TCP连接,威胁要比 Fin_Wait1 的小,占用的资源也很小,通常不会有什么问题。
- Time_Wait
- Client端发出Ack响应后,内核会设定一个时间长度为 2MSL 的定时器,当定时器在到时间点后,内核就会将该连接关闭。
- 当连接尚未关闭的时候,又收到了对方发送过来的 FIN 请求(可能是我们发送出去的请求对方并未收到),或者收到 ICMP 请求(比如 ACK 数据报,在网络传输中出现了错误),该连接就会重新发送 ACK 请求,并重置定时器。
- Close_Wait
- 该连接可能有数据需要发送,或者一些其他事情要做
- 当这类连接过多的时候,就会导致网络性能下降,耗尽连接数,无法建立新的连接。
- Last_Ack
- 等待对方进行发送 ACK 数据报
- 收到了响应的ACK数据报后,连接进入CLOSED 状态,并释放相关资源。
- 如果超时未收到响应,就触发了TCP的重传机制。
- Fin_Wait1
- 等待2msl的原因
保证Server端能够收到Client端发送的最后一个Ack信号。而Server端在收到最后一个Ack信号后才能关闭连接。
- 套接字状态
- 进程间通信方式 & 线程间通信方式
- 进程间通信方式
- 分类
管道、消息队列、信号量、共享内存、Socket、Streams等。其中Socket、Streams支持不同主机上的两个进程通信。 - 分别介绍
- 管道
- 无名管道(默认)
- 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
- 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
- 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中
- 通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道
- 有名管道
- 名字:FIFO
- FIFO可以在无关的进程之间交换数据
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中
- 在数据读出时,FIFO管道中同时清除数据,并且“先进先出”
- 无名管道(默认)
- 消息队列
- 存放在内核中
- 一个消息队列由一个标识符(即队列ID)来标识
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
- 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取
- 信号量
- 它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据
- 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存
- 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作
- 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数
- 支持信号量组
- 共享内存
- 是最快的一种 IPC,因为进程是直接对内存进行存取
- 因为多个进程可以同时操作,所以需要进行同步
- 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
- Socket
- 参考博客:https://blog.csdn.net/Hearthougan/article/details/51275765
- 通信流程
- 图片
- 说明
- 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
- 服务器为socket绑定ip地址和端口号
- 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
- 客户端创建socket
- 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
- 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
- 客户端连接成功,向服务器发送连接状态信息
- 服务器accept方法返回,连接成功
- 客户端向socket写入信息
- 服务器读取信息
- 客户端关闭
- 服务器端关闭
- 图片
- 流程解释
- 创建一个套接字—socket()
socket()函数用于根据指定的地址族、数据类型、协议来分配一个套接字的描述字及其所用的资源 - 指定本地地址—bind()函数
将本地地址与一套接字捆绑。当socket()创建套接字 后,它便存在于一个名字空间(地址族)中,bind()函数通过给一个未命名的套接字分配一个本地名字,来为套接字建立本地捆绑(主机地址) - 监听和请求连接—listen()、connect()函数
- 建立套接字连接—accept()函数
调用accept()函数来接收请求,这样连接便建立好了,之后就可以网络I/O操作了,即类同于普通文件的读写I/O操作 - 数据传输—send()与recv()
客户端and服务器应用程序都用send函数来向TCP连接的另一端发送数据
ps:send函数仅仅是把buf中的数据copy到sockfd的发送缓冲区的剩余空间里,而不是sockfd的发送缓冲中的数据传送到连接的另一端,那是协议干的活 - 关闭套接字—close()
关闭套接字s,并释放分配给该套接字的资源
- 创建一个套接字—socket()
- Streams
- 管道
- 分类
- 线程间通信方式
- 同步:如synchronized关键字
- 轮询:如CAS机制
- wait/notify机制
- 管道通信(共享内存)
- 进程间通信方式
- 网络分层
- 网络层的功能
控制子网的运行。如分组传输、路由选择等。 - 传输层协议
tcp、udp - Tcp和Udp区别
- 是否面向连接
- 是否可靠
- 面向字节流or报文
- 是否运行多对多
- Tcp只能一对一建立连接,传输数据。
- Udp允许一对一,一对多,多对一和多对多的交互通信。
- 首部开销大小
Tcp为20B,Udp为8B。
- Tcp长连接和短连接
- 长链接
- 定义
指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接,一般需要自己做在线维持 - 过程
连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接(一个TCP连接多个读写通信) - 优势
- 提高传输速度
- 降低连续建立连接的资源消耗
- 适用对象
操作频繁(读写),点对点的通讯,而且连接数不能太多情况
- 定义
- 短连接
- 定义
短连接是指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接。 - 过程
连接→数据传输→关闭连接 - 优势
管理简单,存在的连接都是有用的连接,不需要额外的控制手段 - 适用对象
WEB网站的http服务一般都用短链接.
ps:http1.0 -----只支持短连接;
http1.1 -----keep alive 带时间,操作次数限制的长连接
- 定义
- 长链接
- socket的理解,底层,如何做一个server
- 画出tcp断开连接的图
- 操作系统
- 多进程和多线程的区别
- 为什么区分内核态和用户态
- 用户态
进程执行自己的代码时 - 内核态
发生系统调用时,进程执行内核的代码 - 原因
- CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃
- CPU将指令分为特权指令和非特权指令.
- 危险的指令,只允许操作系统及其相关模块使用
- 普通的应用程序只能使用那些不会造成灾难的指令
- 用户态
- 1亿个数,100个线程,每个线程随机从1亿个数里面取一个数做处理,要求保证不会有两个线程对同一个数做处理。我脑子里都是java里的各种锁,他说不要考虑java,想想操作系统里的互斥锁
- 进程间的通信方式,管道通信如何实现,命名管道呢,共享内存呢
- 如何实现一个进程,说说对僵尸进程的理解,孤儿进程的理解,能手写一个fork进程吗
- 孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。 - 僵尸进程
进程终止后进入僵死状态(zombie),等待告知父进程自己终止,后才能完全消失.但是如果一个进程已经终止了,但是其父进程还没有获取其状态,那么子进程就称之为僵尸进程。 - 僵尸进程的危害
如果父进程不调用wait/waitpid,那么保留退出子进程的那段信息就不会释放+进程号会一直被占用。因此如果有大量僵尸进程的出现将导致OS不能产生新进程。 - 僵尸进程的解决办法
- 通过信号机制
子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号 - fork两次
原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程
- 通过信号机制
- 手动创建fork进程
- 函数原型
1)include // 必须引入头文件,使用fork函数的时候,必须包含这个头文件,否则,系统找不到fork函数
2)pid_t fork(void); //void代表没有任何形式参数 - fork函数不需要任何参数,对于返回值有三种情况
- 对于父进程,fork函数返回新建子进程的pid
- 对于子进程,fork函数返回 0;
- 如果出错, fork 函数返回 -1。
- 代码示例
- 函数原型
- 孤儿进程
- 设计题
- 设计火车票订票系统,场景是对于固定的某一天,输入起始站和终点站,返回给用户车次信息和每个席位的剩余票数