面试

2022/2/16 6:13:21

本文主要是介绍面试,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1. 关于 Python 程序的运行方面,有什么手段能提升性能?

1、使用多进程,充分利用机器的多核性能
2、对于性能影响较大的部分代码,可以使用 C 或 C++编写
3、对于 IO 阻塞造成的性能影响,可以使用 IO 多路复用来解决
4、尽量使用 Python 的内建函数
5、尽量使用局部变量

 

2 你所遵循的代码规范是什么?请举例说明其要求?
PEP8 规范。
1. 变量
常量:大写加下划线 USER_CONSTANT。
私有变量 : 小写和一个前导下划线 _private_value。
Python 中不存在私有变量一说,若是遇到需要保护的变量,使用小写和一个前导下划线。但这只是程序员之间的一个约定,用于警告说明这是一个私有变量,外部类不要去访问它。但实际上,外部类还是可以访问到这个变量。
内置变量 : 小写,两个前导下划线和两个后置下划线 __class__两个前导下划线会导致变量在解释期间被更名。这是为了避免内置变量和其他变量产生冲突。用户定义的变量要严格避免这种风格。以免导致混乱。
2. 函数和方法
总体而言应该使用,小写和下划线。但有些比较老的库使用的是混合大小写,即首单词小写,之后
每个单词第一个字母大写,其余小写。但现在,小写和下划线已成为规范。
私有方法 :小写和一个前导下划线
3. 类
类总是使用驼峰格式命名,即所有单词首字母大写其余字母小写。类名应该简明,精确,并足以从
中理解类所完成的工作。
5.2 不要滥用 *args 和 **kwargs。*args 和 **kwargs 参数可能会破坏函数的健壮性。它们使签名变得模糊,而且代码常常开始在不应该的地方构建小的参数解析器。
6.2 用复数形式命名序列
members = ['user_1', 'user_2']
6.3 用显式名称命名字典
person_address = {'user_1':'10 road WD', 'user_2' : '20 street huafu'}
6.4 避免通用名称(尽量避免使用python关键字)
诸如 list, dict, sequence 或者 element 这样的名称应该避免。
6.5 避免现有名称
诸如 os, sys 这种系统已经存在的名称应该避免。
7. 一些数字
一个函数 : 不要超过 30 行代码, 即可显示在一个屏幕类,可以不使用垂直游标即可看到整个函数。
一个类 : 不要超过 200 行代码,不要有超过 10 个方法。一个模块 不要超过 500 行。
8. 验证脚本
可以安装一个 pep8 脚本用于验证你的代码风格是否符合 PEP8。

3 在 linux 中 find 和 grep 的区别

Linux 系统中 grep 命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。grep 全称是 Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。
linux 下的 find:
功能:在目录结构中搜索文件,并执行指定的操作。此命令提供了相当多的查找条件,功能很强大。
语法:find 起始目录寻找条件操作
说明:find 命令从指定的起始目录开始,递归地搜索其各个子目录,查找满足寻找条件的文件并对之采取相关的操作。简单点说说,grep 是查找匹配条件的行,find 是搜索匹配条件的文件。

 

4 Linux 重定向命令有哪些?有什么区别?
1、重定向>
Linux 允许将命令执行结果重定向3到一个文件,本应显示在终端上的内容保存到指定文件中。如:ls >
test.txt ( test.txt 如果不存在,则创建,存在则覆盖其内容 )。
2、重定向>>
>>这个是将输出内容追加到目标文件中。如果文件不存在,就创建文件;如果文件存在,则将新的
内容追加到那个文件的末尾,该文件中的原有内容不受影响

5 软连接和硬链接的区别?
软连接类似 Windows 的快捷方式,当删除源文件时,那么软链接也失效了。硬链接可以理解为源文件的一个别名,多个别名所代表的是同一个文件。当 删除 一个文件的时候,那么此文件的硬链接数减1,当硬链接数为 0 的时候,文件被删除。


6.10个常用的Linux命令?
pwd 显示工作路径
ls 查看目录中的文件
cd /home 进入 '/ home' 目录'
cd .. 返回上一级目录
cd ../.. 返回上两级目录
mkdir dir1 创建一个叫做 'dir1' 的目录'
rm -f file1 删除一个叫做 'file1' 的文件',-f 参数,忽略不存在的文件,从不给出提示。
rmdir dir1 删除一个叫做 'dir1' 的目录'
groupadd group_name 创建一个新用户组
groupdel group_name 删除一个用户组
tar -cvf archive.tar file1 创建一个非压缩的 tarball
tar -cvf archive.tar file1 file2 dir1 创建一个包含了 'file1', 'file2' 以及 'dir1'的档案文件
tar -tf archive.tar 显示一个包中的内容
tar -xvf archive.tar 释放一个包
tar -xvf archive.tar -C /tmp 将压缩包释放到 /tmp 目录下
tar -cvfj archive.tar.bz2 dir1 创建一个 bzip2 格式的压缩包
tar -xvfj archive.tar.bz2 解压一个 bzip2 格式的压缩包
tar -cvfz archive.tar.gz dir1 创建一个 gzip 格式的压缩包
tar -xvfz archive.tar.gz 解压一个 gzip 格式的压缩包

7.说一下字典和 json 的区别?
字典是一种数据结构,json 是一种数据的表现形式,字典的 key 值只要是能 hash 的就行,json 的必须是字符串。

8 什么是可变、不可变类型?
可变不可变指的是内存中的值是否可以被改变,不可变类型指的是对象所在内存块里面的值不可以改变,有数值、字符串、元组;可变类型则是可以改变,主要有列表、字典。


9. 列表
list:是 Python 中使用最频繁的数据类型,在其他语言中通常叫做数组,通过索引进行查找
列表是有序的集合。
应用场景:定义列表使用 [ ] 定义,数据之间使用 “,”分割。
列表的索引从 0 开始:索引就是数据在列表中的位置编号,索引又可以被称为下标。
列表的常用操作:
1)增加
列表名.insert(index, 数据):在指定位置插入数据(位置前有空元素会补位)。
列表名.append(数据):在列表的末尾追加数据(最常用的方法)。
列表.extend(Iterable):将可迭代对象中的元素追加到列表。
2)取值和修改
索引取值:列表名[index] :根据下标来取值。
数据:修改指定索引的数据。
1.In [22]: name_list[0] = "Sasuke"
3)删除
del 列表名[index]:删除指定索引的数据。
列表名.remove(数据):删除第一个出现的指定数据。
列表名.pop():删除末尾的数据,返回值: 返回被删除的元素。
列表名.pop(index):删除指定索引的数据,返回被删除的元素。
列表名.clear():清空整个列表的元素。
4)排序
列表名.sort():升序排序 从小到大。
列表名.sort(reverse=True):降序排序 从大到小。
列表名.reverse():列表逆序、反转。
5)统计相关
len(列表名):得到列表的长度。
列表名.count(数据):数据在列表中出现的次数。
列表名.index(数据):数据在列表中首次出现时的索引,没有查到会报错。
if 数据 in 列表: 判断列表中是否包含某元素。

10. 集合
set:set 集合,在 Python 中的书写方式的{},集合与之前列表、元组类似,可以存储多个数据,但
是这些数据是不重复的。集合对象还支持 union(联合), intersection(交), difference(差)和
sysmmetric_difference(对称差集)等数学运算.

 


集合A和B
快速去除列表中的重复元素 set(A)
交集:共有的部分 ( A & B )
并集:总共的部分 A | B
差集:另一个集合中没有的部分 B - A
对称差集(在 a 或 b 中,但不会同时出现在二者中) A ^ B

二.内存管理与垃圾回收机制

1. Python 的内存管理机制及调优手段?
内存管理机制:引用计数、垃圾回收、内存池。
引用计数:
引用计数是一种非常高效的内存管理手段, 当一个 Python 对象被引用时其引用计数增加 1, 当
其不再被一个变量引用时则计数减 1. 当引用计数等于 0 时对象被删除。
垃圾回收:
1.引用计数
引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。当 Python 的某个对象的引用计数降为0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为 1。如果引用被删除,对象的引用计数为 0,那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了
2.标记清除
如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计数为 0。所以先将循环引用摘掉,就会得出这两个对象的有效计数。

3. 分代回收
从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额外操作。
举个例子:
当某些内存块 M 经过了 3 次垃圾收集的清洗之后还存活时,我们就将内存块 M 划到一个集合
A 中去,而新分配的内存都划分到集合 B 中去。当垃圾收集开始工作时,大多数情况都只对集合 B 进行垃圾回收,而对集合 A 进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合 B 中的某些内存块由于存活时间长而会被转移到集合 A 中,当然,集合 A 中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
内存池:
1. Python 的内存机制呈现金字塔形状,-1,-2 层主要有操作系统进行操作;
2. 第 0 层是 C 中的 malloc,free 等内存分配和释放函数进行操作;
3. 第 1 层和第 2 层是内存池,有 Python 的接口函数 PyMem_Malloc 函数实现,当对象小于
256K 时有该层直接分配内存;
4. 第 3 层是最上层,也就是我们对 Python 对象的直接操作;
Python 在运行期间会大量地执行 malloc 和 free 的操作,频繁地在用户态和核心态之间进行切
换,这将严重影响 Python 的执行效率。为了加速 Python 的执行效率,Python 引入了一个内存池
机制,用于管理对小块内存的申请和释放。
Python 内部默认的小块内存与大块内存的分界点定在 256 个字节,当申请的内存小于 256 字节
时,PyObject_Malloc 会在内存池中申请内存;当申请的内存大于 256 字节时,PyObject_Malloc 的
行为将蜕化为 malloc 的行为。当然,通过修改 Python 源代码,我们可以改变这个默认值,从而改
变 Python 的默认内存管理行为。
调优手段(了解)
1.手动垃圾回收
2.调高垃圾回收阈值
3.避免循环引用(手动解循环引用和使用弱引用)
2.内存泄露是什么?如何避免?
指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。
有 __del__() 函数的对象间的循环引用是导致内存泄漏的主凶。
不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。
通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。
可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存
泄漏。


11 . Python 函数调用的时候参数的传递方式是值传递还是引用传递?
Python 的参数传递有:位置参数、默认参数、可变参数、关键字参数。
函数的传值到底是值传递还是引用传递,要分情况:
不可变参数用值传递: ( 元组 和 字符串 )
像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象
可变参数是引用传递的:( 列表 , 字典 )
比如像列表,字典这样的对象是通过引用传递、和 C 语言里面的用指针传递数组很相似,可变对象能在函数内部改变


12 .对缺省参数的理解 ?
缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时,所传入的参数会替代默认参数。
*args 是不定长参数,他可以表示输入参数是不确定的,可以是任意多个。
**kwargs 是关键字参数,赋值的时候是以键 = 值的方式,参数是可以任意多对在定义函数的时候
不确定会有多少参数会传入时,就可以使用两个参数。

13为什么函数名字可以当做参数用?
Python 中一切皆对象,函数名是函数在内存中的空间,也是一个对象。
14 Python 中 pass 语句的作用是什么?
在编写代码时只写框架思路,具体实现还未编写就可以用 pass 进行占位,使程序不报错,不会进行任何操作

15 map 函数和 reduce 函数?
1从参数方面来讲:
map()包含两个参数,第一个参数是一个函数,第二个是序列(列表 或元组)。其中,函数(即 map
的第一个参数位置的函数)可以接收一个或多个参数。
reduce()第一个参数是函数,第二个是序列(列表或元组)。但是,其函数必须接收两个参数。
2从对传进去的数值作用来讲:
map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。
reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算)。

16 递归函数停止的条件?
递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择
是继续调用自身,还是 return;返回终止递归。
终止的条件:
1. 判断递归的次数是否达到某一限定值
2. 判断运算的结果是否达到某个范围等,根据设计的目的来选择

17 Python 主要的内置数据类型都有哪些?
内建类型:布尔类型、数字、字符串、列表、元组、字典、集合;

18 什么是 lambda 函数? 有什么好处?
lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数
1、lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,
连名字都很随意的情况下;
2、匿名函数,一般用来给 filter, map 这样的函数式编程服务;
3、作为回调函数,传递给某些应用,比如消息处理
lambda 函数是匿名函数;使用 lambda 函数能创建小型匿名函数。这种函数得名于省略了用 def
声明函数的标准步骤;

19 装饰器
装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外,功能,装饰器的返回值也是一个函数对象。
语法:
def 装饰器名称(func):
def wrapper(*args,**kwargs):
新添加的功能
return func(*args,**kwargs)
return wrapper

20 解释一下什么是闭包?
在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。

21函数装饰器有什么作用?
装饰器本质上是一个 Python 函数,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。 比如:插入日志、性能测试、事务处理、缓存、权限的校验等场景 ,有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。
22 生成器、迭代器的区别?
迭代器是一个更抽象的概念,任何对象,如果它的类有 next 方法和 iter 方法返回自己本身,对于 string、list、dict、tuple 等这类容器对象,使用 for 循环遍历是很方便的。在后台 for 语句对容器对象调用 iter()函数,iter()是 python 的内置函数。iter()会返回一个定义了 next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()也是 python 的内置函数。在没有后续元素时,next()会抛出一个 StopIteration 异常。
生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用 yield 语句。每次 next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)
区别:生成器能做到迭代器能做的所有事,而且因为自动创建了 iter()和 next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。

23 Python 中 yield 的用法?
yield 就是保存当前程序执行状态。你用 for 循环的时候,每次取一个元素的时候就会计算一次。用 yield 的函数叫 generator,和 iterator 一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间。generator每次计算需要上一次计算结果,所以用 yield,否则一 return,上次计算结果就没了。


24 Python 中的可变对象和不可变对象?
不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。
Python 中,数值类型(int 和 float)、字符串 str、元组 tuple 都是不可变类型。而列表 list、字典dict、集合set 是可变类型

 

25 Python 中 is 和==的区别?
is 判断的是 a 对象是否就是 b 对象,是通过 id 来判断的。是通过是否是同一个内存地址来判断的
==判断的是 a 对象的值是否和 b 对象的值相等,是通过 value 来判断的。
26 面向对象中怎么实现只读属性?
将对象私有化,通过共有方法提供一个读取数据的接口。
class MyCls(object):
__weight = 50
@property #以访问属性的方式来访问 weight 方法
def weight(self):
return self.__weight

27 谈谈你对面向对象的理解?
面向对象是相对于面向过程而言的。面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法;而面向对象是一种基于结构分析的、以数据为中心的程序设计思想。在面向对象语言中有一个有很重要东西,叫做类。面向对象有三大特性:封装、继承、多态。

28. Python 里 match 与 search 的区别?
match()函数只检测 RE 是不是在 string 的开始位置匹配,search()会扫描整个 string 查找匹配;也就是说 match()只有在 0 位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回 none。

 

 


29. TCP 总结
TCP 客户端的创建流程:
1.创建 TCP 的 socket 套接字
2.连接服务器
3.发送数据给服务器端
4.接收服务器端发送来的消息
5.关闭套接字
TCP客户端代码
def main():
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 1、创建客户端的 socket
server_ip = input("请输入服务器端的 IP 地址:")
server_port = int(input("请输入服务器端的端口号:"))
client_socket.connect((server_ip, server_port)) # 3、连接服务器,参数:元组类型
client_socket.send(send_data.encode("gbk")) #send_data = "我是要发送给服务器端的数据"
recv_date= client_socket.recv(1024) #接收服务器端恢复的消息, 没有消息会阻塞
print("接收到的数据是:", recv_date.decode('gbk'))
client_socket.close()# 6、关闭套接字
端口号是整型
if __name__ == '__main__':
main()
TCP 服务器端的创建流程
1.创建 TCP 服务端的 socket
2.bind 绑定 ip 地址和端口号
3.listen 使套接字变为被动套接字
4.accept 取出一个客户端连接,用于服务
5.recv/send 接收和发送消息
6.关闭套接字
import socket
def main():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建 tcp 服务端的 socket
server_socket.bind((“服务端地址”, 服务端端口号))
server_socket.listen(128) #listen 监听套接字
client_socket, client_addr = server_socket.accept() #专门新建一个套接子用来和客户端连接
recv_data = client_socket.recv(1024) #接收客户端发来的消息
print("接收到客户端%s 的数据:%s" % (str(client_addr), recv_data.decode('gbk')))
client_socket.send("收到消息".encode('gbk')) #回复数据给客户端
client_socket.close() #关闭套接字
server_socket.close()
if __name__ == '__main__':
main()

3. 怎么实现强行关闭客户端和服务器之间的连接?
在 socket 通信过程中不断循环检测一个全局变量(开关标记变量),一旦标记变量变为关闭,则 调用 socket 的 close 方法,循环结束,从而达到关闭连接的目的。

4. 简述 TCP 和 UDP 的区别以及优缺点?
UDP 是面向无连接的通讯协议,UDP 数据包括目的端口号和源端口号信息。
优点:UDP 速度快、操作简单、要求系统资源较少,由于通讯不需要连接,可以实现广播发送
缺点:UDP 传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道 数据是否会正确接收,也不重复发送,不可靠。
TCP 是面向连接的通讯协议,通过三次握手建立连接,通讯完成时四次挥手
优点:TCP 在数据传递时,有确认、窗口、重传、阻塞等控制机制,能保证数据正确性,较为可靠。
缺点:TCP 相对于 UDP 速度慢一点,要求系统资源较多。
什么是TCP粘包问题?
TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。

 

 


UDP
使用 udp 发送/接收数据步骤:
1.创建客户端套接字
2.发送/接收数据
3.关闭套接字
创建客户端UDP代码
import socket
def main():
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建 udp 套接字
dest_addr = ('服务器ip地址', 端口号)
udp_socket.sendto(send_data.encode("utf-8"), dest_addr) #send_data为要发送的数据
recv_data, addr = udp_socket.recvfrom(1024) #等待接收方发送的数据 如果没有收到数据则会阻 udp_socket.close() #关闭套接字 塞等待, # 接收到的数据是一个元组
(接收到的数据, 发送方的 ip 和端口

UDP 服务端
1.创建 socket 套接字
2.绑定端口号
3.接收/发送数据
4.关闭套接字
import socket
def main():
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #创建 udp 套接字
local_addr = ('', 7777)
udp_socket.bind(local_addr)
dest_addr = ('192.168.113.111', 8888)
recv_data, addr = udp_socket.recvfrom(1024)
send_data = "我是要发送的数据"
udp_socket.sendto(send_data.encode("utf-8"), dest_addr)
udp_socket.close()关闭套接字336

 

 

 


30 cookie 和 session 的区别?
1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。
2、cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗考虑到安全应当使用 session。
3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用服务器的性能考虑到减 轻服务器性能方面,应当使用 cookie。
4、单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。
5、建议: 将登陆信息等重要信息存放为 SESSION 其他信息如果需要保留,可以放在 cookie 中
31.请简单说一下三次握手和四次挥手?
三次握手过程:
1 首先客户端向服务端发送一个带有 SYN 标志,以及随机生成的序号 100(0 字节)的报文
2 服务端收到报文后返回一个报文(SYN200(0 字节),ACk1001(字节+1))给客户端
3 客户端再次发送带有 ACk 标志 201(字节+)序号的报文给服务端
至此三次握手过程结束,客户端开始向服务端发送数据。

1 客户端向服务端发起请求:我想给你通信,你准备好了么?
2 服务端收到请求后回应客户端:I'ok,你准备好了么
3 客户端礼貌的再次回一下客户端:准备就绪,咱们开始通信吧!
整个过程跟打电话的过程一模一样:1 喂,你在吗 2 在,我说的你听得到不 3 恩,听得到(接下来请
开始你的表演)

四次挥手过程:
由于 TCP 连接是可以双向通信的(全双工),因此每个方向都必须单独进行关闭(这句话才是
精辟,后面四个挥手过程都是其具体实现的语言描述)
四次挥手过程,客户端和服务端都可以先开始断开连接

1 客户端发送带有 fin 标识的报文给服务端,请求通信关闭
2 服务端收到信息后,回复 ACK 答应关闭客户端通信(连接)请求
3 服务端发送带有 fin 标识的报文给客户端,也请求关闭通信
4 客户端回应 ack 给服务端,答应关闭服务端的通信(连接)请求


32.为什么客户端在 TIME-WAIT 状态必须等待 2MSL 的时间?
1、 为了保证客户端发送的最后一个 ACK 报文段能够达到服务器。 这个 ACK 报文段可能丢失,因而使处在 LAST-ACK 状态的服务器收不到确认。服务器会超时重传 FIN+ACK 报文段,客户端就能在 2MSL 时间内收到这个重传的 FIN+ACK 报文段,接着客户端重传一次确认,重启计时器。最好,客户端和服务器都正常进入到 CLOSED 状态。如果客户端在 TIME-WAIT 状态不等待一段时间,而是再发送完 ACK 报文后立即释放连接,那么就无法收到服务器重传的 FIN+ACK 报文段,因而也不会再发送一次确认报文。这样,服务器就无法按照正常步骤进入 CLOSED 状态。
2、防止已失效的连接请求报文段出现在本连接中。客户端在发送完最后一个 ACK 确认报文段
后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。


34.说说 HTTP 和 HTTPS 区别?
HTTP 协议传输的数据都是未加密的,也就是明文的,因此使用 HTTP 协议传输隐私信息非常不安全,HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比 http 协议安全。
HTTPS 和 HTTP 的区别主要如下:
1、http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。
2、http 和 https使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
3、http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、
身份认证的网络协议,比 http 协议安全

35.网络协议种类
1.1 国际标准化组织ISO发布的OSI 7层协议模型(即OSI开放式互联参考模型),是概念性模型。
1.2 TCP/IP是一种实践类的模型,已成为行业标准。含义是:在使用ip进行通信时,所需要用到的协议群的统称。

1.3 区别
OSI协议模型,更注重通信协议必要的功能是什么?
TCP/IP模型,更注重实现协议,应该开发哪种程序?

1.3.1 应用层:为应用程序提供服务并规定应用程序中通信相关的细节;
1.3.2 表示层:将应用处理的信息转换为适合网络传输的格式,或将来自下一层的数据转换为上层能够处理的格式;主要负责数据格式的转换,确保一个系统的应用层信息可被另一个系统应用层读取具体来说,就是将设备固有的数据格式转换为网络标准传输格式,不同设备对同一比特流解释的结果可能会不同;因此,主要负责使它们保持一致
1.3.3 会话层:负责建立和断开通信连接(数据流动的逻辑通路),记忆数据的分隔等数据传输相关的管理

1.3.4 传输层:只在通信双方的节点上(比如计算机终端)进行处理,而无需在路由器上处理,传输层是OSI中最重要、最关键的一层,是唯一负责总体的数据传输和数据控制的一层;传输层提供端到端的交换数据的机制,检查分组编号与次序,传输层对其上三层如会话层等,提供可靠的传输服务,对网络层提供可靠的目的地站点信息主要功能在这一层,数据的单位称为数据段(segment)
1.3.5 网络层:将数据传输到目标地址;目标地址可以使多个网络通过路由器连接而成的某一个地址,主要负责寻找地址和路由选择,网络层还可以实现拥塞控制、网际互连等功能.
1.3.6 数据链路层:负责物理层面上的互联的、节点间的通信传输(例如一个以太网项链的2个节点之间的通信);该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。

进程总结
进程:程序运行在操作系统上的一个实例,就称之为进程。进程需要相应的系统资源:内存、时间片、pid。
创建进程:
1.首先要导入 multiprocessing 中的 Process;
2.创建一个 Process 对象;
3.创建 Process 对象时,可以传递参数;
1.p = Process(target=XXX, args=(元组,) , kwargs={key:value})
2.target = XXX 指定的任务函数,不用加()
3.args=(元组,) , kwargs={key:value} 给任务函数传递的参数
4.使用 start()启动进程;
5.结束进程。
Process 语法结构:
Process([group [, target [, name [, args [, kwargs]]]]])
process参数
target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码-----只需要传递函数名即可
args:给 target 指定的函数传递的参数,以元组的形式进行传递
kwargs:给 target 指定的函数传递参数,以字典的形式进行传递
name:给进程设定一个名字,可以省略
group:指定进程组,大多数情况下用不到
Process 创建的实例对象的常用方法有:
start():启动子进程实例(创建子进程)
is_alive():判断进程进程是否还在活着
join(timeout):是否等待子进程执行结束,或者等待多少秒
terminate():不管任务是否完成,立即终止子进程
Process 创建的实例对象的常用属性:
name:当前进程的别名,默认为 Process-N,N 为从 1 开始递增的整数
pid:当前进程的 pid(进程号)


使用进程代码
import os
from multiprocessing import Process
import time
def pro_func(name, age, **kwargs):
for i in range(5):
print("子进程正在运行中,name=%s, age=%d, pid=%d" %(name, age, os.getpid()))
print(kwargs)
time.sleep(0.2)
if __name__ == '__main__':
p = Process(target=pro_func, args=('小明',18), kwargs={'m': 20}) # 创建 Process 对象
p.start() #启动进程
time.sleep(1) # 1 秒钟之后,立刻结束子进程
p.terminate() #不管任务是否完成,立即终止子进程
p.join() #是否等待子进程执行结束,或者等待多少秒
注意:进程间不共享全局变量。


进程之间的通信-Queue
在初始化 Queue()对象时,(例如 q=Queue(),若在括号中没有指定最大可接受的消息数量,或数
量为负值时,那么就代表可接受的消息数量没有上限-直到内存的尽头)
Queue.qsize():返回当前队列包含的消息数量。
Queue.empty():如果队列为空,返回 True,反之 False。
Queue.full():如果队列满了,返回 True,反之 False。
Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block 默认值为True。
如果 block 使用默认值,且没有设置 timeout(单位秒),消息列队如果为空,此时程序将被阻塞
(停在读取状态),直到从消息列队读到消息为止,如果设置了 timeout,则会等待 timeout 秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;如果 block 值为 False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;Queue.get_nowait():相当 Queue.get(False);Queue.put(item,[block[, timeout]]):将 item 消息写入队列,block 默认值为 True;如果 block 使用默认值,且没有设置 timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了 timeout,则会等待timeout 秒,若还没空间,则抛出"Queue.Full"异常;如果 block 值为 False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;Queue.put_nowait(item):相当 Queue.put(item, False);

 

 

 

 

 

 

 


使用消息队列代码Queue
from multiprocessing import Process, Queue
import os, time, random
def write(q): # 写数据进程执行的代码:
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
def read(q): # 读数据进程执行的代码:
while True:
if not q.empty():
value = q.get(True)
print('Get %s from queue.' % value)
time.sleep(random.random())
else:
break
if __name__=='__main__':
q = Queue() # 父进程创建 Queue,并传给各个子进程:
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()# 启动子进程 pw,写入:
pw.join()# 等待 pw 结束:
pr.start() # 启动子进程 pr,读取:
pr.join()
print('')# pr 进程里是死循环,无法等待其结束,只能强行终止:
print('所有数据都写入并且读完')

 

 

使用进程池pool进行通信
from multiprocessing import Poolimport os, time, random
def worker(msg):
t_start = time.time()
print("%s 开始执行,进程号为%d" % (msg,os.getpid()))
time.sleep(random.random()*2)
t_stop = time.time()
print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start))
po = Pool(3) # 定义一个进程池,最大进程数 3
for i in range(0,10):
po.apply_async(worker,(i,))# 每次循环将会用空闲出来的子进程去调用目标
print("----start----")
po.close() # 关闭进程池,关闭后 po 不再接收新的请求
po.join()
print("-----end-----") # 等待 po 中所有子进程执行完成,必须放在 close 语句之后

36谈谈你对多进程,多线程,以及协程的理解,项目是否用?
这个问题被问的概率相当之大,其实多线程,多进程,在实际开发中用到的很少,除非是那些对项
目性能要求特别高的,有的开发工作几年了,也确实没用过,你可以这么回答,给他扯扯什么是进程,线程(cpython 中是伪多线程)的概念就行,实在不行你就说你之前写过下载文件时,用过多线程技术,或者业余时间用过多线程写爬虫,提升效率。
进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最
小单位,进程拥有自己独立的内存空间,所以进程间数据不共享,开销大。
线程: 调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在一个进程至少有一个
线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。


37. 什么是多线程竞争?
线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱 ,即所谓的线程不安全
那么怎么解决多线程竞争问题?-- 锁。
锁的好处:
确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
锁的致命问题:死锁。
38. 解释一下什么是锁,有哪几种锁?
锁(Lock)是 Python 提供的对线程控制的对象。有、可重入锁、死锁。

39. 什么是死锁呢?
若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,
互相干等着,程序无法执行下去,这就是死锁。
GIL 锁 全局解释器锁(只在 cpython 里才有)
作用:限制多线程同时执行,保证同一时间只有一个线程执行,所以 cpython 里的多线程其实是伪多线程!所以 Python 里常常使用协程技术来代替多线程,协程是一种更轻量级的线程,
进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块 gevent 下切换是遇到了耗时操作才会切换。
三者的关系:进程里有线程,线程里有协程
40. 说说下面几个概念:同步,异步,阻塞,非阻塞?
同步:多个任务之间有先后顺序执行,一个执行完下个才能执行。
异步:多个任务之间没有先后顺序,可以同时执行有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调!
阻塞:如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。
非阻塞:如果不会卡住,可以继续执行,就是说非阻塞的。
同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。
41. 什么是僵尸进程和孤儿进程?怎么避免僵尸进程?
孤儿进程:父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
僵尸进程:进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。
避免僵尸进程的方法:
1.fork 两次用孙子进程去完成子进程的任务;
2.用 wait()函数使父进程阻塞;
3.使用信号量,在 signal handler 中调用 waitpid,这样父进程不用阻塞。
42 Python 中的进程与线程的使用场景?
多进程适合在 CPU 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)。
多线程适合在 IO 密集型操作(读写数据操作较多的,比如爬虫)

43 线程是并发还是并行,进程是并发还是并行?
线程是并发,进程是并行;
进程之间相互独立,是系统分配资源的最小单位,同一个线程中的所有线程共享资源。

44.并行(parallel)和并发(concurrency)?
并行:同一时刻多个任务同时在运行。
并发:在同一时间间隔内多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情况。
实现并行的库有:multiprocessing
实现并发的库有:threading
程序需要执行较多的读写、请求和回复任务的需要大量的 IO 操作,IO 密集型操作使用并发更好。
CPU 运算量大的程序程序,使用并行会更好。
12.IO 密集型和 CPU 密集型区别?
IO 密集型:系统运作,大部分的状况是 CPU 在等 I/O (硬盘/内存)的读/写。
CPU 密集型:大部份时间用来做计算、逻辑判断等 CPU 动作的程序称之 CPU 密集型。



这篇关于面试的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程