1. 数据结构算法梳理
两个栈实现一个队列,两个队列模拟一个栈,栈的特性(递归转成非递归用到栈),队列的特性(处理哈夫曼编码用非递归和树的广度遍历可以用队列),链表,数组,线性表,排序算法,哈希表,二叉树(BST树,AVL树,红黑树),五大算法
2. 二叉树相关面试梳理
(1)BST树的第k大节点,BST树的结构和特性 BST树的结构和特性:基于二叉树本身的特征还要满足当前节点大于左子树的所有节点的值、小于右子树的所有节点的值,这颗二叉树就可以成为BST树,BST树和平衡没关系。
中序遍历是从小到大排序的,所以求第K大节点就是求中序遍历的第k个节点。
(2)求BST树的倒数第k大节点,求最近公共祖先节点,判断是否是子树,判断BST树是否是一颗平衡树
3. vector底层数据结构,为什么是二倍扩容?
vector底层的数据结构是二倍扩容的数组。
vector有两个方法:compacity返回的是它底层数据结构的容量,size返回它真正元素的个数。我们可以默认构造一个vector,然后循环去添加值,每次循环打印size和compacity,就能看出来往vector容器放元素的时候它的扩容是怎么进行的。在windows下的vs2017,2019上用vector会发现它是1.5倍扩容的,当在Linux下用gcc,g++去编译会发现它是2倍扩容的。
首先为什么是倍数扩容而不是固定扩容,这里涉及了一个均摊时间以倍数扩容效率高(比如如果vector以固定数量扩容一次扩容10个,当vector里面元素放的比较多不够放的话,一次扩容10个肯定不好,均摊时间是O(n)),倍数扩容均摊时间是O(1)。
二倍扩容0、1、2、4、8、16、32、64...,1.5倍扩容0、1、2、3、4、6、9...。为什么说1.5倍扩容好?通过malloc开辟内存,malloc是用户空间的C函数,malloc底层使用的是glibc里面的内存管理ptmalloc,glibc也是工作在用户空间,实际上内存是在内核上进行管理的,比如说第一次调用malloc申请4B时,用户空间根本就没有,需要向内核申请4B内存,向内核申请都是以页面为单位的,把申请的这个页面中的4B分配出去,剩下的就被分成不同的块管理在ptmalloc上管理起来,在内核上分配内存都是用brk指针
(分配小块内存,<128k)和mmap
(分配大块内存,>128k)。对于小块内存刚开始brk分配堆内存通过指针偏移来分配,大内存使用mmap()系统调用函数来在虚拟地址空间中找一块空间来开辟,以二倍扩容当我们要扩容新申请内存的时候,原来已经被释放的内存永远也不会被复用起来,因为以二倍扩容的话,新申请的内存比前面以释放内存的总和还要大;而1.5倍的好处就是新申请内存的时候,可以把前面已经释放的内存复用起来,利于内存复用。所以这就是为什么1.5倍是各个云里面对于顺序容器采用的扩容倍数最多的扩容方式。
为什么不是3或者4倍?
如果倍数超过2倍(包含2倍)方式扩容会存在:
- 空间浪费可能会比较高,比如:扩容后申请了64个空间,但只存了33个元素,有接近一半的空间没有使用。
- 无法使用到前面已释放的内存。
4. 红黑树和哈希表
map,multimap,set,multiset底层是红黑树,unordered_map,unordered_set底层是哈希表。
红黑树的增删查都是对数时间,哈希表的增删查是常量O(1)。哈希表是无序的,红黑树是有序的,对k会进行排序。所以只要是我们应用的场景对于数据的增删查效率都有所要求而对数据的有序性没有要求的话,一般选用哈希表;如果对于有序性有要求的话,选用红黑树。
会从容器问道底层数据结构,从红黑树又涉及到和AVL的对比区别、红黑树的特点、红黑树增加删除如何操作、左旋右旋涂色如何更改,哈希表的哈希冲突、链式哈希表、常用的哈希函数有哪些,解决查重问题经常一块思考哈希表、位图法、布隆过滤器。
5. gcc是什么工具,编译链接原理
gnu
是一种开源的计划,包含了很多有用的工具,也包含了linux。
gcc
是一个编译器,最早只是编译C语言,最后发展成了工具链,可以编译C、C++、Java等可以编译很多语言。
g++
采用C++的语法规则编译源代码。
gdb
是调试。启动调试r,调试静态程序,调试动态程序,调试coredump文件(coredump是堆转储文件,记录了程序挂掉内存的详细信息以及各个线程函数调用堆栈信息,可以帮助我们定位程序为什么挂掉)。
6. linux下的进程状态,如何看进程状态。
(1)进程状态
R状态:有两个状态就绪和运行,不管是正在运行的程序还是可以被调度的程序都属于R状态。
S状态:睡眠状态,是可中断的。
D状态:不可被中断的状态,只有通过IO事件唤醒才可以,经常用于IO事件。
T状态:调试程序,程序进入stop状态。
Z状态:僵尸态。进程结束了,但是内核资源没有被wait回收,占用内核资源,导致当前系统所能创建的进程数量变少。
(2)查看进程状态
ps aux
查看当前系统的进程列表,有好多进程信息,State就是它的状态(Ss:后面的s表示这个进程包含子进程;S<:<表示它是高优先级)。cd /proc/
进入proc文件系统,proc文件系统可以让我们在shell上查看一些内核相关的东西。每一个当前运行的系统进程以pid号命名的文件夹,可以cd pid号
进入进程目录,cat status
可以查看State。
- 查看某个tcp服务的时候用
netstat -tanp
进程通信基于信号量、共享内存、消息队列都是我们本地进行进程间通信常用的。分布在不同机器的进程间如何通信?
线程通信是共享地址空间的,通信可以通过直接共享数据段和堆来进行数据共享,互斥有读写锁、互斥锁、自旋锁、CAS,条件变量、信号量可以作为线程间通信。
7. HTTP数据报组成,如何解析HTTP数据报
(1)http和https
https就是http+ssl,可以了解加密认证过程,签名,CA认证,对称加密和非对称加密。
http本身是一个应用层协议,其实每一层的协议都是给需要传输的数据增加了一定的约定俗成的东西,比如TCP给应用层数据加了TCP报头,包含了源端口、目的端口、序号、确认号、ACK、当前数据每一个字节的序号、校验码等等,传输层往上再到IP层,IP协议会再给TCP层的数据再加一个IP头,一次性在物理层能够传输的数据的字节最大是1460B,应用层只管传输数据,因为有当前TCP、IP的协议栈会帮我们去分包到对端会帮我们组包。
(2)http数据包的组成
打开浏览器F12可以查看
http请求报文头
http应答报文头
根据数据格式进行解析就行。
8. 传输层采用TCP协议,在应用层读取数据时,如何判断是否读完,如果没读完,如何判断还剩多少?
考TCP的粘包问题。
-
从解决TCP粘包问题回答 TCP是流式协议,会在缓冲区满了以后发送,所以我们经常在连续发送数据的时候,如果是我们业务上自己做的一发一应答这个没问题,但如果我们连续发几次,对端可能只收一次就收完了,那么怎么在业务上去区分一次的数据是否读完?加数据头,不管是用结构体发送数据,还是用我们线程的数据组装协议,我们都会给数据加一个数据头。
-
从类似传输文件业务角度回答
传输文件为了支持秒传,秒传就是每一次传输文件都会用文件内容计算md5值,把md5值和filesize发送到服务端,服务端记录了所有服务端已保存文件的md5值,如果匹配到的话,就说明这个文件已经在服务端存在,对客户端直接显示传输完成,如果是一新文件,就表示服务端会返回ok已准备好可以传输,而且服务端也记录了这个文件的大小,在服务端接收的时候,就可以根据最开始协商的filesize来不断检测已经读取多少还剩下多少,可以用这些信息用进度条显示。