头条面试总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 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 = 磁盘块的大小/数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。
      • 索引的最左匹配特性(即从左往右匹配)
  • 网络通信
    • 画出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的重传机制。
      • 等待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,并释放分配给该套接字的资源
          • 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
  • 操作系统
    • 多进程和多线程的区别
    • 为什么区分内核态和用户态
      • 用户态
        进程执行自己的代码时
      • 内核态
        发生系统调用时,进程执行内核的代码
      • 原因
        • 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。
        • 代码示例
          在这里插入图片描述
  • 设计题
    • 设计火车票订票系统,场景是对于固定的某一天,输入起始站和终点站,返回给用户车次信息和每个席位的剩余票数

猜你喜欢

转载自blog.csdn.net/caoxiaohong1005/article/details/82989194