字节跳动 后端研发实习生 面试题目总结(转载)
面试内容经由网络搜索,汇总得到,在面试过程中很多问题都是被问到了的
时间原因,一部分查了答案,一部分没有
-
TCP握手过程 为什么是4次
三次握手建立连接,客户端发送syn包到服务器,服务器收到syn包,确认客户的syn包并且自己也发送一个syn包,客户端收到后确认服务器的syn包,连接建立。
超时重传:超时重传机制用来保证TCP传输的可靠性。每次发送数据包时,发送的数据报都有seq号,接收端收到数据后,会回复ack进行确认,表示某一seq 号数据已经收到。发送方在发送了某个seq包后,等待一段时间,如果没有收到对应的ack回复,就会认为报文丢失,会重传这个数据包。
快速重传:接受数据一方发现有数据包丢掉了。就会发送ack报文告诉发送端重传丢失的报文。如果发送端连续收到标号相同的ack包,则会触发客户端的快速重 传。比较超时重传和快速重传,可以发现超时重传是发送端在傻等超时,然后触发重传;而快速重传则是接收端主动告诉发送端数据没收到,然后触发发送端重传
流量控制:接收端告诉发送端自己还有多少缓冲区可以接受数据(只考虑接收端和发送端)
拥塞控制:慢启动、拥塞避免、拥塞发生、快速恢复(考虑整个网络的状况)
四次挥手断开连接:第一次:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当 然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但此时主动关闭方还可以接受数据。第二次:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。第三次:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。第四次:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
SYN标志建立一个新连接,FIN标志释一个连接,RST重置连接
三次握手用于防止“已失效的连接请求报文段”,报文段没有丢失,而是在某个节点长时间滞留。
四次挥手:由于连接是全双工的。所以每个方向都必须单独进行关闭
为什么是三次握手,四次挥手:因为在握手的时候服务端在listen状态,收到建立连接的syn报文后,将ack和syn放在一个报文里发送给对方,而关闭连接时,收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,
再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
-
设计一个可以满足高效率获取第k大和前k个大的元素的数据结构
大顶堆
-
哈希表 再哈希
哈希表hashtable(key,value) 就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
再哈希的方法:
拉链法:数组+链表
开放地址法:将所有的元素都存在哈希表中
再散列法:再使用哈希函数去散列一个输入的时候,输出是同一个位置就再次散列,直至不发生冲突位置
-
设计线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
生产者消费者模式
-
解释内存中的堆和栈,栈溢出
内存中的栈由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈,先进后出。它是由高地址向低地址扩展的数据机构,是一段连续的内存区域,只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
内存中的堆一般用来动态分配内存,实现和操作系统和编译器有关。
内存中的堆:队列优先,先进先出。向高地址扩展的数据结构,是不连续的内存区域,在操作系统中,一般是由程序员动态分配释放的,分配方式:操作系统有一个专门存储空闲地址的链表,当程序申请分配空间时,OS会遍历这个链表(遍历方向:低地址向高地址),找到第一个大于申请的空间的堆节点,并从空闲节点列表中删除该节点,把空间分配给程序,若找到的空间比申请的空间要大,系统会自动把多余的那部分重新放入空闲链表中。一般来说,操作系统会在内存的首地址处记录分配的空间大小,以便程序能够正确地释放该内存空间。堆的大小取决于计算机有效的虚拟内存。
虚拟内存:利用部分的硬盘空间充当内存使用。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
调度方式有分页式、段式、段页式3种。页式调度是将逻辑和物理地址空间都分成固定大小的页。主存按页顺序编号,而每个独立编址的程序空间有自己的页号顺序,通过调度辅存中程序的各页可以离散装入主存中不同的页面位置,并可据表一一对应检索。页式调度的优点是页内零头小,页表对程序员来说是透明的,地址变换快,调入操作简单;缺点是各页不是程序的独立模块,不便于实现程序和数据的保护。段式调度是按程序的逻辑结构划分地址空间,段的长度是随意的,并且允许伸长,它的优点是消除了内存零头,易于实现存储保护,便于程序动态装配;缺点是调入操作复杂。将这两种方法结合起来便构成段页式调度。在段页式调度中把物理空间分成页,程序按模块分段,每个段再分成与物理空间页同样小的页面。段页式调度综合了段式和页式的优点。其缺点是增加了硬件成本,软件也较复杂。大型通用计算机系统多数采用段页式调度。
-
快速排序
-
读写锁
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。
读写锁适合于对数据结构的读次数比写次数多得多的情况.
信号量PV****操作
**互斥锁 **信号量为1
进程同步的方法:
临界区、互斥量、信号量、事件
乐观锁和悲观锁:
悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,
-
怎么给大量url和ip去重
将多个文件合并 cat a b > c
去重排序sort test.log | uniq
-
浏览器输入url到响应的过程
DNS解析,查找到相应的IP地址,应用层客户端发送HTTP请求,决定是UDP还是HTTP传输,,数据进入网路层通过OSPF或者是BGP查找下一跳主机,找到下一跳主机之后使用ARP协议解析得到MAC地址,进入数据链路层,数据传输,服务器接收数据,服务器响应数据,页面渲染,DOM树
-
多进程 和 多线程
同一进程的不同线程会共享内存空间中的全局区和堆,私有的线程空间包括栈和寄存器。
多进程:每个进程一个pid,进程是程序在计算机上一次执行活动
创建子进程 fork() 子进程pid为0,子进程先执行,父进程再执行,返回两次,fork后,子进程会赋值父进程的task_struct结构,并为子进程的对栈分配物理页。理论上来说,子进程应该完整地复制父进程的堆,栈以及数据空间,但是2者共享正文段。
多线程:线程是可执行代码的可分派单元。
线程安全:概念比较直观。一般说来,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。主要考虑线程间的共享变量,全局变量、静态变量。
可重入:可重入一定是线程安全的
1、不在函数内部使用静态或全局数据
2、不返回静态或全局数据,所有数据都由函数的调用者提供。
3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
4、不调用不可重入函数。
IPC****进程间通信
管道:具有亲缘关系之间的进程间的通信,有固定的读端和写端 pipe 速度慢,容量有限,只有父子进程能通讯
FIFO****命名管道:FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。任何进程间都能通讯,但速度慢
信号量:计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。需要与共享内存联系起来。不能传递复杂消息,只能用来同步
消息队列:可以实现消息的随机查询。容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
共享内存(最快):能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
-
缓存的运行过程 如何保证cache一致性
缓存只是内存中少部分数据的复制品,所以CPU到缓存中寻找数据时,也会出现找不到的情况(因为这些数据没有从内存复制到缓存中去),这时CPU还是会到内存中去找数据,这样系统的速率就慢下来了,不过CPU会把这些数据复制到缓存中去,以便下一次不要再到内存中去取。
缓存映射:直接映射缓存,组相连缓存,全相连缓存
cache****一致性:用于保证多个CPU cache之间缓存共享数据的一致。MESI协议为了保证多个CPU cache中共享数据的一致性,定义了cache line的四种状态,而CPU对cache的4种操作可能会产生不一致状态,因此cache控制器监听到本地操作和远程操作的时候,需要对地址一致的cache line状态做出一定的修改,从而保证数据在多个cache之间流转的一致性。
cache的4种状态:
modify:表示当前CPU中拥有最新数据,虽然主存中的数据和当前CPU中的数据不一致,但是以当前CPU中的数据为准
exclusive:表示当前CPU独占数据(其他CPU没有数据),并且和主存的数据一致
shared:表示当前CPU和其他CPU共享数据,且数据在多个CPU 之间一致、多个CPU之间的数据和主存一致
invalid:表示当前CPU中是脏数据,不可用,其他CPU可能有数据、也可能没有数据
引起数据状态转化的操作:
去本地cache的数据,将数据写到本地cache,读取主存中的数据,将数据写到主存中
-
怎么等一个线程做完
join()方法
-
解释下线程和协程区别,协程库中yield干嘛用的
Python中的yield吗?
用于让当前进程释放其占用的CPU资源,以便让其他进程有机会执行
-
怎么创建新进程,内核做了啥?这个回答了下fork, vfork等,然后具体do_fork过程,copy on write等
-
求一些数的全排列
-
SQL语句(包含count,group by)
selelct x.sno,x.score,y.score from studtent x,sc y where xxx and xxx
GROUP BY必须得配合聚合函数来用,分组之后你可以计数(COUNT),求和(SUM),求平均数(AVG)等。
select sno,avg(score) from sc group by sno having avg(score)>60
select sc.sno,sname,count(cno),sum(score) from student join sc on student.sno=sc.sno group by sc.sno,sname
select count(tname) from teacher where tname like “无%”
select sno,sname from student where sno not in(select sno from SC where cno in(select cno from coursewhere tno in(select tno from teacherwhere tname=\’悟空\’)))
order by asc(升序) desc(降序)
-
MySQL中事务隔离级别
事务的基本要求ACID
原子性(要么做,要么不做)、一致性、隔离性(同一时间只允许一个事务请求同一数据,不同事务之间没干扰),持久性
事务的并发问题:
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
mysql****隔离级别:
读未提交:这种隔离级别下,事务间完全不隔离,会产生脏读,可以读取未提交的记录,实际情况下不会使用。
不可重复读(读提交):本事务读取到的是最新的数据(其他事务提交后的)。问题是,在同一个事务里,前后两次相同的SELECT会读到不同的结果(不重复读)
可重复读:在同一个事务里,SELECT的结果是事务开始时时间点的状态,因此,同一个事务同样的SELECT操作读到的结果会是一致的。但是,会有幻读现象
串行化:读操作会隐式获取共享锁,可以保证不同事务间的互斥
-
TCP/UDP
-
有状态连接和无状态连接
-
进程之间的通信方式
-
TCP报文中的字段
-
操作系统内核态与用户态
用户态—>内核态:唯一途径是通过中断、异常、陷入机制(访管指令)
内核态—>用户态:设置程序状态字PSW
特权指令:只能由操作系统使用、用户程序不能使用的指令。 举例:启动I/O 内存清零 修改程序状态字 设置时钟 允许/禁止终端 停机
非特权指令:用户程序可以使用的指令。 举例:控制转移 算数运算 取数指令 访管指令(使用户程序从用户态陷入内核态)
处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的 ;
而处于核心态执行中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
-
HTTP和TCP的关系
TCP是传输层,而http是应用层今天学习了下,知道了 http是要基于TCP连接基础上的,简单的说,TCP就是单纯建立连接,不涉及任何我们需要请求的实际数据,简单的传输。http是用来收发数据,即实际应用上来的。
-
进程间通信和线程间通信的区别
进程在unix上,使用的是管道和信号灯来传递信息。
线程通过共享的全局数据去来进行通讯就可以了。
-
乐观锁和悲观锁
-
滑动窗口
-
TCP server最多可以建立多少个TCP连接
-
TCP流量控制和拥塞控制
-
PYTHON 可变类型和不可变类型
Python的每个对象都分为可变和不可变,主要的核心类型中,数字、字符串、元组是不可变的,列表、字典是可变的。
对不可变类型的变量重新赋值,实际上是重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象(如果没有其他变量引用原有对象的话(即引用计数为0),原有对象就会被回收)。
-
linux查看内存
sudo atop
ps
-
socket编程
-
一个进程,有十个线程,其中一个线程fork后,子进程有几个线程
在fork多线程的进程时,创建的子进程只包含一个线程,该线程是调用fork函数的那个线程的副本
-
链表翻转
-
TCP序列号的作用
保证传输的顺序,你要传输的所有数据的每一个字节都要编号。这个序号称为字节序号
-
建堆的时间复杂度
-
为什么要对网络分层
-
协议如何封装 报文头部字段
-
快速排序和时间复杂度
-
二叉树的层次遍历
-
int 1234变成4321如何实现
-
数据库ACID代表什么
-
数据库分页如何实现
-
哈希表由哪些数据结构实现
-
get****和post
最直接的区别,GET请求的参数是放在URL里的,POST请求参数是放在请求body里的;GET请求的URL传参有长度限制,而POST请求没有长度限制;GET请求的参数只能是ASCII码,所以中文需要URL编码,而POST请求传参没有这个限制;
GET和POST本质上两者没有任何区别。他们都是HTTP协议中的请求方法。底层实现都是基于TCP/IP协议。
GET 向服务器获取指定资源 POST 向服务器提交数据,数据放在请求体里
-
tcp接收窗口和拥塞窗口
-
什么时候会向对端传窗口的大小 TCP协议
-
连续发送两次http请求,会得到两次结果吗?可能第二次比第一次快吗?
HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。 IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠地传递数据包,使得网络上接收端收到发送端所发出的所有包,并且顺序与发送顺序一致。TCP协议是可靠的、面向连接的。
HTTP协议是无状态的,指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系。HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)。
短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
-
服务器状态502 503 504什么问题,怎么排查
502 网关错误
504 网关超时
503 服务器目前无法使用(由于超载或停机维护)
查看日志,使用ulimit查看系统打开文件限制,负载均衡
-
linuxIO模型,区别在哪
-
线程独立拥有哪些资源
-
nginx怎么处理请求
-
http缓存机制
-
长连接
-
Redis
-
interrupt和signal的区别
-
操作系统在interrupt中发挥什么作用
-
重传ack的时机只有ack超时吗?
慢启动:最初的TCP在连接建立成功后会向网络中发送大量的数据包,这样很容易导致网络中路由器缓存空间耗尽,从而发生拥塞。因此新建立的连接不能够一开始就大量发送数据包,而只能根据网络情况逐步增加每次发送的数据量,以避免上述现象的发生。具体来说,当新建连接时,cwnd初始化为1个最大报文段(MSS)大小,发送端开始按照拥塞窗口大小发送数据,每当有一个报文段被确认,cwnd就增加1个MSS大小。这样cwnd的值就随着网络往返时间(Round Trip Time,RTT)呈指数级增长,事实上,慢启动的速度一点也不慢,只是它的起点比较低一点而已。
拥塞避免:TCP使用了一个叫慢启动门限(ssthresh)的变量,当cwnd超过该值后,慢启动过程结束,进入拥塞避免阶段。对于大多数TCP实现来说,ssthresh的值是65536(同样以字节计算)。拥塞避免的主要思想是加法增大,也就是cwnd的值不再指数级往上升,开始加法增加。
快速重传:那就是收到3个相同的ACK。TCP在收到乱序到达包时就会立即发送ACK,TCP利用3个相同的ACK来判定数据包的丢失,此时进行快速重传,快速重传做的事情有:1.把ssthresh设置为cwnd的一半
2.把cwnd再设置为ssthresh的值(具体实现有些为ssthresh+3)
3.重新进入拥塞避免阶段。
-
拥塞窗口要不要把自己的大小发给接收方,意义何在?(这个问题一面也问了,没有答出来)
-
git merge与rebase的区别
如果你想要一个干净的,没有merge commit的线性历史树,那么你应该选择git rebase
如果你想保留完整的历史记录,并且想要避免重写commit history的风险,你应该选择使用git merge,产生merge commit
-
STL内存分配
-
http2的特点
-
多路复用和长连接什么意思,怎么设置长连接
-
进程状态都有哪些,怎么转换
-
调度算法都有哪些,调度的时机是什么
算法:先来先服务,短作业优先,高优先权优先(响应时间/要求服务时间),时间片轮转,多级反馈队列
调度时机:进程执行完毕,阻塞,自己的时间片用完,发生中断
-
http的长连接和短连接
-
路由器是哪一层的,有什么功能,路由寻址,路由表存了什么
-
ping协议会发生什么(ICMP)用于在IP主机、路由器之间传递控制消息
(1) A主机构建一个ICMP格式的数据包;
(2) ICMP协议+B主机的IP地址 交给IP协议;
(3) IP层构建一个数据包(A主机的IP地址+控制信息+B主机的IP地址),获得B主机的MAC地址,以便构建一个数据帧;(IP协议会根据B主机的IP地址和自己的子网掩码判断是不是属于同一层网络。如果是属于同一层网络的话,就会获得B主机的MAC地址)
-
主机B接受到主机A的发过来的数据帧以后,先检查该帧中包含的B的IP地址,并和本地的物理地址进行比对,如果符合的话,就接受,否则,就抛弃。同样,需要将该数据帧交由自己的IP层协议,IP层检查以后,再交由ICMP协议,构建一个ICMP的应答包,发送给主机A。
-
进程线程 fork
-
死锁(死锁条件、避免死锁、死锁检测、死锁预防)
-
dns是TCP还是UDP
DNS占用53号端口,同时使用TCP和UDP协议。
辅域名服务器向主域名服务器查询数据是否有变动,如果有变动进行数据同步,使用TCP;服务器向DNS服务器查询域名是用UDP
-
timewait
-
线程池
-
虚拟内存怎么用
-
中文数字转化为int类型
-
有m个长度为n的有序数组,把所有数据有序打印
-
二叉树遍历,快速排序
-
cookie和session的区别
-
全排序