您的位置:首页 > 职场人生

我的面试总结

2020-02-17 10:20 344 查看

我的面试总结

基础

计算机网络

1,OSI,TCP/IP,五层协议的体系结构,以及各层协议


即应用层,传输层,网络层,数据链路层,物理层。
每一层的协议如下:
物理层:RJ45、CLOCK、IEEE802.3 (中继器,集线器,网关)
数据链路:PPP、FR、HDLC、VLAN、MAC (网桥,交换机)
网络层:IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP、 (路由器)
传输层:TCP、UDP、SPX
会话层:NFS、SQL、NETBIOS、RPC
表示层:JPEG、MPEG、ASII
应用层:FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS

简单介绍几种协议

HTTP协议: 超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统
TCP:
IP:
FTP:

TCP和UDP的区别

TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
TCP首部开销20字节;UDP的首部开销小,只有8个字节
TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

2,TCP三次握手四次挥手

三次握手:

第一次握手:客户端发送syn=1(seq=x)到服务器,并进入SYN-SEND(发送)状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN=1(seq=y),即SYN+ACK包,此时服务器进入SYN-RECV(接收)状态;

第三次握手:客户端收到服务器的确认后,向服务器发送确认包ACK=1(确认号ack=y+1),序号seq=x+1 此包发送完毕,客户端和服务器进入ESTABLISHED(已建立)状态,完成三次握手。

说明:
1)SYN和ACK是标志位(0/1)(ACK=1表明ack有效),seq是序列号,ack是确认号。2)给对方的确认方式就是把对方传来的seq+1并赋给ack。

  1. 为什么要三次握手,两次行不?
    为了防止已经失效的连接请求报文段突然又传回服务器。
    解释:当客户端发送一个连接请求报文段到服务器时,由于网络延迟等原因,在TCP连接释放后回的某个时间点才到达服务器,这本来是一个失效的报文,服务器收到此失效报文后,向客服端发送确认报文,如果是两次握手,由于是一个失效的报文,此时客服端并未向服务端发出连接请求,因此不会向服务端发送数据,而服务器却认为TCP连接已经建立,所以就一直等待客服端发送数据,这样就造成了服务器资源的浪费,而而如果采用三次握手,客服端不会向服务器发出确认,服务器由于没有收到确认消息,就知道客服端并没有要求建立连接,从而不会造成浪费资源。

四次挥手:

第一次挥手:客服端停止向服务器发送数据,并将FIN置为1,报文当前序号为seq=u。最后将其发送给服务器,客服端进入FIN-WAIT-1状态。(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。

第二次挥手:服务器收到客服端释放连接的请求后,将确认报文的ACk置1,确认号为ack=u+1,这个报文段自己的序号为seq=v(等于前面服务端已传送的最后一个字节的序号加1),然后发送到客服端,服务器进入CLOSE-WAIT状态。

第三次挥手:若服务器没有数据要发给客服端了,服务器将FIN置为1,报文段当前的序号为seq=w,同时将ACK置为1,再次发送上次给客服端的确认号ack=u+1,这是服务器进入LAST-ACK状态,等待客服端确认。

第四次挥手:客服端收到服务器连接释放的请求后,将确认报文中的ACK置为1,确认号为ack=w+1,将自己的报文序号seq=u+1写入报文,发送给服务器,然后进入TIME-WAIT状态。客服端经过2MSL时间后,进入CLOSE状态,服务器收到确认报文后,进入CLOSE状态,TCP连接释放。
说明:

1)SYN攻击 用众多伪造ip地址向服务器发送SYN=1(请求连接),让服务器处于SYN-RCVD状态,但都无法第三次握手(因为伪造ip不存在)

2)4次挥手中的FIN就相当于三次握手中的SYN。

3)序号seq,确认序号ack,确认标志位ACK作用还是一样的,就是确认作用(把seq加上1赋给ack,并把ACK置1)

4)为什么一个3次1个4次不一样?

因为两端的数据并不是同时发送完,所以两端谁发送完数据都需要自己告诉对方一次,并且对方确认一次。

ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。

问题

为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
**
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?**
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假想网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

3,拥塞控制

一般原理:发生拥塞控制的原因:资源(带宽、交换节点的缓存、处理机)的需求>可用资源。

作用:拥塞控制就是为了防止过多的数据注入到网络中,这样可以使网络中的路由器或者链路不至于过载。拥塞控制要做的都有一个前提:就是网络能够承受现有的网络负荷。

对比流量控制:拥塞控制是一个全局的过程,涉及到所有的主机、路由器、以及降低网络相关的所有因素。流量控制往往指点对点通信量的控制。是端对端的问题。

拥塞窗口:发送方为一个动态变化的窗口叫做拥塞窗口,拥塞窗口的大小取决于网络的拥塞程度。发送方让自己的发送窗口=拥塞窗口,但是发送窗口不是一直等于拥塞窗口的,在网络情况好的时候,拥塞窗口不断的增加,发送方的窗口自然也随着增加,但是接受方的接受能力有限,在发送方的窗口达到某个大小时就不在发生变化了。

发送方如何知道网络拥塞了呢?发送方发送一些报文段时,如果发送方没有在时间间隔内收到接收方的确认报文段,则就可以人为网络出现了拥塞。

慢启动算法的思路:主机开发发送数据报时,如果立即将大量的数据注入到网络中,可能会出现网络的拥塞。慢启动算法就是在主机刚开始发送数据报的时候先探测一下网络的状况,如果网络状况良好,发送方每发送一次文段都能正确的接受确认报文段。那么就从小到大的增加拥塞窗口的大小,即增加发送窗口的大小。

例子:开始发送方先设置cwnd(拥塞窗口)=1,发送第一个报文段M1,接收方接收到M1后,发送方接收到接收方的确认后,把cwnd增加到2,接着发送方发送M2、M3,发送方接收到接收方发送的确认后cwnd增加到4,慢启动算法每经过一个传输轮次(认为发送方都成功接收接收方的确认),拥塞窗口cwnd就加倍。

拥塞避免:为了防止cwnd增加过快而导致网络拥塞,所以需要设置一个慢开始门限ssthresh状态变量(我也不知道这个到底是什么,就认为他是一个拥塞控制的标识),它的用法:

1、当cwnd < ssthresh,使用慢启动算法,

2、 当cwnd > ssthresh,使用拥塞控制算法,停用慢启动算法。

3、 当cwnd = ssthresh,这两个算法都可以。

拥塞避免的思路:是让cwnd缓慢的增加而不是加倍的增长,每经历过一次往返时间就使cwnd增加1,而不是加倍,这样使cwnd缓慢的增长,比慢启动要慢的多。

无论是慢启动算法还是拥塞避免算法,只要判断网络出现拥塞,就要把慢启动开始门限(ssthresh)设置为设置为发送窗口的一半(>=2),cwnd(拥塞窗口)设置为1,然后在使用慢启动算法,这样做的目的能迅速的减少主机向网络中传输数据,使发生拥塞的路由器能够把队列中堆积的分组处理完毕。

实例:1、TCP连接进行初始化的时候,cwnd=1,ssthresh=16。

2、在慢启动算法开始时,cwnd的初始值是1,每次发送方收到一个ACK拥塞窗口就增加1,当ssthresh =cwnd时,就启动拥塞控制算法,拥塞窗口按照规律增长,

3、当cwnd=24时,网络出现超时,发送方收不到确认ACK,此时设置ssthresh=12,(二分之一cwnd),设置cwnd=1,然后开始慢启动算法,当cwnd=ssthresh=12,慢启动算法变为拥塞控制算法,cwnd按照线性的速度进行增长。

快重传:

快重传算法要求首先接收方收到一个失序的报文段后就立刻发出重复确认,而不要等待自己发送数据时才进行捎带确认。接收方成功的接受了发送方发送来的M1、M2并且分别给发送了ACK,现在接收方没有收到M3,而接收到了M4,显然接收方不能确认M4,因为M4是失序的报文段。如果根据可靠性传输原理接收方什么都不做,但是按照快速重传算法,在收到M4、M5等报文段的时候,不断重复的向发送方发送M2的ACK,如果接收方一连收到三个重复的ACK,那么发送方不必等待重传计时器到期,由于发送方尽早重传未被确认的报文段。

快恢复:

当发送方连续接收到三个确认时,就执行乘法减小算法,把慢启动开始门限(ssthresh)设置为cwnd的一半,但是接下来并不执行慢开始算法。

此时不执行慢启动算法,而是把cwnd设置为新的ssthresh值, 然后执行拥塞避免算法,使拥塞窗口缓慢增大。

操作系统(OS)

1,进程和线程的区别

进程是资源分配最小单位,线程是程序执行的最小单位;

进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;

CPU切换一个线程比切换进程花费小;

创建一个线程比进程开销小;

线程占用的资源要⽐进程少很多。

线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)

进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

2,锁

###数据结构

排序算法

1,冒泡排序
它重复的遍历要排序的元素,依次比较相邻的元素,如果他们的顺序错误就把他两交换,若没有交换,就完成排序

def bubbblesort():
arr =
20000
[3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]
for i in range(0,len(arr)-1):
#第一轮比较
flag = 0
for j in range(0,len(arr)-1-i):
#第二轮比较
if arr[j]>arr[j+1]:
arr[j+ 1], arr[j] = arr[j], arr[j+1]
flag = 1
if flag == 0:
break
return arr
def main():
print(bubbblesort())

if __name__ == '__main__':
main()

时间复杂度O(n^2),稳定的

2,快速排序(重点)
通过一趟排序,将要排序的数据分割成两个部分,其中一部分的所有数据都比另一部分小,然后在用这个方法对这两部分分别快排。

左右指针法
1,选取一个关键字(temp)作为枢轴,一般取整组记录的第一个数/最后一个,这里采用选取序列最后一个数为枢轴。
2,设置两个变量i = 0;j = N - 1;
3,从i 一直向后走,直到找到一个大于temp的值,j 从后至前,直至找到一个小于temp的值,然后交换这两个数。
4,重复第三步,一直往后找,直到i 和j 相遇,这时将temp 放置i 的位置即可。

def quicksort(arr,l,r):
i = l
j = r

if l<r:
temp = arr[l]

while i!=j:
while j>i and arr[j] > temp:
j -=1
if i<j:
arr[i] = arr[j]
i +=1
while j>i and arr[i] < temp:
i +=1
if i<j:
arr[j] = arr[i]
j -=1
arr[i] = temp
quicksort(arr,l,i-1)
quicksort(arr,i+1,r)

def main():
arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]
quicksort(arr,0,len(arr)-1)
print(arr)

if __name__ == '__main__':
main()

时间复杂度为O(nlogn), 不稳定

3,简单选择排序

采用选择机制,从头到尾扫描数列,找出最小的记录和第一个交换,然后从剩下的序列中继续这种选择和交换。

def selectsort(nums):
for i in range(len(nums)):
for j in range(i+1,len(nums)):
temp = nums[i]
if nums[j]<nums[i]:
nums[j],nums[i] = nums[i],nums[j]
return nums

def main():
arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]
selectsort(arr)
print(arr)

if __name__ == '__main__':
main()

时间复杂度O(n^2),不稳定

4,直接插入排序
每趟将一个待排序的元素作为关键字,按照其关键字的大小插入到已经排好的部分序列的适当位置。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

def insertsort(nums):
for i in range(1,len(nums)):
temp = nums[i]
j =i-1
while temp < nums[j] and j>=0:
nums[j+1] = nums[j]
j-=1
nums[j+1]=temp

def main():
arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]
insertsort(arr)
print(arr)

if __name__ == '__main__':
main()

时间复杂度O(n^2),稳定的,算法适用于少量数据的排序

5,还有希尔排序(O(nlogn)),堆排序(O(nlogn)),归并排序(O(nlogn)),折半排序(O(n^2))
不稳定的算法有:快排,希尔排序,简单选择排序,堆排序,其他都是稳定的。
平均情况下:快排,希尔排序,归并,,堆排序时间复杂度为O(nlogn),其他的都是O(n^2)

树结构

机器学习

1,梯度消失和梯度爆炸,如何解决

梯度爆炸:梯度爆炸就是由于初始化权值过大,前面层会比后面层变化的更快,就会导致权值越来越大,梯度爆炸的现象就发生了。

梯度消失:因为通常神经网络所用的激活函数是sigmoid函数,这个函数有个特点,就是能将负无穷到正无穷的数映射到0和1之间,并且对这个函数求导的结果是f′(x)=f(x)(1−f(x))。因此两个0到1之间的数相乘,得到的结果就会变得很小了。神经网络的反向传播是逐层对函数偏导相乘,因此当神经网络层数非常深的时候,最后一层产生的偏差就因为乘了很多的小于1的数而越来越小,最终就会变为0,从而导致层数比较浅的权重没有更新,这就是梯度消失。

解决办法:
1,用ReLU激活函数来替代sigmoid函数
2,预训练加微调
3,梯度剪切、正则 (爆炸)

2, 过拟合和欠拟合及其解决办法

欠拟合:模型在训练集上学习的不够好,经验误差大,称为欠拟合

解决办法:
增加训练次数。
添加其他特征项,例如,组合特征、泛化特征、相关性特征。
添加多项式特征,例如将线性模型通过添加二次项或者三次项使模型泛化能力更强。
减少正则化参数,正则化的目的是防止过拟合

过拟合:当模型对训练集学习得太好的时候(学习数据集通性的时候,也学习了数据集上的特性,导致模型在新数据集上表现差,也就是泛化能力差),此时表现为经验误差很小,但泛化误差很大,这种情况称为过拟合。
解决办法:
正则化
正则化方法包括L0正则、L1正则和L2正则。
L0范数是指向量中非0的元素的个数。L1范数是指向量中各个元素绝对值之和,也叫“稀疏规则算子”(Lasso regularization)。两者都可以实现稀疏性。
L2范数是指向量各元素的平方和然后求平方根。可以使得W的每个元素都很小,都接近于0,但与L1范数不同,它不会让它等于0,而是接近于0。L2正则项起到使得参数w变小加剧的效果。
剪枝
剪枝是决策树中一种控制过拟合的方法,预剪枝通过在训练过程中控制树深、叶子节点数、叶子节点中样本的个数等来控制树的复杂度。后剪枝则是在训练好树模型之后,采用交叉验证的方式进行剪枝以找到最优的树模型。
提前终止迭代
主要是用在神经网络中的,在神经网络的训练过程中我们会初始化一组较小的权值参数,此时模型的拟合能力较弱,通过迭代训练来提高模型的拟合能力,随着迭代次数的增大,部分的权值也会不断的增大。如果我们提前终止迭代可以有效的控制权值参数的大小,从而降低模型的复杂度。
Dropout
是深度学习中最常用的控制过拟合的方法,主要用在全连接层处。在一定的概率上(通常设置为0.5,原因是此时随机生成的网络结构最多)隐式的去除网络中的神经元,但会导致网络的训练速度慢2、3倍,而且数据小的时候,Dropout的效果并不会太好。因此只会在大型网络上使用。

注:
L1正则化是指权值向量www中各个元素的绝对值之和,通常表示为∣∣w∣∣1

L2正则化是指权值向量www中各个元素的平方和然后再求平方根(可以看到Ridge回归的L2正则化项有平方符号),通常表示为∣∣w∣∣2

​L1正则化有助于生成一个稀疏权值矩阵,进而可以用于特征选择

L2正则化防止过拟合

python

内存管理机制

一,引用计数
每个对象都有引用计数,对对象的引用会使得引用计数加一,移除对象的引用就会减一

二,垃圾回收
1)引用计数

引用计数也是一种垃圾收集机制,而且是一种最直观、最简单的垃圾收集技术。党某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要回收的垃圾,被回收掉。但是有一个例外,循环引用是对象之间的相互引用,会使得一组对象的引用计数不为0,然后这些对象实际上没有被任何外部对象所引用,这些对象就会占内存永远不会被释放掉。
因此Python又引入了其他的垃圾回收机制来弥补引用计数的缺陷:“标记-清理”,“分代回收”。
2)标记清理

对于循环引用,当两个对象相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含对其他对象的引用,因此引用计数不会归零,对象也不会销毁;从而导致内存泄漏。为了解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问的对象的循环并删除它们。

在实际操作中并不改动真实的引用计数,而是将集合中对象的引用计数复制一份副本,改动该对象引用的副本。(对于副本做任何的改动,都不会影响到对象生命周期的维护。)这个计数副本的唯一作用是寻找root object集合(该集合中的对象是不能被回收的)。当成功寻找到root object集合之后,首先将现在的内存链表一分为二,一条链表中维护root object集合,成为root链表,而另外一条链表中维护剩下的对象,成为unreachable链表。之所以要剖成两个链表,是基于这样的一种考虑:现在的unreachable可能存在被root链表中的对象直接或间接引用的对象,这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。
(3)分代回收

从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额外操作。
三,内存池机制
Python提供了对内存的垃圾收集机制,但是它是将不用的内存放到内存池,而不是返回给系统。

python中内存机制呈现出金字塔形状,-1、-2层主要由操作系统进行操作。第0层是c中的malloc,free等内存分配和释放函数进行操作。

(1)第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现。Python 中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。不过,通过修改Python源代码,我们可以改变这个默认值,从而改变Python的默认内存管理行为。

(2)Python引入了一个内存池机制,是为了加速Python的执行效率,用于管理对小块内存的申请和释放。

(3)对于python对象,如整数、浮点数、List,都有其独立的私有内存池,对象间不共享它们的内存池。也就是说,如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。(值语义)

是c中的malloc,free等内存分配和释放函数进行操作。

(1)第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现。Python 中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。不过,通过修改Python源代码,我们可以改变这个默认值,从而改变Python的默认内存管理行为。

(2)Python引入了一个内存池机制,是为了加速Python的执行效率,用于管理对小块内存的申请和释放。

(3)对于python对象,如整数、浮点数、List,都有其独立的私有内存池,对象间不共享它们的内存池。也就是说,如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。(值语义)

  • 点赞 1
  • 收藏
  • 分享
  • 文章举报
鑫鑫点灯007 发布了1 篇原创文章 · 获赞 1 · 访问量 11 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: