搜狐面经答案

tech2024-01-12  74

搜狐面经

数组和链表的区别:

(1)逻辑角度来说:

数组必须事先定义固定的长度,不能适应数据动态增减的情况,当数据增加时候,可能超过预定义空间,数据较减少的时候,会造成空间浪费

链表:动态的仅从存储分配,可以适应数据增减的情况,且可以方便的插入删除数据项

(2)内存上来说,数组的地址空间是连续的,链表的地址空间是不连续的

(3)数组访问的时间复杂度是O(1),链表定位元素的时间复杂度O(n)

         数组插入和删除的时间复杂度是O(n),链表的时间复杂度是O(1)

1.指针和引用的区别:

指针:

指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内他可以先后指向几个不同的对象。指针不需要自定义的时候赋值,和其他内置类型一样,如果没有被初始化,则拥有一个不确定的值

引用:

引用必须被初始化,初始化之后不能改变其指向

sizeof引用得到的是所指向的变量的大小,而sizeof指针得到的指针本身的大小

2.野指针:

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的.

3.如果浏览器输入请求,服务器是怎么处理的;

总的来说划分为以下几个过程:

DNS 解析:将域名解析成 IP 地址TCP 连接:TCP 三次握手发送 HTTP 请求服务器处理请求并返回 HTTP 报文浏览器解析渲染页面断开连接:TCP 四次挥手

 

一、DNS解析

先在本机的浏览器的自身缓存中查询,如果没命中就去操作系统缓存中有没有解析过的结果, windows下边有一个 hosts文件去查询,但是这种很容易被劫持,通过修改hosts文件解析到别人制定的IP上,就是所谓的域名劫持,(解决方法:将hosts文件设置成readonly,防止被恶意篡改)如果此时没有命中的话如果至此还没有命中域名,才会真正的请求本地域名服务器(LDNS)来解析这个域名,这台服务器一般在你的城市的某个角落,距离你不会很远,并且这台服务器的性能都很好,一般都会缓存域名解析结果,大约80%的域名解析到这里就完成了如果还没有命中,就去根域名服务器即系,根域名服务器反应根名称服务器收到DNS请求后,把所查询得到的所请求的DNS域名中顶级域名所对应的顶级名称服务器地址返回给本地名称服务器本地名称服务器根据根名称服务器所返回的顶级名称服务器地址,向对应的顶级名称服务器发送与前面一样的DNS域名查询请求...

然后本地名称服务器继续按照前面介绍的方法一次次地向三级、四级名称服务器查询,直到最终的对应域名所在区域的权威名称服务器返回到最终的记录给本地名称服务器。然后再由本地名称服务器返回给DNS客户,同时本地名称服务器会缓存本次查询得到的记录项。

 

 

 

二、TCP三次握手的过程

TCP 三次握手的过程如下:

客户端发送一个带 SYN=1,Seq=X 的数据包到服务器端口(第一次握手,由浏览器发起,告诉服务器我要发送请求了)服务器发回一个带 SYN=1, ACK=X+1, Seq=Y 的响应包以示传达确认信息(第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧)客户端再回传一个带 ACK=Y+1, Seq=Z 的数据包,代表“握手结束”(第三次握手,由浏览器发送,告诉服务器,我马上就发了,准备接受吧)

2. 为啥需要三次握手

谢希仁著《计算机网络》中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。

 

三、发送HTTP请求:

1. 请求行包含请求方法、URL、协议版本

请求方法包含 8 种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE。URL 即请求地址,由 <协议>://<主机>:<端口>/<路径>?<参数> 组成协议版本即 http 版本号 POST /chapter17/user.html HTTP/1.1

以上代码中“POST”代表请求方法,“/chapter17/user.html”表示 URL,“HTTP/1.1”代表协议和协议的版本。现在比较流行的是 Http1.1 版本

2. 请求头包含请求的附加信息,由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。

请求头部通知服务器有关于客户端请求的信息。它包含许多有关的客户端环境和请求正文的有用信息。其中比如:

Host,表示主机名,虚拟主机;

Connection,HTTP/1.1 增加的,使用 keepalive,即持久连接,一个连接可以发多个请求;

User-Agent,请求发出者,兼容性以及定制化需求。

3. 请求体,可以承载多个请求参数的数据,包含回车符、换行符和请求数据,并不是所有请求都具有请求数据。

name=tom&password=1234&realName=tomson

上面代码,承载着 name、password、realName 三个请求参数。

四、服务器处理请求并返回 HTTP 报文

 http 响应报文

响应报文由响应行(request line)、响应头部(header)、响应主体三个部分组成。如下图所示:

(1) 响应行包含:协议版本,状态码,状态码描述

状态码规则如下:

1xx:指示信息–表示请求已接收,继续处理。2xx:成功–表示请求已被成功接收、理解、接受。3xx:重定向–要完成请求必须进行更进一步的操作。4xx:客户端错误–请求有语法错误或请求无法实现。5xx:服务器端错误–服务器未能实现合法的请求。

(2) 响应头部包含响应报文的附加信息,由 名/值 对组成

(3) 响应主体包含回车符、换行符和响应返回数据,并不是所有响应报文都有响应数据

 

五、浏览器解析渲染页面

浏览器拿到响应文本 HTML 后,接下来介绍下浏览器渲染机制

浏览器解析渲染页面分为一下五个步骤:

根据 HTML 解析出 DOM 树根据 CSS 解析生成 CSS 规则树结合 DOM 树和 CSS 规则树,生成渲染树根据渲染树计算每一个节点的信息根据计算好的信息绘制页面

1. 根据 HTML 解析 DOM 树

根据 HTML 的内容,将标签按照结构解析成为 DOM 树,DOM 树解析的过程是一个深度优先遍历。即先构建当前节点的所有子节点,再构建下一个兄弟节点。在读取 HTML 文档,构建 DOM 树的过程中,若遇到 script 标签,则 DOM 树的构建会暂停,直至脚本执行完毕。

2. 根据 CSS 解析生成 CSS 规则树

解析 CSS 规则树时 js 执行将暂停,直至 CSS 规则树就绪。浏览器在 CSS 规则树生成之前不会进行渲染。

3. 结合 DOM 树和 CSS 规则树,生成渲染树

DOM 树和 CSS 规则树全部准备好了以后,浏览器才会开始构建渲染树。精简 CSS 并可以加快 CSS 规则树的构建,从而加快页面相应速度。

4. 根据渲染树计算每一个节点的信息(布局)

布局:通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸回流:在布局完成后,发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。

5. 根据计算好的信息绘制页面

绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。重绘:某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的重绘。回流:某个元素的尺寸发生了变化,则需重新计算渲染树,重新渲染。  

六、断开连接:

当数据传送完毕,需要断开 tcp 连接,此时发起 tcp 四次挥手。

发起方向被动方发送报文,Fin、Ack、Seq,表示已经没有数据传输了。并进入 FIN_WAIT_1 状态。(第一次挥手:由浏览器发起的,发送给服务器,我请求报文发送完了,你准备关闭吧)被动方发送报文,Ack、Seq,表示同意关闭请求。此时主机发起方进入 FIN_WAIT_2 状态。(第二次挥手:由服务器发起的,告诉浏览器,我请求报文接受完了,我准备关闭了,你也准备吧)被动方向发起方发送报文段,Fin、Ack、Seq,请求关闭连接。并进入 LAST_ACK 状态。(第三次挥手:由服务器发起,告诉浏览器,我响应报文发送完了,你准备关闭吧)发起方向被动方发送报文段,Ack、Seq。然后进入等待 TIME_WAIT 状态。被动方收到发起方的报文段以后关闭连接。发起方等待一定时间未收到回复,则正常关闭。(第四次挥手:由浏览器发起,告诉服务器,我响应报文接受完了,我准备关闭了,你也准备吧)

4.同步IO、模拟preactor

linux系统中,所有的设备读写都可以看做文件的读写来操作,对文件的读写一般要经过内核态和用户态的切换,正因为有切换才导致了IO有同步和异步的说法。

通常来讲IO可以分成两种:

来自网络的IO来自文件或者设备的IO

同步IO:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程

异步IO:进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞;进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程

 

Reactor模式是处理并发I/O比较常见的一种模式,用于同步I/O,中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有I/O事件到来或是准备就绪(文件描述符或socket可读、写),多路复用器返回并将事先注册的相应I/O事件分发到对应的处理器中(epoll)

Reactor是一种事件驱动机制,和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。用“好莱坞原则”来形容Reactor再合适不过了:不要打电话给我们,我们会打电话通知你。

       Reactor模式与Observer模式在某些方面极为相似:当一个主体发生改变时,所有依属体都得到通知。不过,观察者模式与单个事件源关联,而反应器模式则与多个事件源关联

 

应用场景:

长途客车在路途上,有人上车有人下车,但是乘客总是希望能够在客车上得到休息。 传统做法:     每隔一段时间(或每一个站),司机或售票员对每一个乘客询问是否下车。 Reactor做法:汽车是乘客访问的主体(Reactor),乘客上车后,到售票员(acceptor)处登记,之后乘客便可以休息睡觉去了,当到达乘客所要到达的目的地时(指定的事件发生,乘客到了下车地点),售票员将其唤醒即可。

 

Reactor模式:

      某一瞬间,服务器共有10万个并发连接,此时,一次IO复用接口的调用返回了100个活跃的连接等待处理。先根据这100个连接找出其对应的对象,这并不难,epoll的返回连接数据结构里就有这样的指针可以用。接着,循环的处理每一个连接,找出这个对象此刻的上下文状态,再使用read、write这样的网络IO获取此次的操作内容,结合上下文状态查询此时应当选择哪个业务方法处理,调用相应方法完成操作后,若请求结束,则删除对象及其上下文。

       这样,我们就陷入了面向过程编程方法之中了,在面向应用、快速响应为王的移动互联网时代,这样做早晚得把自己玩死。我们的主程序需要关注各种不同类型的请求,在不同状态下,对于不同的请求命令选择不同的业务处理方法。这会导致随着请求类型的增加,请求状态的增加,请求命令的增加,主程序复杂度快速膨胀,导致维护越来越困难,苦逼的程序员再也不敢轻易接新需求、重构

 

Proacotr模型:

       在Reactor模式中,事件分离者等待某个事件或者可应用或个操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分离器就把这个事件传给事先注册的处理器(事件处理函数或者回调函数),由后者来做实际的读写操作。        在Proactor模式中,事件处理者(或者代由事件分离者发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区,读的数据大小,或者用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分离者得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。

 

5.线程池中请求队列的实现以及为什么用线程池

线程池的设计

美团技术团队

 

阻塞队列和非阻塞队列的区别:

阻塞队列和普通队列的区别在于:

当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。

试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列

 

非阻塞队列的执行并不会被阻塞,无论是消费者的出队,还是生产者的入队。

 

为什么用线程池?

1.  降低资源损耗,使用线程池可以避免频繁的创建线程和销毁线程,线程池中线程可以重复使用。

2.  提高响应速度。当请求到达时,由于线程池中的线程已经创建好了,使用线程池,可以省去线程创建的这段时间

3. 提高线程的可管理性。线程是稀缺资源,当创建过多的线程时,会造成系统性能的下降,而使用线程池,可以对线程进行统一分配、调优和监控

 

线程池的使用流程:

先判断线程池中线程的数量是否超过核心线程数,如果没有超过核心线程数,就创建新的线程去执行任务;如果超过了核心线程数,就进入到下面流程。2. 判断任务队列是否已经满了,如果没有满,就将任务添加到任务队列中;如果已经满了,就进入到下面的流程。3. 再判断如果创建一个线程后,线程数是否会超过最大线程数,如果不会超过最大线程数,就创建一个新的线程来执行任务;如果会,则进入到下面的流程。4. 执行饱和处理机制。

 

任务缓冲:

任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

 

 

6.如果发送一个post请求,会怎么样

 

7.怎么维护用户的登陆状态,session,cookie

 

8.多线程的作用是什么,如果不用的话呢

为了解决负载均衡问题,充分利用CPU资源.为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而不互相干扰.为了处理大量的IO操作时或处理的情况需要花费大量的时间等等,比如:读写文件,视频图像的采集,处理,显示,保存等

如果不用多线程呢?效率低?程序串行执行

9.高并发的瓶颈在哪里

 

10.非阻塞socket

阻塞和非阻塞的概念:

什么是阻塞socket,什么是非阻塞socket。对于这个问题,我们要先弄清什么是阻塞/非阻塞。阻塞与非阻塞是对一个文件描述符指定的文件或设备的两种工作方式。 

阻塞的意思是指,当试图对该文件描述符进行读写时,如果当时没有东西可读或者暂时不可写,程序就进入等待状态,直到有东西可读或者可写为止。 

非阻塞的意思是,当没有东西可读或者不可写时,读写函数就马上返回,而不会等待

 

哪些socket api会阻塞:

accept,connect,recv(recvfrom),send(sendto),closesocket,select(poll或epoll)

1)accept在阻塞模式下,没有新连接时,线程会进入睡眠状态;非阻塞模式下,没有新连接时,立即返回WOULDBLOCK错误。

2)connect在阻塞模式下,仅TCP连接建立成功或出错时才返回,分几种具体的情况,这里不再叙述;非阻塞模式下,该函数会立即返回INPROCESS错误(需用select检测该连接是否建立成功)

3)recv/recvfrom/send/sendto很好理解,因为这两类函数读写socket文件描述符的接收/发送缓冲区。 

4) select/poll/epoll并不是真正意义上的阻塞,它们的阻塞是由于它们最后一个timeout参数决定的,timeout大于0时,它们会一直等待直到超时才退出(相等于阻塞了吧,^_^),而timeout=-1即永远等待

 

 

11.多线程处理IO和epollIO复用的区别是什么

 

 

12.redis呢,单线程epoll实现的

 

 

 

13.vector,map,set的底层实现

vector:动态增加大小时,并不是在原空间之后持续新空间(因为根本无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间,因此,一旦引起空间重新配置,指向原vector的所有迭代器都失效了,这是程序员易犯的一个错误,务必小心

map:  Map是关联容器,以键值对的形式进行存储,方便进行查找,关键词起到索引的作用,值则表示与索引相关联的数据,以红黑树的结构实现,插入删除等操作都可以在O(log n)时间内完成

set:Set是关联容器,set中每个元素都只包含一个关键字,set支持高效的关键字查询操作---检查每一个给定的关键字是否在set中,set是以红黑树的平衡二叉检索树结构实现的,支持高效插入删除,插如元素的时候会自动调整二叉树的结构,使得每个子树根节点键值大于左子树所有节点的键值,小于右子树所有节点的键值,另外还得保证左子树和右子树的高度相等

 

14.红黑树和平衡树的区别

https://blog.csdn.net/u010899985/article/details/80981053

15.多线程和多进程

为什么使用多线程?

在一个程序中,有很多的操作是非常耗时的,如数据库读写操作,IO操作等,如果使用单线程,那么程序就必须等待这些操作执行完成之后才能执行其他操作。使用多线程,可以在将耗时任务放在后台继续执行的同时,同时执行其他操作。提高程序执行的效率

缺点:

使用太多线程,会损耗系统资源影响系统性能多线程使用不当会产生很多问题,比如资源争抢

多进程:

凡是用于完成操作系统的各种功能的进程就是系统进程,而所有由你启动的进程都是用户进程,

多进程就是指计算机同时执行多个进程,一般是同时运行多个软件。

 

 

16.旋转数组找到target

 

最新回复(0)