计算机网络与python知识点总结

文章目录

计算机网络相关知识点整理:

1. OSI七层协议模型,TCP/IP四层模型,五层协议的体系结构之间的关系。

一、OSI七层模型
OSI七层协议模型主要是:应用层(Application)、表示层(Presentation)、会话层(Session)、传输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。
二、TCP/IP四层模型
TCP/IP是一个四层的体系结构,主要包括:应用层、运输层、网际层和网络接口层。从实质上讲,只有上边三层,网络接口层没有什么具体的内容。
在这里插入图片描述在这里插入图片描述
三、五层体系结构
五层体系结构包括:应用层、运输层、网络层、数据链路层和物理层。
五层协议只是OSI和TCP/IP的综合,实际应用还是TCP/IP的四层结构。为了方便可以把数据链路层和物理层称为网络接口层。

三种模型结构如下:
在这里插入图片描述

各层协议如下表:

在这里插入图片描述

2. TCP 和 UDP 是什么?简述它们有什么区别?

运输层中定义了一些传输数据的协议和端口号(WWW端口80等),如:
TCP(transmission control protocol –传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据)
UDP(user datagram protocol–用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。

  • TCP是面向连接的传输控制协议,而UDP提供了无连接的数据报服务;
  • TCP具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;
  • UDP在传输数据前不建立连接,不对数据报进行检查与修改,无须等待对方的应答,所以会出现分组丢失、重复、乱序,应用程序需要负责传输可靠性方面的所有工作;
    UDP具有较好的实时性,工作效率较TCP协议高。
  • TCP—传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能顺序地从一端传到另一端。
  • UDP—用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,不保证数据按顺序传递,故而传输速度很快。

3. 请描述 TCP 三次握手的过程, 为什么要三次握手?

三次握手过程如下:
三次握手 建立起 TCP连接 的 reliable,分配初始序列号和资源,在相互确认之后开始数据的传输。有 主动打开(一般是client) 和 被动打开(一般是server)。TCP使用3次握手建立一条连接,该握手初始化了传输可靠性以及数据顺序性必要的信息,这些信息包括两个方向的初始序列号,确认号由初始序列号生成,使用3次握手是因为3次握手已经准备好了传输可靠性以及数据顺序性所必要的信息,该握手的第3次实际上并不是需要单独传输的,完全可以和数据一起传输。
详细过程如下所示:

  • 第一步,Client会进入SYN_SENT状态,并发送Syn 消息给Server端,SYN标志位在此场景下被设置为1,同时会带上Client这端分配好的Seq号,这个序列号是一个U32的整型数,该数值的分配是根据时间产生的一个随机值,通常情况下每间隔4ms会加1。除此之外还会带一个MSS,也就是最大报文段长度,表示Tcp传往另一端的最大数据块的长度。
  • 第二步,Server端在收到,Syn消息之后,会进入SYN_RCVD状态,同时返回Ack消息给Client,用来通知Client,Server端已经收到SYN消息并通过了确认。这一步Server端包含两部分内容,一部分是回复Client的Syn消息,其中ACK=1,Seq号设置为Client的Syn消息的Seq数值+1;另一部分是主动发送Sever端的Syn消息给Client,Seq号码是Server端上面对应的序列号,当然Syn标志位也会设置成1,MSS表示的是Server这一端的最大数据块长度。
  • 第三步,Client在收到第二步消息之后,首先会将Client端的状态从SYN_SENT变换成ESTABLISHED,此时Client发消息给Server端,这个方向的通道已经建立成功,Client可以发送消息给Server端了,Server端也可以成功收到这些消息。其次,Client端需要回复ACK消息给Server端,消息包含ACK状态被设置为1,Seq号码被设置成Server端的序列号+1。(备注:这一步往往会与Client主动发起的数据消息,合并到一起发送给Server端。)
  • 第四步,Server端在收到这个Ack消息之后,会进入ESTABLISHED状态,到此时刻Server发向Client的通道连接建立成功,Server可以发送数据给Client,TCP的全双工连接建立完成。

为什么需要三次握手?
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

  • TCP的连接因为是全双工的,也就是Client和Server两端,发送消息两个方向的连接都要建立成功。如果要保证双向连接都成功的话,三次通信是最少的次数了。大于三次的话,后面的次数通信就没有必要了,是在浪费资源。
  • 二次的话,会怎么样,可不可以呢?答案是不可以,我们来看下,下面的场景。
  • 在谈论这个之前,我们先要知道TCP是基于IP协议的,而IP协议是有路由的,IP协议不能够保证先发送的数据先到达,这当中依赖于IP协议底层的网络质量,以及Client与Server之间的路由跳数。
  • Client在发送完Syn消息1,这里称作Syn1之后,假设因为网络原因,Syn1并没有到达Server端,这个时候Client端已经超时,Client之后重新发起SYN消息,这里称作Syn2。结果由于网络原因Syn2先到答Server,Server于是与Client基于Syn2建立了连接,结果没过多久Syn1又到达了Server,Server于是关掉了Syn2建立的那条连接,又重新建立了一条连接。对于Client来说新建立的这条连接是早就过时的,所以Client不会在这条连接上发送任何数据,这就导致了Server端长时间收不到数据,Client新的连接被断掉了。

4. 请描述 TCP 四次分手的过程, 为什么需要四次分手?

TCP 四次分手的过程:A—>B Fin, B—>A ACK, B—>A Fin, A—>B ACK
当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。那对于TCP的断开连接,这里就有了神秘的“四次分手”。

  • 第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  • 第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我已经知道你没有数据要发送了;
  • 第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入CLOSE_WAIT状态;
  • 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了

为什么需要四次分手:
TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。如果要正确的理解四次分手的原理,就需要了解四次分手过程中的状态变化。

  • FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)

  • FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)

  • CLOSE_WAIT:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)

    LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)

  • TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)

  • CLOSED: 表示连接中断。

5. 四次分手过程中为什么等待 2msl?

A—>B Fin, B—>A ACK, B—>A Fin, A—>B ACK
B—>A Fin, A—>B ACK过程中:B收到ACK,关闭连接。但是A无法知道ACK是否已经到达B,于是开始等待?等待什么呢?假如ACK没有到达B,B会为FIN这个消息超时重传 timeout retransmit ,那如果A等待时间足够,又收到FIN消息,说明ACK没有到达B,于是再发送ACK,直到在足够的时间内没有收到FIN,说明ACK成功到达。这个等待时间至少是:B的timeout + FIN的传输时间,为了保证可靠,采用更加保守的等待时间2MSL。MSL,Maximum Segment Life,这是TCP 对TCP Segment 生存时间的限制。TTL, Time To Live ,IP对IP Datagram 生存时间的限制,255 秒,所以 MSL一般 = TTL = 255秒A发出ACK,等待ACK到达对方的超时时间 MSL,等待FIN的超时重传,也是MSL,所以如果2MSL时间内没有收到FIN,说明对方安全收到FIN。

6. TCP 粘包是怎么回事,如何处理?UDP 有粘包吗?

粘包产生原因:
TCP:由于TCP协议本身的机制(面向连接的可靠地协议-三次握手机制)客户端与服务器会维持一个连接(Channel),数据在连接不断开的情况下,可以持续不断地将多个数据包发往服务器,但是如果发送的网络数据包太小,那么他本身会启用Nagle算法(可配置是否启用)对较小的数据包进行合并(基于此,TCP的网络延迟要UDP的高些)然后再发送(超时或者包大小足够)。那么这样的话,服务器在接收到消息(数据流)的时候就无法区分哪些数据包是客户端自己分开发送的,这样产生了粘包;服务器在接收到数据库后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,造成粘包现象(确切来讲,对于基于TCP协议的应用,不应用包来描述,而应 用 流来描述),个人认为服务器接收端产生的粘包应该与linux内核处理socket的方式 select轮询机制的线性扫描频度无关。

UDP:本身作为无连接的不可靠的传输协议(适合频繁发送较小的数据包),他不会对数据包进行合并发送(也就没有Nagle算法之说了),他直接是一端发送什么数据,直接就发出去了,既然他不会对数据合并,每一个数据包都是完整的(数据+UDP头+IP头等等发一次数据封装一次)也就没有粘包一说了。

分包产生的原因:可能是IP分片传输导致的,也可能是传输过程中丢失部分包导致出现的半包,还有可能就是一个包可能被分成了两次传输,在取数据的时候,先取到了一部分(还可能与接收的缓冲区大小有关系),总之就是一个数据包被分成了多次接收。

粘包与分包处理方法:

  • 如果你是连续的整个数据流 比如发送文件 那么完全不考虑粘包也无所谓 因为可以建立连接后发送 发送完毕后断开连接 整个数据流就是整个一个文件 无论数据从那里切开都无所谓 整个拼接后依旧是整个一个文件的数据。

  • 如果你发送的数据是多次通信 比如把一个目录下所有的文件名都发送过去 那么就不能当作一个整体发送了 必须对他们划分边界 有一个很简单的处理方法 就是采用"数据长度+实际数据"的格式来发送数据 这个"数据长度"的格式是固定宽度的 比如4字节 可以表示0~4GB的宽度了 足够用了 这个宽度说明了后续实际数据的宽度 这样你就可以把粘包后的数据按照正确的宽度取出来了。
    每次都是取出4字节 随后按照正确的宽度取出后续部分的就OK了

  • 如果你的所有数据都是固定宽度的 比如不停的发送温度数据 每个都是1字节 那么宽度已知了 每次你都取出一个1字节就OK了 所以就不用发送宽度数据了

  • 当然你也可以按照建立连接断开连接来划分边界 每次发送数据都打开关闭一次连接 不过对于频繁的小数据量是不可取的做法 因为开销太大 建立连接和关闭连接也是需要耗费网络流量的

总结:
一个是采用分隔符的方式,即我们在封装要传输的数据包的时候,采用固定的符号作为结尾符(数据中不能含结尾符),这样我们接收到数据后,如果出现结尾标识,即人为的将粘包分开,如果一个包中没有出现结尾符,认为出现了分包,则等待下个包中出现后 组合成一个完整的数据包,这种方式适合于文本传输的数据,如采用/r/n之类的分隔符;

另一种是采用在数据包中添加长度的方式,即在数据包中的固定位置封装数据包的长度信息(或可计算数据包总长度的信息),服务器接收到数据后,先是解析包长度,然后根据包长度截取数据包(此种方式常出现于自定义协议中),但是有个小问题就是如果客户端第一个数据包数据长度封装的有错误,那么很可能就会导致后面接收到的所有数据包都解析出错(由于TCP建立连接后流式传输机制),只有客户端关闭连接后重新打开才可以消除此问题,我在处理这个问题的时候对数据长度做了校验,会适时的对接收到的有问题的包进行人为的丢弃处理(客户端有自动重发机制,故而在应用层不会导致数据的不完整性);

总之, 粘包的情况是无法绝对避免的 因为网络环境是很复杂的 依赖发送和接收缓冲区的控制是不能保证100%的 只要在发送的数据中说明数据的宽度随后在接收部分按照这个宽度拆开就OK了 宽度全都是统一的已知宽度的情况下拆开更加容易 连在发送端填入宽度数据都可以省去了

7. time_wait 是什么情况?出现过多的 close_wait 可能是什么原因?

  • TIME_WAIT 是主动关闭连接的一方保持的状态,对于服务器来说它本身就是“客户端”,在完成一个爬取任务之后,它就会发起主动关闭连接,从而进入TIME_WAIT的状态,然后在保持这个状态2MSL(max segment lifetime)时间之后,彻底关闭回收资源。

  • TIME_WAIT状态可以通过优化服务器参数得到解决,因为发生TIME_WAIT的情况是服务器自己可控的,要么就是对方连接的异常,要么就是自己没有迅速回收资源,总之不是由于自己程序错误导致的。

  • CLOSE_WAIT表示被动关闭,关闭 TCP 连接过程中,第 1 次挥手服务器接收客户端的 FIN 报文段,第 2 次挥手时,服务器发送了 ACK 报文段之后,服务器会进入 close_wait 状态。

  • 如果一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在对方关闭连接之后服务器程序自己没有进一步发出FIN信号,一般原因都是TCP连接没有调用关闭方法。换句话说,就是在对方连接关闭之后,程序里没有检测到,或者程序压根就忘记了这个时候需要关闭连接,于是这个资源就一直被程序占着。这种情况,通过服务器内核参数也没办法解决,服务器对于程序抢占的资源没有主动回收的权利,除非终止程序运行,一定程度上,可以使用TCP的KeepAlive功能,让操作系统替我们自动清理掉CLOSE_WAIT连接。

  • 什么情况下,连接处于CLOSE_WAIT状态呢?
    答案一:在被动关闭连接情况下,在已经接收到FIN,但是还没有发送自己的FIN的时刻,连接处于CLOSE_WAIT状态。通常来讲,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态。但是在一些特殊情况下,就会出现连接长时间处于CLOSE_WAIT状态的情况。
    答案二:出现大量close_wait的现象,主要原因是某种情况下对方关闭了socket链接,但是我方忙与读或者写,没有关闭连接。 代码需要判断socket,一旦读到0,断开连接,read返回负,检查一下errno,如果不是AGAIN,就断开连接。

8. epoll,select 的区别?边缘触发,水平触发区别?

  • select,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

  • 问题:当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求

  • 解决方案:.一种较好的方式为I/O多路转接(I/O multiplexing)(貌似也翻译多路复用),先构造一张有关描述符的列表(epoll中为队列),然后调用一个函数,直到这些描述符中的一个准备好时才返回,返回时告诉进程哪些I/O就绪。select和epoll这两个机制都是多路I/O机制的解决方案,select为POSIX标准中的,而epoll为Linux所特有的。

  • 区别(epoll相对select优点)主要有三:

    1.select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:#define __FD_SETSIZE 1024 表示select最多同时监听1024个fd。而epoll没有,它的限制是最大的打开文件句柄数目。

    2.epoll的最大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对“活跃”的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有“活跃”的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个“伪”AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂)。

    3.使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

  • 边缘触发,水平触发区别如下:

  • epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的水平触发模式,ET是“高速”边缘模式。

  • LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

    ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了,但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)
     LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。

9. 简述一下你了解的端口及对应的服务。(至少 5 个)

  • 21 FTP(文件传输协议)
    22 SSH
    23 Talnet(远程)服务
    25 SMTP(简单邮件传输协议)
    53 DNS域名服务器
    80 HTTP超文本传输协议
    110 POP3邮件协议3
    443 HTTPS
    1080 Sockets
    1521 Oracle数据库默认端口
    3306 Mysql服务

10. HTTP 协议是什么?工作原理是什么?

  • HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。HTTP是基于TCP/IP协议的应用程序协议,它并不包括数据包的传输,主要规定了客户端与服务器的通信格式,默认使用的是80端口。
  • 工作原理
    HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息,通常情况下会配合数字证书实现。
    TLS/SSL协议不仅仅是一套加密传输的协议,更是一件经过艺术家精心设计的艺术品,TLS/SSL中使用非对称加密,对称加密,数字证书以及HASH算法四种技术。
    一个事务分为四个过程:建立连接、浏览器发出请求信息、服务器发出响应信息、关闭连接。每次连接只处理一个请求和响应。对每一个文件的访问,浏览器与服务器都要建立一次单独的连接。
    1 ) 、地址解析
    如用客户端浏览器请求这个页面:http://localhost:8080/simple.htm 从中分解出协议名、主机名、端口、对象路径等部分,对于我们的这个地址,解析得到的结果如下:
    协议名:http
    主机名:localhost.com
    端口:8080
    对象路径:/index.htm 在这一步,需要域名系统DNS解析域名localhost.com,得主机的IP地址。
    2)封装HTTP请求数据包
    把以上部分结合本机自己的信息,封装成一个HTTP请求数据包
    3)封装成TCP包,建立TCP连接(TCP的三次握手)
    在HTTP工作开始之前,客户机(Web浏览器)首先要通过网络与服务器建立连接,该连接是通过TCP来完成的,该协议与IP协议共同构建Internet,即著名的TCP/IP协议族,因此Internet又被称作是TCP/IP网络。HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之后才能,才能进行更层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80。这里是8080端口。
    4)客户机发送请求命令
    建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可内容。
    5)服务器响应
    服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。 实体消息是服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据
    6)服务器关闭TCP连接
    一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码 Connection:keep-alive TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

11. HTTP 报文结构

HTTP通信过程包括客户端往服务器端发送请求以及服务器端给客户端返回响应两个过程。在这两个过程中就会产生请求报文和响应报文。

  • 什么是HTTP报文?
    HTTP报文是用于HTTP协议交互的信息,HTTP报文本身是由多行数据构成的字符串文本。客户端的HTTP报文叫做请求报文服务器端的HTTP报文叫做响应报文

  • HTTP报文由哪几部分构成?各部分都有什么作用?
    HTTP报文由报文首部报文主体构成,中间由一个空行分隔。 报文首部是客户端或服务器端需处理的请求或响应的内容及属性, 可以传递额外的重要信息。报文首部包括请求行和请求头部,报文主体主要包含应被发送的数据。通常,不一定有报文主体。

HTTP报文首部的结构:由首部字段名和字段值构成的,中间用冒号“:”分割。首部字段格式: 首部字段名:字段值。

例如,在HTTP首部中以Content-Type这个字段来表示报文主体的对象类型:

  • Content-Type:text/html。
    上述的Content-Type是首部字段名,text/html是字段值,字段值可以是多个值,例如:Keep-Alive:timeout=15,max=10。

HTTP首部字段通常有4种类型:通用首部,请求首部,响应首部,实体首部。

  • 通用首部字段:请求报文和响应报文两方都会使用的首部。

  • 请求首部字段:从客户端向服务器端发送请求报文时使用的首部。补充了请求的附加内容、客户端信息、响应内容相关优先级等信息。

  • 响应首部字段:从服务器端向客户端返回响应报文时使用的首部。补充了响应的附加内容,也会要求客户端附加额外的内容信息。

  • 实体首部字段:针对请求报文和响应报文的实体部分使用的首部。补充了资源内容更新时间等和实体有关的信息。

请求报文及响应报文的结构

  • (1)HTTP请求报文
    一个HTTP请求报文由请求行(request line)、请求头部(request header)、空行和请求数据4个部分构成。
    在这里插入图片描述
    请求行数据格式由三个部分组成:请求方法、URI、HTTP协议版本,他们之间用空格分隔。
    该部分位于数据首行,基本格式为:
GET /index.html HTTP/1.1

该部分的请求方法字段给出了请求类型,URI给出请求的资源位置(/index.html)。HTTP中的请求类型包括:GET、POST、HEAD、PUT、DELETE。一般常用的为GET和POST方式。最后HTTP协议版本给出HTTP的版本号。

  • (2)HTTP响应报文
    HTTP响应报文由状态行(HTTP版本、状态码(数字和原因短语))、响应头部、空行和响应体4个部分构成。
    其中响应头部和响应体同样也是通过一个空行进行隔开,或者有一些浏览器响应头部在Header中显示,响应体在Reponse中显示。
    响应头部主要是返回一些服务器的基本信息,以及一些Cookie值等。
    响应体为请求需要得到的具体数据,可以为任何类型数据,一般网页浏览返回的为html文件内容。
    状态行主要给出响应HTTP协议的版本号、响应返回状态码、响应描述,同样是单行显示。格式为:HTTP/1.1 200 OK
    状态码告知从服务器端返回的请求的状态,一般由一个三位数组成,分别以整数1~5开头组成。
    在这里插入图片描述

12. GET 和 POST 请求的区别

  • (1)get是从服务器上获取数据,post是向服务器传送数据。

  • (2)生成方式不同
    Get:URL输入;超连接;Form表单中method属性为get;Form表单中method为空。
    Post只有一种:Form表单中method为Post。

  • (3)数据传送方式:Get传递的请求数据按照key-value的方式放在URL后面,在网址中可以直接看到,使用?分割URL和传输数据,传输的参数之间以&相连,如:login.action?name=user&password=123。所以安全性差。
    POST方法会把请求的参数放到请求头部和空格下面的请求数据字段就是请求正文(请求体)中以&分隔各个字段,请求行不包含参数,URL中不会额外附带参数。所以安全性高。

  • (4)发送数据大小的限制:通常GET请求可以用于获取轻量级的数据,而POST请求的内容数据量比较庞大些。
    Get:1~2KB。get方法提交数据的大小直接影响到了URL的长度,但HTTP协议规范中其实是没有对URL限制长度的,限制URL长度的是客户端或服务器的支持的不同所影响。
    Post:没有要求。post方式HTTP协议规范中也没有限定,起限制作用的是服务器的处理程序的能力。

  • (5)提交数据的安全:POST比GET方式的安全性要高。Get安全性差,Post安全性高。
    通过GET提交数据,用户名和密码将明文出现在URL上,如果登录页面有浏览器缓存,或者其他人查看浏览器的历史记录,那么就可以拿到用户的账号和密码了。安全性将会很差。

13. HTTP 常见的状态码有哪些?301,302,404,500,502,504 等

  • 1XX 请求正在处理

  • 2XX 请求成功
    200 OK 正常处理
    204 no content 请求处理成功但没有资源可返回
    206 Partial Content 对资源的某一部分请求

  • 3XX 重定向
    301 Moved Permanenly请求资源的URI已经更新(永久移动),客户端会同步更新URI。
    302 Found 资源的URI已临时定位到其他位置,客户端不会更新URI。

    303 See Other 资源的URI已更新,明确表示客户端要使用GET方法获取资源。

    304 Not Modified 当客户端附带条件请求访问资源时资源已找到但未符合条件请求。

    307 Temporary Redirect临时重定向

  • 4XX 客户端错误
    400 Bad Request 请求报文中存在语法错误,一般为参数异常。
    401 Unauthorized 发送的请求需要HTTP认证。
    403 Forbiddden 不允许访问,对请求资源的访问被服务器拒绝 404 Not Found 无法找到请求的资源,请求资源不存在。
    405 请求的方式不支持。

  • 5XX 服务器错误
    500 Internal Server Error 服务器的内部资源出故障,服务器在执行请求时发生了错误。

    503 Service Unavailable 服务器暂时处于超负载状态或正在进行停机维护,无法处理请求,服务器正忙

主要状态码

  • 200:正常
  • 301:永久重定向
  • 302/307:临时重定向
  • 304:未修改,可以使用缓存,无需再次修改
  • 404:资源找不到
  • 500:服务器内部错误

14. HTTP 与 HTTPS 的区别是什么?

HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
区别

  • 1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
  • 2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  • 3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  • 4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。HTTPS并不是一个单独的协议,是对工作在一加密连接(SSL/TLS)上常规HTTP协议。通过在TCP和HTTP之间加入TLS(Transport Layer Security)来加密。

15. 在浏览器中输入 www.baidu.com 后执行的全部过程。

浏览器——即“客户端”

  • 1、浏览器获取输入的域名www.baidu.com
  • 2、浏览器向域名系统DNS请求解析www.baidu.com的IP地址
  • 3、DNS解析出百度服务器的IP地址
  • 4、浏览器与服务器建立TCP连接(默认端口80)
  • 5、浏览器发出HTTP请求,请求百度首页
  • 6、服务器通过HTTP请求把首页文件发给浏览器
  • 7、TCP连接释放
  • 8、浏览器解析首页文件,展示web界面

16. 常用加密算法及原理

在安全领域,利用密钥加密算法来对通信的过程进行加密是一种常见的安全手段。利用该手段能够保障数据安全通信的三个目标:

  • 1、数据的保密性,防止用户的数据被窃取或泄露;
  • 2、保证数据的完整性,防止用户传输的数据被篡改;
  • 3、通信双方的身份确认,确保数据来源与合法的用户;

而常见的密钥加密算法类型大体可以分为三类:对称加密、非对称加密、单向加密。

对称加密算法

  • 对称加密算法采用单密钥加密,在通信过程中,数据发送方将原始数据分割成固定大小的块,经过密钥和加密算法逐个加密后,发送给接收方;接收方收到加密后的报文后,结合密钥和解密算法解密组合后得出原始数据。由于加解密算法是公开的,因此在这过程中,密钥的安全传递就成为了至关重要的事了。而密钥通常来说是通过双方协商,以物理的方式传递给对方,或者利用第三方平台传递给对方,一旦这过程出现了密钥泄露,不怀好意的人就能结合相应的算法拦截解密出其加密传输的内容。
  • 对称加密算法拥有着算法公开、计算量小、加密速度和效率高得特定,但是也有着密钥单一、密钥管理困难等缺点在这里插入图片描述
    常见的对称加密算法有:
    DES:分组式加密算法,以64位为分组对数据加密,加解密使用同一个算法。
    3DES:三重数据加密算法,对每个数据块应用三次DES加密算法。
    AES:高级加密标准算法,是美国联邦政府采用的一种区块加密标准,用于替代原先的DES,目前已被广泛应用。
    Blowfish:Blowfish算法是一个64位分组及可变密钥长度的对称密钥分组密码算法,可用来加密64比特长度的字符串。

非对称加密算法
非对称加密算法采用公钥和私钥两种不同的密码来进行加解密。公钥和私钥是成对存在,公钥是从私钥中提取产生公开给所有人的,如果使用公钥对数据进行加密,那么只有对应的私钥才能解密,反之亦然。
下图为简单非对称加密算法的常见流程:
在这里插入图片描述

发送方Bob从接收方Alice获取其对应的公钥,并结合相应的非对称算法将明文加密后发送给Alice;Alice接收到加密的密文后,结合自己的私钥和非对称算法解密得到明文。这种简单的非对称加密算法的应用其安全性比对称加密算法来说要高,但是其不足之处在于无法确认公钥的来源合法性以及数据的完整性。

  • 非对称加密算法具有安全性高、算法强度负复杂的优点,其缺点为加解密耗时长、速度慢,只适合对少量数据进行加密,其常见算法包括:
    RSA:RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,可用于加密,也能用于签名。
    DSA:数字签名算法,仅能用于签名,不能用于加解密。
    DSS:数字签名标准,技能用于签名,也可以用于加解密。
    ELGamal:利用离散对数的原理对数据进行加解密或数据签名,其速度是最慢的。

单向加密
单向加密算法常用于提取数据指纹,验证数据的完整性。发送者将明文通过单向加密算法加密生成定长的密文串,然后传递给接收方。接收方在收到加密的报文后进行解密,将解密获取到的明文使用相同的单向加密算法进行加密,得出加密后的密文串。随后将之与发送者发送过来的密文串进行对比,若发送前和发送后的密文串相一致,则说明传输过程中数据没有损坏;若不一致,说明传输过程中数据丢失了。单向加密算法只能用于对数据的加密,无法被解密,其特点为定长输出、雪崩效应。常见的算法包括:MD5、sha1、sha224等等,其常见用途包括:数字摘要、数字签名等等
在这里插入图片描述

Python 语法相关知识点整理

1. 说说 Linux 常用命令的命令有哪些(不少于 20 个高阶命令)。

linux的目录结构
/ 下级目录结构

  • bin (binaries)存放二进制可执行文件
  • sbin (super user binaries)存放二进制可执行文件,只有root才能访问
  • etc (etcetera)存放系统配置文件
  • usr (unix shared resources)用于存放共享的系统资源
  • home 存放用户文件的根目录
  • root 超级用户目录
  • dev (devices)用于存放设备文件
  • lib (library)存放跟文件系统中的程序运行所需要的共享库及内核模块
  • mnt (mount)系统管理员安装临时文件系统的安装点
  • boot 存放用于系统引导时使用的各种文件
  • tmp (temporary)用于存放各种临时文件
  • var (variable)用于存放运行时需要改变数据的文件

操作文件及目录在这里插入图片描述
在这里插入图片描述

系统常用命令
在这里插入图片描述

在这里插入图片描述
压缩解压缩
在这里插入图片描述
文件权限操作
linux文件权限的描述格式解读

r 可读权限,w可写权限,x可执行权限(也可以用二进制表示 111 110 100 --> 764)
第1位:文件类型(d 目录,- 普通文件,l 链接文件)
第2-4位:所属用户权限,用u(user)表示
第5-7位:所属组权限,用g(group)表示
第8-10位:其他用户权限,用o(other)表示
第2-10位:表示所有的权限,用a(all)表示在这里插入图片描述

2. 简述解释型和编译型编程语言?

  • 解释型语言编写的程序不需要编译,在执行的时候,专门有一个解释器能够将VB语言翻译成机器语言,每个语句都是执行的时候才翻译。这样解释型语言每执行一次就要翻译一次,效率比较低。

  • 用编译型语言写的程序执行之前,需要一个专门的编译过程,通过编译系统,把源高级程序编译成为机器语言文件,翻译只做了一次,运行时不需要翻译,所以编译型语言的程序执行效率高,但也不能一概而论,部分解释型语言的解释器通过在运行时动态优化代码,甚至能够使解释型语言的性能超过编译型语言

3. python 中.pyc 文件是什么?

当我们在命令行中输入python hello.py时,其实是激活了Python的“解释器”,告诉“解释器”:你要开始工作了。可是在“解释”之前,其实执行的第一项工作和Java一样,是编译。当我们执行python hello.py时,他也一样执行了这么一个过程,所以我们应该这样来描述Python,Python是一门先编译后解释的语言。

  • 简述Python的运行过程:
    在说这个问题先来说两个概念,0PyCodeObject和pyc文件。
    我们在硬盘上看到的pyc自然不必多说,而其实PyCodeObject则是Python编译器真正编译成的结果。
    当python程序运行时,编译的结果则是保存在位于内存中的PyCodeObject中,当Python程序运行结束时,Python解释器将PyCodeObject写回到pyc文件中。
    当python程序第二次运行时,首先程序会在硬盘中寻找pyc文件,如果找到,则直接载入,否则就重复上面的过程。
    所以我们应该这样来定位PyCodeObject和pyc文件,我们说pyc文件其实是 PyCodeObject的一种持久化保存方式。

4. python 中的可变对象和不可变对象之间的区别

可变对象:对象存放在地址中的值会原地被改变(所谓的改变是创建了一块新的地址并把新的对象的值放在新地址中原来的对象并没有发生变化)
不可变对象:对象存放在地址中的值不会改变
int str float tuple 都属于不可变对象 其中tuple有些特殊 dict set list 属于可变对象
总结:
可变对象是指,一个对象在不改变其所指向的地址的前提下,可以修改其所指向的地址中的值;
不可变对象是指,一个对象所指向的地址上值是不能修改的,如果你修改了这个对象的值,那么它指向的地址就改变了,相当于你把这个对象指向的值复制出来一份,然后做了修改后存到另一个地址上了,但是可变对象就不会做这样的动作,而是直接在对象所指的地址上把值给改变了,而这个对象依然指向这个地址。

变量的不可变举例:


i=73
print(id(i))
i+=2
print(id(i))

i是一个变量,它指向对象的内容是73,当执行i+=2时,首先创建出一个新的内存里面存放改变后的值(75),然后让i指向新的地址,这是不可变对象在“改变”时的执行步骤,原来的对象内容和内存并没有发生变化。对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。

在这里插入图片描述
可变对象实例:
可变对象对于自身的任意方法,是不需要重新开辟内存空间的,而是原地改变。

m=[5,9]

m.append(6)

print(m)

m的值变为了[5,9,6]当执行append时由于m是list类型属于可变对象所以它不会开辟新的内存空间而是像下图一样原地改变其值:
在这里插入图片描述

5. 字符串拼接直接用+会产生什么问题怎么去优化?

+的效率最慢,优化有两种方式如下:

s = []

for n in range(0,1000):

    s.append(str(n))

''.join(s)
s = ''.join(map(str,range(0,1000)))  #此方法最好

6. 列表和元组有什么不同?列表和集合有什么区别?

一、列表
1.任意对象的有序集合
列表是一组任意类型的值,按照一定顺序组合而成的
2.通过偏移读取
组成列表的值叫做元素(Elements)。每一个元素被标识一个索引,第一个索引是0,序列的功能都能实现
3.可变长度,异构以及任意嵌套
列表中的元素可以是任意类型,甚至是列表类型,也就是说列表可以嵌套
4.可变的序列
支持索引、切片、合并、删除等等操作,它们都是在原处进行修改列表
5.对象引用数组
列表可以当成普通的数组,每当用到引用时,Python总是会将这个引用指向一个对象,所以程序只需处理对象的操作。当把一个对象赋给一个数据结构元素或变量名时,Python总是会存储对象的引用,而不是对象的一个拷贝
二、元组
1.任意对象的有序集合
与列表相同
2.通过偏移存取
与列表相同
3.属于不可变序列类型
类似于字符串,但元组是不可变的,不支持在列表中任何原处修改操作,不支持任何方法调用
4.固定长度、异构、任意嵌套
固定长度即元组不可变,在不被拷贝的情况下长度固定,其他同列表
5.对象引用的数组
与列表相似,元祖是对象引用的数组
和list相比
1.比列表操作速度快
2.对数据“写保护“
3.可用于字符串格式化中
4.可作为字典的key
三、字典
1.通过键而不是偏移量来读取
字典就是一个关联数组,是一个通过关键字索引的对象的集合,使用键-值(key-value)进行存储,查找速度快
2.任意对象的无序集合
字典中的项没有特定顺序,以“键”为象征
3.可变长、异构、任意嵌套
同列表,嵌套可以包含列表和其他的字典等
4.属于可变映射类型
因为是无序,故不能进行序列操作,但可以在远处修改,通过键映射到值。字典是唯一内置的映射类型(键映射到值的对象)
5.对象引用表
字典存储的是对象引用,不是拷贝,和列表一样。字典的key是不能变的,list不能作为key,字符串、元祖、整数等都可以
和list比较,dict有以下几个特点:
1.查找和插入的速度极快,不会随着key的增加而增加
2.需要占用大量的内存,内存浪费多
而list相反:
1.查找和插入的时间随着元素的增加而增加
2.占用空间小,浪费内存很少
所以,dict是用空间来换取时间的一种方法
四、集合
1.是一组key的集合,但不存储value,并且key不能重复
创建一个set,需要提供一个list作为输入集合,s = set([1,2,3]),注意,传入的参数 [1, 2, 3] 是一个list,而显示的 set([1, 2, 3]) 只是告诉你这个set内部有1,2,3这3个元素,显示的[ ]不表示这是一个list
2.重复元素在set中自动被过滤
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作
还有一种集合是forzenset( ),是冻结的集合,它是不可变的,存在哈希值,好处是它可以作为字典的key,也可以作为其它集合的元素。缺点是一旦创建便不能更改,没有add,remove方法
和dict对比
1.set和dict的唯一区别仅在于没有存储对应的value
2.set的原理和dict一样,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”

7. python 中字典的底层是怎么实现的?

从以下三个方面来回答:
1.python字典及其特性

字典是Python的一种可变、无序容器数据结构,它的元素以键值对的形式存在,键值唯一,它的特点搜索速度很快:数据量增加10000倍,搜索时间增加不到2倍;当数据量很大的时候,字典的搜索速度要比列表快成百上千倍1。

2.哈希表

Python字典的底层实现是哈希表。什么是哈希表,简单来说就是一张带索引和存储空间的表,对于任意可哈希对象,通过哈希索引的计算公式:hash(hashable)%k(对可哈希对象进行哈希计算,然后对结果进行取余运算),可将该对象映射为0到k-1之间的某个表索引,然后在该索引所对应的空间进行变量的存储/读取等操作。

在这里插入图片描述

3.Python字典如何运用哈希表
我们通过描述插入,查询,删除,扩容,哈希碰撞这几个过程来解释这一切。

插入:

对键进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的表地址空间为空,将键值对存入该地址空间;

更新:

对键进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的地址空间中健与要更新的健一致,那么就更新该健所对应的值;

查询:

对要查找的健进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的地址空间中健与要查询的健一致,那么就将该键值对取出来;

扩容:

字典初始化的时候,会对应初始化一个有k个空间的表,等空间不够用的时候,系统就会自动扩容,这时候会对已经存在的键值对重新进行哈希取余运算(重新进行插入操作)保存到其它位置;

碰撞:
有时候对于不同的键,经过哈希取余运算之后,得到的索引值一样,这时候怎么办?这时采用公开寻址的方式,运用固定的模式将键值对插入到其它的地址空间,比如线性寻址:如果第i个位置已经被使用,我们就看看第i+1个,第i+2个,第i+3个有没有被使用…直到找到一个空间或者对空间进行扩容。
比如:我们想存储 {’小小‘:18}这个键值对,经过哈希和取余运算之后,我们发现,其对应的索引值是0,但是0所指向的空间已经被’小王‘占用了,这就是碰撞。怎么办呢?我们看看0+1对应的索引有没有被占用,如果没有,我们就把’小小‘放在索引1所对应的地址空间中。取的时候,也按照同样的规则,进行探查。

在这里插入图片描述
字典比列查找高效原因
列表查找是按顺序一个一个遍历,当列表越大,查找所用的时间就越久
字典是通过键值直接计算得到对应的地址空间,查找一步到位
注意:
1、Cython 中 哈希表的计算公式为:hash(‘hashable’)&k,其中k 为2的n次方减1,其实与hash(‘hashable’)%(k+1)的结果一致。
2、解决碰撞的方法,Python用的不是线性寻址,而是一种更为复杂的寻址模式。

8. is 和==的区别

Python中对象包含的三个基本要素,分别是:id(身份标识)、type(数据类型)和value(值)。
is和== 都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同。

‘ == ’ 是python标准操作符中的比较操作符,用来比较判断两个对象的 value(值) 是否相等,例如下面两个字符串间的比较:

>>> a = 'cheesezh'
>>> b = 'cheesezh'
>>> a == b
True

is也被叫做同一性运算符,这个运算符比较判断的是对象间的唯一身份标识,也就是id是否相同。

>>> x = y = [4,5,6]   # X与Y指向同一个地址
>>> z = [4,5,6]  # Z 指向另一个地址
>>> x == y
True
>>> x == z
True
>>> x is y
True
>>> x is z
False
>>>
>>> print id(x)
3075326572
>>> print id(y)
3075326572
>>> print id(z)
3075328140

注意:只有数值型和字符串型的情况下,a is b才为True,当a和b是tuple,list,dict或set型时,a is b为False。

>>> a = 1 #a和b为数值类型
>>> b = 1
>>> a is b
True
>>> id(a)
14318944
>>> id(b)
14318944
>>> a = 'cheesezh' #a和b为字符串类型
>>> b = 'cheesezh'
>>> a is b
True
>>> id(a)
42111872
>>> id(b)
42111872
>>> a = (1,2,3) #a和b为元组类型
>>> b = (1,2,3)
>>> a is b
False
>>> id(a)
15001280
>>> id(b)
14790408
>>> a = [1,2,3] #a和b为list类型
>>> b = [1,2,3]
>>> a is b
False
>>> id(a)
42091624
>>> id(b)
42082016
>>> a = {'cheese':1,'zh':2} #a和b为dict类型
>>> b = {'cheese':1,'zh':2}
>>> a is b
False
>>> id(a)
42101616
>>> id(b)
42098736
>>> a = set([1,2,3])#a和b为set类型
>>> b = set([1,2,3])
>>> a is b
False
>>> id(a)
14819976
>>> id(b)
14822256

9. 深拷贝和浅拷贝的区别是什么?如何实现?

深拷贝和浅拷贝需要注意的地方就是可变元素的拷贝
在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的,也就是说浅拷贝它拷贝的是浅层次的数据结构(不可变元素),对象里的可变元素作为深层次的数据结构并没有被拷贝到新地址里面去,而是和原对象里的可变元素指向同一个地址,所以在新对象或原对象里对这个可变元素做修改时,两个对象是同时改变的,但是深拷贝不会这样,这个是浅拷贝相对于深拷贝最根本的区别。
也可以这样理解:
深拷贝就是完全跟以前就没有任何关系了,原来的对象怎么改都不会影响当前对象
浅拷贝,原对象的list元素改变的话会改变当前对象,如果当前对象中list元素改变了,也同样会影响原对象。
浅拷贝就是藕断丝连
深拷贝就是离婚了

通常复制的时候要用深拷贝,因为浅拷贝后,两个对象中不可变对象指向不同地址,相互不会改变,但是两个对象中的可变元素是指向相同的地址,一个变了,另一个会同时改变,会有影响(list是可变对象)。

如果要让原list和copy list没有影响怎么办?
深拷贝,拷贝后完全开辟新的内存地址来保存之前的对象,虽然可能地址执行的内容可能相同(同一个地址,例如’s’),但是不会相互影响。
比如:
List1=[‘a’,’b’,’c’]
List2=[‘a’,’b’,’c’]
两个列表中的’a’的地址是相同的
Id(list1[0])=id(list2[0]),但是两个列表的地址是不同的

举例:

import copy
a=[1,2,3,4,5,['a','b']]
#原始对象
b=a#赋值,传对象的引用
c=copy.copy(a)#对象拷贝,浅拷贝
d=copy.deepcopy(a)#对象拷贝,深拷贝
print "a=",a,"    id(a)=",id(a),"id(a[5])=",id(a[5])
print "b=",b,"    id(b)=",id(b),"id(b[5])=",id(b[5])
print "c=",c,"    id(c)=",id(c),"id(c[5])=",id(c[5])
print "d=",d,"    id(d)=",id(d),"id(d[5])=",id(d[5])
print "*"*70

a.append(6)#修改对象a
a[5].append('c')#修改对象a中的['a','b']数组对象
print "a=",a,"    id(a)=",id(a),"id(a[5])=",id(a[5])
print "b=",b,"    id(b)=",id(b),"id(b[5])=",id(b[5])
print "c=",c,"       id(c)=",id(c),"id(c[5])=",id(c[5])
print "d=",d,"            id(d)=",id(d),"id(d[5])=",id(d[5])

结果:
在这里插入图片描述

10. Python 中的 pass 语句有什么作用?

在编写代码时只写框架思路,具体实现还未编写就可以用 pass 进行占位,使程序不报错,不会进行任何操作。

11. 能否解释一下 *args 和 **kwargs?

函数形参:*args 和 **kwargs

*args:将实参中按照位置传值,多出来的值都给args,以元组方式实现。

def multiply(*args): #累乘
    z = 1
    for num in args:
        z *= num
    print(z)

multiply(4, 5)
multiply(10, 9)
multiply(2, 3, 4)
multiply(3, 5, 10, 6)


执行结果:
20
90
24
900
def func(x, *args):
    print(x)
    print(args)

func(1, 2, 3, 4, 5)  # 1->x  2,3,4,5->args

执行结果:
1
(2,3,4,5)

**是形参中按照关键字传值把多余的传值以字典的方式实现。

def print_values(**kwargs):
    for key, value in kwargs.items():
        print("The value of {} is {}".format(key, value))
print_values(my_name="Sammy", your_name="Casey")

执行结果:
The value of my_name is Sammy
The value of your_name is Casey

12. 迭代器、可迭代对象、生成器分别是什么?生成器的作用和使用场景?

  • 可迭代对象
    像list,tuple,set,dict,str等可以直接作用于for循环的对象,称为可迭代对象。可迭代对象实现了__iter__方法,用于返回迭代器。
    iter()
    将可迭代对象转化为迭代器。
demo = [1,2,3,4]
print(isinstance(demo, Iterable)) //True
iter_object = iter(demo)  #可迭代对象转换为迭代器
print(iter_object) //<list_iterator object at 0x00000258DC5EF748>
  • 迭代器
    迭代器对象就是实现了iter() 和 next() 方法的对象.迭代器是通过next()来实现的,每调用一次他就会返回下一个元素,当没有下一个元素的时候返回一个StopIteration异常,所以实际上定义了这个方法的都算是迭代器。可以用通过下面例子来体验一下迭代器:
    迭代器类似于一个游标,卡到哪里就是哪里,可以通过这个来访问某个可迭代对象的元素
In [38]: s = 'ab'

In [39]: it = iter(s)

In [40]: it
Out[40]: <iterator at 0x1068e6d50>

In [42]: it.next()
Out[42]: 'a'

In [43]: it.next()
Out[43]: 'b
  • 生成器(Generators)
    生成器是构造迭代器的最简单有力的工具,与普通函数不同的只有在返回一个值的时候使用yield来替代return,然后yield会自动构建好next()和iter()。是不是很省事。例如:
定义生成器方式一:
gen = (x*x for x in range(5))
 print(gen) 
//Out:<generator object <genexpr> at 0x00000258DC5CD8E0>

我们可以利用next()访问生成器下一个元素
print(next(gen)) //0
print(next(gen)) //1
...
print(next(gen)) //16
print(next(gen)) //StopIteration

用for循环遍历
for n in gen:  
print(n) //0 1 4  9 16


定义生成器方式二:
def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

>>> for char in reverse('golf'):
...     print char
...
f
l
o
g

生成器最佳应用场景:你不想同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果集里还包含循环。比方说,循环打印1000000个数,我们一般会使用xrange()而不是range(),因为前者返回的是生成器,后者返回的是列表(列表消耗大量空间)。

13. yield 和 return 的工作原理

在这里插入图片描述
在这里插入图片描述

1、.print并不会阻断程序的执行,就不用多说了。
2、func2()方法中的循环执行第一次就被return结束掉了(后面的2、3、4就不会有返回的机会了)3、yield你可以通俗的叫它"轮转容器",可用现实的一种实物来理解:水车,先yield来装入数据、产出generator object、使用next()来释放;好比水(数据)装入水车(yield)中,随着轮子转动(调用next()),被转到下面的水槽就能将水送入水道中流入田里。

在这里插入图片描述

14. 请解释 Python 中的闭包?

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

#闭包函数实现的基本模板

def outter(xxx):
     def inner()
           xxx
      return inner

#为函数体传参的两个方案
#1.直接传入参数
#2.在函数体内部定义变量

15. python 中的装饰器是什么?如何实现?使用场景?

  • python的装饰器说白了就是闭包函数的一种应用场景,在运用的时候我们遵循:
    开放封闭原则:对修改封闭,对拓展开放。
    装饰器概念:装饰他人的器具,本身可以是任意可调用的对象,被装饰者也可以是任意可调用对象
    装饰器的原则:1.不可修改被装饰对象的源代码,2不修改被装饰对象的调用方式
    装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能
  • 如何实现:装饰器是通过闭包的方式实现的,外函数接收一个函数作为外函数的临时变量,然后在内函数中执行这个函数。内函数将需要的参数接收进来并传给执行的函数,然后将执行结果返回。在内函数中,可以添加额外的功能的代码,这些额外的功能就是装饰器添加的功能。最后外函数将内函数返回。使用装饰器来装饰函数时,在被装饰的函数的前一行,使用@装饰器函数名的形式来装饰,则函数本身的功能正常实现,装饰器中添加的功能也实现了。如上面代码中打印被装饰函数的函数名。
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

16. python 你平时主要用过哪些包?

time、random、numpy、Matplotlib、scikit-learn: Machine Learning in Python、Pandas、jieba

17. python 中的 map 是怎么实现的?

map()函数的简介以及语法:
map是python内置函数,会根据提供的函数对指定的序列做映射。
map()函数的格式是:
map(function,iterable,…)
第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个集合。
把函数依次作用在list中的每一个元素上,得到一个新的list并返回。注意,map不改变原list,而是返回一个新list。

items = [1,2,3,4,5]
lamb'd x:x**2为一个表达式,接受一个参数x并返回x的平方
squared=map(lamdba x:x**2, items)


执行结果:[1,4,9,16,25]

18. Python 是如何进行内存管理的?

Python引入了一个机制:引用计数。
python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数,当对象被创建时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为0时,它被垃圾回收。
引用计数增加情况:
1.
对象被创建:x = 4
2.
另外的别人被创建:y = x
3.
被作为参数传递给函数:foo(x)
4.
作为容器对象的一个元素:a = [1, x, ‘33’]
引用计数减少情况:
1.
一个本地引用离开了它的作用域。比如上面的foo(x)
函数结束时,x指向的对象引用减1。
2.
对象的别名被显式的销毁:del x ;或者del
y
3.
对象的一个别名被赋值给其他对象:x = 789
4.
对象从一个窗口对象中移除:myList.remove(x)
5.
窗口对象本身被销毁:del myList,或者窗口对象本身离开了作用域。

垃圾回收
1、当内存中有不再使用的部分时,垃圾收集器就会把他们清理掉。它会去检查那些引用计数为0的对象,然后清除其在内存的空间。当然除了引用计数为0的会被清除,还有一种情况也会被垃圾收集器清掉:当两个对象相互引用时,他们本身其他的引用已经为0了。
2、垃圾回收机制还有一个循环垃圾回收器, 确保释放循环引用对象(a引用b, b引用a, 导致其引用计数永远不为0)。

内存池机制
在Python中,许多时候申请的内存都是小块的内存,这些小块内存在申请后,很快又会被释放,由于这些内存的申请并不是为了创建对象,所以并没有对象一级的内存池机制。这就意味着Python在运行期间会大量地执行malloc和free的操作,频繁地在用户态和核心态之间进行切换,这将严重影响Python的执行效率。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。

Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的
malloc。另外Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。

19. 简述什么是面向过程编程和面向对象编程?

面向过程的程序设计(Procedure-Oriented Programming)是一种以过程为中心的设计方式。在该方式中,将目标功能的实现分为多个步骤。程序依据步骤的过程一步步执行,最终实现程序功能。
面向对象的程序设计(Object-Oriented Programming,简记为OOP),是当下最流行的程序设计方式之一。在面向对象的设计思想中,将程序视为多个对象共同协作的结果。程序被划分为多个子模块,再由多个对象完成各自模块最终实现程序的功能。

  • 举例:级要举办元旦晚会演出,有三名同学报名了歌舞表演环节,分别是唱歌的小明、唱歌的小李和跳舞的小红。歌舞环节由三个对象(小明、小李和小红),每个对象实现各自的模块(小明唱歌、小李唱歌、小红跳舞),由此实现了程序的功能。

面向对象的解决步骤:

扫地:

1.1 拿出扫地机器人

1.2 扫地机器人!开始干活!
洗衣:

2.1 找到洗衣机,放入衣服和洗衣液

2.2 洗衣机!开始干活!
吹风:

3.1 拿出电风扇

3.2 电风扇!开始干活!
这里的扫地机器人、洗衣机、电风扇扮演着对象(Object)。
面向过程的解决步骤:

扫地:

1.1 拿扫把和扫帚

1.2 将垃圾汇集到某处

1.3 将垃圾扫进扫帚

1.4 将扫帚的垃圾倒进垃圾桶
洗衣:

2.1 拿盆接好水,倒入洗衣液

2.2 放入衣服并浸泡

2.3 揉搓衣服

2.4 换清水漂洗
比较总结:

  • 面向过程编程中,开发者注重于程序功能实现的过程,编程过程中扮演类似执行者的角色
  • 面向对象编程中,开发者注重于对象的创建和调用,编程过程中扮演类似指挥者的角色
  • 面向过程编程中,开发者可以精准把控程序执行的每一步和每一个细节(比如:手洗衣服过程中,衣服的哪个部位需要多搓一会,扇扇子的时候多扇头还是扇脚)
  • 面向对象编程中,开发者无需知道对象的每一个细节,对象如何工作交给对象的设计者完成(当然开发者常常扮演设计者的角色,同时已经有很多东西已经被设计好了)
  • 面向过程设计方式在中小型项目中更有优势。开发者只需要想好步骤,再依据步骤写下来即可。
  • 面向对象设计方式在大中型项目中更有优势。开发者设计好对象后,只需调用对象完成任务使得代码更简洁易懂易于维护。
  • 面向对象设计方式在宏观上是面向对象的,在微观上依旧是面向过程的。 在每个对象的内部有着它们的行为属性(扫地、洗衣服、吹风),设计者在设计如何让它们工作的过程中依旧是按照面向过程的思想让程序按照步骤执行。由此可见:面向过程是程序设计的基本方式

20. 介绍一下继承和多态

继承:程序向上总结
将子类共同的行为和属性集中写到父类中,通过继承,所有子类都能自动获得这些属性和行为,大大减少了重复代码。
继承成为多态实现的基础。
多态:程序向下扩展(当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。)
父类某些行为,子类进行继承重写,从而实现:同种行为,不同的实现。

21. 介绍python 的魔术方法,new,init,call 的区别是什么

  • 在Python 中 存在于类里面的构造方法__init__()负责将类的实例化,而在__init__()启动之前,new()决定是否要使用该__init__()方法,因为__new__()可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。
  • 如果将类比喻为工厂,那么__init__()方法则是该工厂的生产工人,init()方法接受的初始化参数则是生产所需原料,init()方法会按照方法中的语句负责将原料加工成实例以供工厂出货。而 new()则是生产部经理,new()方法可以决定是否将原料提供给该生产部工人,同时它还决定着出货产品是否为该生产部的产品,因为这名经理可以借该工厂的名义向客户出售完全不是该工厂的产品。
class A(object):
    def __new__(cls, x):
        print 'this is in A.__new__, and x is ', x
        return super(A, cls).__new__(cls)

    def __init__(self, y):
        print 'this is in A.__init__, and y is ', y

class B(A):
    def __new__(cls, z):
        print 'this is in B.__new__, and z is ', z
        return A.__new__(cls, z)

    def __init__(self, m):
        print 'this is in B.__init__, and m is ', m

if __name__ == '__main__':
    a = A(100)
    print '=' * 20
    b = B(200)
    print type(b)

执行结果:
this is in A.__new__, and x is  100
this is in A.__init__, and y is  100
====================
this is in B.__new__, and z is  200
this is in A.__new__, and x is  200
this is in B.__init__, and m is  200
<class '__main__.B'>

1.定义A类作为下面类的父类,A类继承object类,因为需要重写A类的__new__()函数,所以需要继承object基类,成为新式类,经典类没有__new__()函数;
2.子类在重写__new__()函数时,写return时必须返回有继承关系的类的__new__()函数调用,即上面代码中的B类继承自A类,则重写B类的__new__()函数,写return时,只能返回A.new(cls)或者object.new(cls);
3.B类的__new__()函数会在B类实例化时被调用,自动执行其中的代码语句,但是重写__new__()函数不会影响类的实例化结果,也就是说不管写return时返回的是A的还是object的,B类的实例化对象就是B类的,而不会成为A类的实例化对象;只是在实例化时,如果返回的是A.new(cls),则会执行A类中定义的__new__()函数;
4.new()函数确定了类的参数的个数,object类默认定义的__new__()函数的参数为(cls, *more),但如果在子类中重写了__new__(cls, x), 则实例化类时,需要传入一个x参数,而__init__()函数接受到的有两个参数,一个是实例化生成的实例对象self代替,一个是传入的实参x的值;

  • init()是python的一个构造方法,在定义类时,用于初始化的一些操作;它能实现的功能及原理相对来讲也是比较简单一点,就是在实例化该类时,自动执行__init__()方法定义的内容;
>>> class A(object):
    def __init__(self, x):
        self.x = x
        print '__init__ called.'
    def foo(self):
        print self.x

     
>>> a = A('123')
__init__ called.
>>> 
>>> a.foo()
123

call_()方法能够让类的实例对象,像函数一样被调用;

class A(object):
    def __call__(self, x):
        print('__call__ called, print x: ', x)

>>> a = A()
>>> a('123')
__call__called, print x: 123

22. Python 如何实现单例模式?

详细完整版实现单例模式的各种方法
该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
方式一:Python 的模块
Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:

class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()

将上面的代码保存在文件 mysingleton.py 中,要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对象

from a import singleton

方式二:基于__new__方法实现
当我们实例化一个对象时,是先执行了类的__new__方法(我们没写时,默认调用object.new),实例化对象;然后再执行类的__init__方法,对这个对象进行初始化,所有我们可以基于这个,实现单例模式

23. 数据库中索引的作用,主键索引工作的大体流程。

作用:提高数据的查询速度
第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二,可以大大加快 数据的检索速度,这也是创建索引的最主要的原因。
第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
第四,在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

主键索引工作的大体流程:
主键:唯一标识记录(常见的什么什么ID),不允许重复,不允许为空。
主键默认建立唯一索引,所以创建表时,不能在同一个字段上建立两个索引
主键一定是唯一性索引,唯一性索引并不一定就是主键。
一个表中可以有多个唯一性索引,但只能有一个主键(图中多个主键指复合主键)
主键列不允许空值,而唯一性索引列允许空值。
索引可以提高查询的速度。

主键和索引都是键,不过主键是逻辑键,索引是物理键,意思就是主键不实际存在,而索引实际存在在数据库中

在这里插入图片描述

24. 给定一张表,写出 SQL 语句,字段id,name,subject,grade。求出总分大于 300 分的学生的名单,求出没有不及格成绩的学生名单。

create table students(
    -> id int primary key auto_increment ,
    -> studentname varchar(20) not null unique,
    -> subject varchar(20) not null,
    -> grade float   )


select * from students where grade > = 60 ;

25. 数据库联接操作,左连接,右链接,全链接的操作以及区别(手写 SQL 语句)

内连表:table_A inner join table_B,表 table_A 和 table_B 相匹配的行出现在结果集中
左连表:table_A left join table_B,表 table_A 和 table_B 相匹配的行出现在结果集中,外加表 table_A 中独有的数据,为对应的数据用 null 填充
右连表:table_A right join table_B,表 table_A 和 table_B 相匹配的行出现在结果集中,外加表 table_B 中独有的数据,为对应的数据用 null 填充

26. 数据库的三大范式的理解。

  • 第一范式(1NF):列不可拆分 , 即无重复的域。指数据库表的每一列都是不可分割的基本数据项。 符合第一范式的特点就有:有主关键字、主键不能为空、主键不能重复,字段不可以再分。
    理解: 第零范式中重复的字段抽取出来,作为表的数据,从而形成一个稳定的、冗余数据少得表结构。
    第二范式(2NF):满足属性对主键是完全函数依赖的,因此,满足第二范式的表当然也是满足第一范式的,第二范式的目的就是消除表中的部分依赖。
    完全函数依赖
    设有属性集K和P,若K中的所有属性共同能够推出P中的任意属性,且对于K的任何真子集,都不能推出P中的任意属性,则成K完全函数依赖P。
    部分函数依赖
    与上相似,只是,K中存在真子集使得,该子集能推出p中任意属性。
    举例
    有一张学生成绩表,包含如下属性(学生Id,课程Id,课程分数,学生名字),其中,主键为(学生id,课程id),表中的数据如下:
    在这里插入图片描述
    此时这张表的设计就不满足第二范式, 因为 主键(学生id,课程id) 能够唯一确定学生的姓名(K中存在真子集使得,该子集能推出p中任意属性。),因此,不满足属性完全函数依赖主键,因此不是第二范式。
    从上面的表数据易知,不满足第二范式的表至少有以下几个缺点:
    1:数据重复,浪费空间,因为每存一条记录,都要存学生的名字,这样就是得存在大量重复的数据。
    2: 插入异常,若一个表三个属性:姓名、成绩、老师名称,如果该老师没有教任何学生,就无法录入老师信息,产生插入异常。
    3: 更新异常,删除异常等
    解决方法
    student_name字段放入学生表中,即消除表中的部分依赖。
    第三范式(3NF): 第三范式是指在满足第二范式的情况下,消除表中的传递依赖。
    传递依赖,就是指x–>y,y–>z,那么可以得到x–>z.
    传递依赖常发生在主键、外键、外键相关的属性上,例如,假设有:
    学生表(学生id,学生姓名,院系id,院系名) ,此处主键为(学生id),外键为(院系id)
    院系表(院系id,院长名称),主键为 (院系id)
    很明显,此处存在传递依赖,因为 学生id 可以唯一确定 院系id,而 院系id 可以唯一确定 院系名。
    不满足第三范式的表至少有以下几个缺点:
    1 : 数据重复,浪费空间,因为学生表每存一条记录,都会记录住院系的名字,存在大量的重复数据。
    2: 插入异常,若新建一个院系,而该院系没有学生的话,该院系就没有名字。
    3: 更新异常,删除异常等

27 . 什么是 SQL 注入?

SQL注入:是现在普通使用的一种攻击手段,通过把非法的SQL命令插入到Web表单中或页面请求查询字符串中,最终达到欺骗服务器执行恶意的SQL语句的目的。SQL注入一旦成功,轻则直接绕开服务器验证,直接登录成功,重则将服务器端数据库中的内容一览无余,更有甚者,直接篡改数据库内容等。
SQL注入的产生条件:1). 有参数传递;2). 参数值带入数据库查询并且执行
为防止SQL注入,需要对用户的输入进行过滤,因为在Web攻防中,我们永远不要相信用户的输入。
防止SQL注入包括:使用预编译语句,绑定变量。
使用安全的存储过程对抗SQL注入。
检查数据类型。
使用安全函数。

28. Python 的底层实现,用到的排序函数(手写冒泡算法)

参考博客

29. 解释一下 python 中的 GIL 是什么

参考

30. GIL 是单线程的,那么 python 中多线程的实现有什么用。

进程之间不能共享内存,但线程之间共享内存非常容易。

操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此,使用多线程来实现多任务并发执行比使用多进程的效率高。

Python 语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了 Python 的多线程编程。
比如一个浏览器必须能同时下载多张图片;一个 Web 服务器必须能同时响应多个用户请求;图形用户界面(GUI)应用也需要启动单独的线程,从主机环境中收集用户界面事件……总之,多线程在实际编程中的应用是非常广泛的。

31. 对于多线程,我是怎么去使用的,如果要我去设计一个线程池,我该怎么去设计。

参考

32. python 协程实现一个生产者消费者模型。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值