面试题汇总
准备找工作了,在网上搜八股文背太杂,自己汇总一下。
一、 Python相关
1. Python中的数据类型
- 整型(Integers)
- 浮点型(Floats)
- 字符串(Strings)
- 列表(Lists)
- 元组(Tuples)
- 字典(Dictionaries)
- 集合(Sets)
2. 可变和不可变数据类型
- 可变类型:当对象的值发生变化,但内存地址没有发生变化时,则说明是可变对象。
- 列表
- 字典
- 集合
- 不可变类型:当对象的值发生变化时,但内存地址也发生改变时,则说明是不可变类型。
- 整型
- 浮点型
- 字符串
- 元组
3. 推导式
一种简洁的方式来创建序列,有包括列表、字典、生成器。
- 列表
nums = [i for i in range(1,10)] # [1,2,3,4,5,6,7,8,9,10]
nums = [i for i in range(1,10) if i % 2 == 0] # [2,4,6,8]
- 生成器
对于一个比较大的数,用生成器更节省内存。
nums = (i for i in range(1,10))
print(type(nums)) # <class 'generator'>
- 字典
nums = ['零','壹','贰','參']
data = {key:value for key,value in enumerate(nums)}
data # {0: '零', 1: '壹', 2: '贰', 3: '參'}
4. 浅拷贝、深拷贝和赋值
- 浅拷贝
我的理解就是复制了对象本身,如果对象还包含深层的可变对象,那么还是用的是同一个引用,比如列表套列表。
list1 = [1,2,3,[4,5,'初始值']]
list2 = list1.copy()
list1.append(6) # 不会影响list2
print(list1) # [1, 2, 3, [4, 5, '初始值'], 6]
print(list2) # [1, 2, 3, [4, 5, '初始值']]
list1[3][2] = '修改'
print(list1) # [1, 2, 3, [4, 5, '修改'], 6]
print(list2) # [1, 2, 3, [4, 5, '修改']]
- 深拷贝
不仅复制了对象本身,还递归复制了对象所包含的对象,完全独立的副本。
import copy
list1 = [1,2,3,[4,5,'初始值']]
list2 = copy.deepcopy(list1)
list1.append(6)
print(list1) # [1, 2, 3, [4, 5, '初始值'], 6]
print(list2) # [1, 2, 3, [4, 5, '初始值']]
list1[3][2] = '修改' # 不会影响list2
print(list1) # [1, 2, 3, [4, 5, '修改'], 6]
print(list2) # [1, 2, 3, [4, 5, '初始值']]
- 赋值
指向这个对象的引用地址,是完全相同的,只是名字不一样。
list1 = [1,2,3,[4,5,'初始值']]
list2 = list1
list1.append(6) # 影响list2
print(list1) # [1, 2, 3, [4, 5, '初始值'], 6]
print(list2) # [1, 2, 3, [4, 5, '初始值'], 6]
list1[3][2] = '修改'
print(list1) # [1, 2, 3, [4, 5, '修改'], 6]
print(list2) # [1, 2, 3, [4, 5, '修改'], 6]
5. is和 ==
- is比较的是对象的内存地址是否一致,可以通过id()查看。
- ==比较的时对象的值是否一致。
6. 闭包
可以简单地理解为一个函数内部定义的函数,内部函数不仅可以使用自己的局部变量,还可以使用外部函数的局部变量。闭包的特点是即使外部函数的执行已经结束,闭包内部函数仍然可以访问外部函数的局部变量。闭包必须满足的条件:
- 必须有一个内部函数
- 内部函数使用了外部函数的变量
- 外部函数返回值是内部函数
def people(name):
def say(val):
print(f'{name}说:{val}')
return say
p = people('Mumu')
p('hi') # Mumu说:hi
p('你好') # Mumu说:你好
7. 装饰器
和闭包有点像。装饰器本质上还是个函数,接受一个函数作为参数,在不改变原来函数的代码基础上给函数增加功能,通过@语法糖使用。可以用在日志记录、性能测试、缓存等。
from functools import wraps
def log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if kwargs.get('name') != 'admin':
raise KeyError('没有权限访问')
else:
result = func(*args, **kwargs)
return result
return wrapper
@log_decorator
def greet(name):
"""文档"""
print( f"Hello, {name}!")
t = greet(name='admin')
装饰器的缺点就是会导致原函数的函数名和函数文档丢失,上面的代码中如果没有加@wraps(func)
那么greet
函数名和文档就变成wrapper
函数的了。
8. GIL
全局解释器锁,是CPython解释器中的一个机制,用于限制解释器在任何给定时刻只能在一个线程中执行Python字节码。
- 影响
对于I/O密集型任务,GIL影响不大,因为线程大部分时间都在等待外部I/O操作执行完成。对于CPU密集型任务,GIL限制了多线程的效率,因为只能有一个线程在执行,这就导致多线程程序在多核心处理器上不能充分利用硬件资源。
- 解决
1.多进程代替多线程。Python的multiprocessing模块可以创建多个进程,每个进程有自己的Python解释器和内存空间,不受GIL的限制。
2.使用其他解释器:如Jython或IronPython,这些解释器没有GIL。
9. Python的内存管理和垃圾回收
Python提供了自动化的内存管理,也就是说内存空间的分配与释放都是由Python解释器在运行时自动进行的,在一定程度上解决内存泄露的问题。Python内存管理有三个关键点:引用计数、标记清理、分代收集。它的内部有一个名为ob_refcnt
的引用计数器成员变量
- 引用计数
Python中的每一个对象其实就是PyObject
结构体,它的内部有一个引用计数器变量,当对象的引用计数值为0时,它的内存就会被释放掉。当两个对象互相引用就会产生循环引用的问题,它们的引用计算永远不会为0,会导致内存泄露的问题。就需要用其他垃圾回收算法处理。
class Obj(object):
pass
A= Obj()
B = Obj()
A.ref = B
B.ref = A
- 标记清理
标记阶段,遍历所有的对象,如果对象是可达的(被其他对象引用),那么就标记该对象为可达;清除阶段,再次遍历对象,如果发现某个对象没有标记为可达,则就将其回收。
- 分代回收
核心思想是:对象存在的时间越长,是垃圾的可能性就越小,应该尽量不对这样的对象进行垃圾回收。Python中对象被分配到三个“世代”中,基于它们存活时间的长短。新创建的对象被放入第一世代,如果它们在一次收集后仍然存活,它们会被移动到第二世代,以此类推。第一世代的对象会更频繁地被检查,因为它们更有可能是短暂的,这样可以减少垃圾收集的开销。
10. *args和**kwargs
python中的函数参数有位置参数、关键字参数和默认参数。*args可变位置参数,**kwargs是可变关键字参数。当不知道参数数量时可以用这两个,接收参数后*args为元组,**kwargs为字典。
def fun(a,*args,**kwargs):
print(a) # 1
print(args) # (2,3)
print(kwargs) # {'A': 4, 'B': 5}
fun(1,2,3,A=4,B=5)
11. 可迭代对象、迭代器和生成器
- 可迭代对象
可迭代对象支持迭代操作,简单来说就是可以通过 for 循环来遍历其元素。Python中的可迭代对象,都需要实现__iter__方法,该方法返回一个迭代器对象。
- 迭代器
迭代器是实现了迭代器协议的对象,在Python中一个类实现了__next__和__iter__这两个魔法方法就表示该类是个迭代器对象。__iter__方法返回迭代器对象本身,__next__ 方法返回当前元素,并移到下一个元素。如果没有更多元素,它会引发 StopIteration 异常。
- 生成器
生成器是一种特殊类型的迭代器,在函数中用yield关键字实现迭代器。
# 迭代器实现range函数
class Myrange:
def __init__(self,start,end,setp=1):
self.start = start
self.end = end
self.setp = setp
def __iter__(self):
return self
def __next__(self):
if self.start < self.end:
temp = self.start
self.start += self.setp
return temp
raise StopIteration
for i in Myrange(1,10,2):
print(i)
# 生成器实现range函数
def Myrange(start, end, step=1):
while start < end:
tmp = start
yield tmp
start += step
for i in Myrange(1, 10):
print(i)
12. 一些常见的魔术方法
- __new__
静态方法,实例创建之前被调用,主要任务是创建并返回该实例,可以在实例被初始化之前做一些操作,比如实现单例模式。
- __init__
实例对象创建后立即调用,初始化实例对象的属性。
- __getattr__
访问类中不存在的属性时被调用。
- __getattribute__
访问类中的属性时被调用,不管存不存在,属性的值最终是这个方法的返回值。
- __setattr__
修改类中的属性时被调用。
- __delattr__
删除类中的属性被调用。
13. with 上下文管理器
with语句包裹的代码块,通过上下文管理器来自动处理资源的分配和释放。经常用于文件操作、数据库连接。上下文管理器在类中必须实现__enter__ 和 __exit__ 这两个魔术方法。__enter__ 方法在进入 with 代码块时被调用,__exit__ 方法在离开 with 代码块时被调用。
class MyResource:
def __init__(self,filename):
self.filename = filename
def __enter__(self):
print(f"打开文件:{self.filename}")
return self # 返回的对象会赋值给 as 子句后的变量
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print(f'出现异常:{exc_val}')
return True # 如果处理了异常,返回 True
print('关闭文件')
return False # 返回 False,如果有异常发生,允许异常向外传播
def do_something(self):
print("操作文件")
raise FileNotFoundError('文件不存在')
# 使用自定义的上下文管理器
with MyResource('A.txt') as resource:
resource.do_something()
13. lambda
lambda arguments: expression
“arguments”是一个或多个参数(用逗号分隔)
“expression”是关于这些参数的表达式,它的计算结果就是函数的返回值。
匿名函数,用一个表达式来实现一个函数功能,通常用于需要函数作为参数的高阶函数上面,比如map,filter、sorted。
nums = [1, 2, 3, 4]
doubled = map(lambda x: x * 2, nums)
print(list(doubled)) # 输出: [2, 4, 6, 8]
14. 单下划线和双下划线
_name:一种约定,用来指定变量私有,只能在类中访问,但是并不强制,还是可以在外部访问到。
__name:也可以实现属性私有化,会触发python的变量名改写机制,变成_classname__name,避免子类意外重写基类的方法或属性。
15. Python自省
自省是指通过一定的机制查询到对象的内部结构,在python中一切皆对象,可以通过自省机制得到对象的类型内部的属性。
- dir()函数,返回 一个列表,包含对象所有属性和方法。
- id()函数,用于返回一个对象的内存地址。
- type()函数,用于放回一个对象的类型。
- hasattr()函数,用于测试对象是否具有某个属性。
- getattr()函数,用于获取对象某个属性。
- isinstance() , 函数测试对象,以确定它是否是某个特定类型或定制类的实例
16. 异常处理机制
主要是try, except, finally, 和 else语句
- try: 代码执行过程中出现异常,Python会查找对应的except块处理。
- except: 用来捕获try块中发生的异常,并进行处理。可以定义多个except来捕获不同类型的异常。
- finally: 无论try块中是否发生异常,finally块中的代码都会执行。常用于执行清理工作,如关闭文件或数据库连接。
- else: 如果try块没有引发异常,则执行else块中的代码。else块必须位于所有except块之后,并且在finally块之前
17. 进程、线程、协程
- 进程
进程是操作系统分配资源和调度的基本单位。每个进程都有自己独立的内存空间,包括代码、数据段、堆和栈等。
优点:稳定性高,一个进程崩溃不会直接影响其他进程。
缺点:资源消耗较大,上下文切换成本较高,进程间通信相对复杂。
- 线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以包含多个线程,它们共享进程的内存空间,包括数据段、代码段和打开的文件等,但每个线程有自己的堆栈和局部变量。
优点:线程间的数据共享和通信更为简单,上下文切换和资源消耗小于进程。
缺点:稳定性较进程差,一个线程的崩溃可能影响同一进程内的其他线程,线程安全和同步是主要问题。
- 协程
Python 的协程是一种用户态进行并发编程的方法,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,协程的调度完全由应用程序控制,不再需要陷入系统的内核态。
协程允许在单个线程内执行多个函数,这些函数可以挂起和恢复,使得它们能够异步执行。提供了编写非阻塞异步代码的能力,这在处理 I/O 密集型和高级别的结构化网络代码时非常有用。
优点:协程减少了线程的创建和销毁的开销,以及上下文切换的成本,从而提高了性能。
缺点:编程模型比较复杂,需要理解事件循环以及异步编程的概念,且协程的运行效率高度依赖于事件循环的实现
import asyncio
import time
async def read_file(filename, s):
await asyncio.sleep(s) # 模拟耗时
print(f'读取文件{filename}')
async def main():
tasks = [
read_file('A.txt',3),
read_file('B.txt',5),
read_file('C.txt',2),
]
# 并发执行所有任务
await asyncio.gather(*tasks)
start_time = time.time()
asyncio.run(
main()
)
print(f'花费时间{time.time() - start_time}')
上面的代码模拟读取文件操作,每个文件的耗时不一样,由于是并发执行,会先输出:“读取文件C.txt”,因为它的耗时最短,最终的运行时间5秒多一些。如果正常去读的话,运行时间要3+5+2=10秒多。
18. 什么是内存泄露?如何避免?
内存泄露发生在当内存空间被分配给程序中的对象,而该对象在不再被需要后未被释放,导致内存无法被重用。
- 识别内存泄露:首先要确定是否存在内存泄露。这可以通过监控内存使用情况和使用内存分析工具来完成。
- 避免循环引用:循环引用是导致内存泄露的常见原因,尤其是在使用类和复杂数据结构时。
- 合理使用缓存:缓存是另一个常见的内存泄露源头。确保为缓存设置合理的大小限制和到期策略。
- 及时释放资源:在使用完文件、网络连接等资源后,确保及时关闭和释放这些资源。
19. 递归
递归就是函数自己调用自己,解决更小的子问题的部分。递归函数都必须有至少一个停止条件,防止无限递归。
# 递归求阶乘
def factorial(n):
# 停止条件
if n == 0:
return 1
# 子问题
else:
return n * factorial(n - 1)
# 递归求斐波那契
def fb(n):
if n == 0 or n == 1:
return n
else:
return fb(n - 1) + fb(n-2)
20. 面向对象和面向过程
- 面向对象编程:使用“对象”来模拟现实世界中的事物和事物之间的交互,将对象属性和操作数据的方法的集合在一起。
- 封装:将数据(属性)和操作数据的方法(行为)封装在对象内部,隐藏对象的内部细节,只暴露必要的接口给外部使用。
- 继承:允许新创建的类(子类)继承现有类(父类)的属性和方法,支持代码复用。
- 多态:允许不同类的对象对同一消息做出响应,即同一个接口可以被不同的对象以不同的方式实现。
- 面向过程编程:将程序视为一系列的过程或函数调用。每个过程/函数完成一个特定的任务,通过数据结构和算法来解决问题
- 重点关注过程:程序被视为一系列操作数据的函数或过程的集合。程序的逻辑从一系列函数的执行中体现出来。
- 模块化:程序被分解为可以重用的函数或过程,每个模块完成一个具体的子任务。
- 顺序执行:程序按照代码的顺序执行,数据通过参数传递给函数,函数执行完成后可能会返回数据。
二、Django
三、数据库
MySQL可以看看这篇文章,很全。2022年最新【MySQL】八股文汇总(附答案~)
Redis:这可能是最全面的Redis面试八股文了
四、计算机网络
1. 三次握手和四次挥手
三次握手是TCP协议建立连接的过程,确保双方都准备好进行数据传输
- SYN:客户端发送一个SYN(同步序列编号)包到服务器,并进入SYN_SEND状态,等待服务器确认。
- SYN-ACK:服务器接收到SYN包,回复一个SYN-ACK(同步确认)包,进入SYN_RECV状态。
- 客户端收到SYN-ACK包,发送一个ACK(确认)包给服务器,完成连接建立。
四次挥手是TCP协议终止连接的过程,确保双方数据完全传输完成
- FIN:发起关闭连接的一方发送一个FIN(结束)包。
- 接收方收到FIN包后,发送一个ACK包作为确认,并进入CLOSE_WAIT状态。
- 接收方发送一个FIN包,请求关闭连接。
- 发起方收到FIN后,发送一个ACK包作为回应,然后双方完成连接终止。
2. HTTP 和 HTTPS 有什么区别
HTTP(超文本传输协议)和HTTPS(安全超文本传输协议)是用于传输超文本的两种协议。它们在网页浏览和数据传输中扮演着核心角色,但主要区别在于安全性方面。HTTP以明文方式传输数据,数据有可能被截取和查看。HTTPS 使用 SSL/TLS 协议进行加密处理,包括数据加密和身份验证,确保只有客户端和服务器能够理解交换的信息。
3. TCP 和 UDP
- TCP是一种面向连接的、可靠的传输协议。保证了数据的顺序、可靠性和数据完整性。通过三次握手建立连接,四次挥手断开连接。适用于对数据准确性要求高的应用,如Web浏览、电子邮件、文件传输等。
- UDP是一种无连接的协议,提供了简单但是不可靠的消息传输服务。它不保证消息的顺序、可靠性或数据完整性,因此传输速度较快。适用于对实时性要求高的应用,如在线视频会议、实时游戏等。
4. OSI模型
- 物理层:负责在物理媒介上传输原始比特流。电缆、光纤、无线传输介质在这层。
- 数据链路层:负责在相邻节点之间建立、维护和解除数据链路。以太网、Wi-Fi等在这层。
- 网络层:责数据包从源到目的地的传输和路由选择。路由器、IP地址。
- 传输层:负责提供端到端的数据传输服务,确保数据的正确传输。TCP、UDP。
- 会话层:负责在网络中的两个节点之间建立、管理和终止会话。
- 表示层:确保传输的数据可以被接收方理解。数据格式转换、加密、解密。
- 应用层:为应用软件提供网络服务,如文件传输、电子邮件、远程文件服务等。HTTP、FTP
发表评论
共 0 条评论
暂无评论