Python 数据类型详解
1️⃣ 数值类型(Number)
| 类型 | 核心特性 | 可变性 | 常用操作 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
int | 整数类型 | 不可变 | + - * / // % ** | Python 3 长整型不限大小,掌握位运算 | 航班编号、票价整数计算 |
float | 浮点数 | 不可变 | + - * / ** round() | 浮点数精度问题,decimal 高精度计算 | 航班票价、折扣计算 |
complex | 复数 | 不可变 | real, imag | 少用,多考数学理解 | 科学计算(航班优化算法可能用) |
bool | 布尔值 | 不可变 | and, or, not | Python 内部实现为 int 子类 | 条件判断、航班状态标记 |
一、int —— 整数类型(Python 最强的数值类型)
1️⃣ 底层原理
-
Python 3 的
int是:
arbitrary precision integer(任意精度)
-
不存在溢出
-
本质是 多位数组 + 符号位
a = 10
b = a
a += 1
# b 仍然是 10(不可变对象)
📌 不可变 = 每次运算都会创建新对象
2️⃣ 常用 & 易忽略操作
+ - * / // % **
整除陷阱(重要)
-7 // 3 # -3 (向下取整,不是向 0)
3️⃣ 位运算(面试高频)
& # 按位与
| # 按位或
^ # 异或
<< # 左移
>> # 右移
面试点
-
x << 1 == x * 2 -
x & 1判断奇偶
def is_odd(x):
return x & 1
4️⃣ 实战(航班编号 / 计数)
flight_id = 102345
seat_count = 180
remaining = seat_count - booked
二、float —— 浮点数(最容易出 Bug)
1️⃣ 底层原理(非常重要)
-
遵循 IEEE 754
-
二进制无法精确表示大多数十进制小数
0.1 + 0.2 == 0.3 # False
📌 不是 Python 的问题,是 计算机世界的物理限制
2️⃣ 精度问题示例
price = 199.9
discount = 0.1
price * discount # 19.990000000000002
3️⃣ 正确处理金钱:decimal
from decimal import Decimal
price = Decimal("199.9")
discount = Decimal("0.1")
price * discount # 精确
📌 面试加分点:
涉及钱,一定不用 float
4️⃣ round() 陷阱
round(2.5) # 2
round(3.5) # 4
👉 银行家舍入法(round half to even)
5️⃣ 实战(票价、折扣)
final_price = round(price * discount, 2)
三、complex —— 复数(冷门但能加分)
1️⃣ 基本结构
z = 3 + 4j
z.real # 3.0
z.imag # 4.0
2️⃣ 运算支持
(1+2j) * (3+4j)
abs(3+4j) # 模长 = 5
3️⃣ 面试加分点
-
Python 内建支持复数
-
不能比较大小
(1+2j) > (3+4j) # TypeError
4️⃣ 实战(理论场景)
-
信号处理
-
优化算法
-
路径规划数学模型
📌 业务系统很少用,但你知道就是优势
四、bool —— 布尔值(最容易被忽视)
1️⃣ 本质
isinstance(True, int) # True
等价关系
True == 1
False == 0
📌 bool 是 int 的子类
2️⃣ 运算特性
True + True # 2
False * 10 # 0
👉 很多计数逻辑直接利用这一点
3️⃣ 逻辑短路
a and b
a or b
-
and:第一个 False 就停 -
or:第一个 True 就停
user and user.is_active
4️⃣ 真值判断规则
False 的情况
False
0
0.0
''
[]
{}
None
5️⃣ 实战(航班状态)
is_delayed = False
is_cancelled = True
if is_cancelled or is_delayed:
notify_passengers()
2️⃣ 序列类型(Sequence)
| 类型 | 核心特性 | 可变性 | 常用操作 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
list | 有序、可重复 | 可变 | append, extend, insert, pop, remove, sort | 切片 lst[start:end:step],列表推导式 | 航班列表存储、票价列表 |
tuple | 有序、可重复 | 不可变 | index, count | 元组解包,节省内存 | 航班固定信息组合 (航班号, 日期) |
range | 有序、不可重复 | 不可变 | range(start, stop, step) | 延迟生成数字序列,占用内存少 | 航班循环编号生成 |
str | 字符串 | 不可变 | split, join, strip, replace, find | f-string, encode/decode | 航班号、乘客姓名 |
一、list —— 万能容器,但“可变=风险”
1️⃣ 核心原理
-
动态数组
-
连续内存 + 扩容机制(通常 1.125~2 倍)
lst = [1, 2, 3]
lst.append(4) # 原地修改
📌 可变对象,传参极易出 Bug
2️⃣ 高频操作 & 本质
append(x) # O(1) 均摊
extend(iter) # 批量追加
insert(i,x) # O(n)
pop(i) # O(n)
remove(x) # O(n)
sort() # 原地排序
3️⃣ 切片
lst[start:end:step]
⚠️ 切片返回新 list
a = lst[:]
a is lst # False
4️⃣ 列表推导式
a = lst[:]
a is lst # False
📌 可读性 + 性能都优于 for
5️⃣ 实战(航班列表)
flights = []
flights.append("MU1234")
flights.sort()
二、tuple —— 安全、快、适合“结构化数据”
1️⃣ 核心原理
-
不可变对象
-
存储的是 引用不可变
t = (1, [2, 3])
t[1].append(4) # 不报错
📌 不可变 ≠ 内容不可变
2️⃣ 元组解包
flight_no, date = ("MU1234", "2026-01-08")
高级用法:
a, *b, c = (1,2,3,4,5)
3️⃣ 面试加分点
-
比 list 更省内存
[list 对象头]
+ size 当前元素个数
+ allocated 已分配容量(>= size)
+ 指针数组 指向元素的指针[tuple 对象头]
+ size 固定长度
+ 指针数组 正好 size 个
tuple 是定长、不可变序列,
底层结构比 list 简单,
不需要维护扩容容量和多余空间,
所以在 CPython 中比 list 占用更少内存,
同时遍历性能也更好。
-
可作为 dict key(前提:元素可 hash)
d = {("MU1234", "2026-01-08"): 100}
4️⃣ 实战(固定航班信息)
flight = ("MU1234", "2026-01-08", "SHA", "PEK")
三、range —— 隐形高手(懒加载)
1️⃣ 核心原理
-
不存数据
-
只存:start / stop / step
r = range(1, 1_000_000_000)
📌 几乎不占内存
2️⃣ 特性
-
有序
-
不重复
-
支持切片(仍是 range)
range(10)[2:8:2]
3️⃣ 面试加分点
i in range(1000000000) # O(1)
👉 不遍历,而是数学计算
4️⃣ 实战(编号生成)
for i in range(1000, 1100):
generate_flight_no(i)
3️⃣ 集合类型(Set)
| 类型 | 核心特性 | 可变性 | 常用操作 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
set | 无序、唯一
| 可变 | add, remove, union, intersection, difference | 集合运算效率高,hash 特性
| 存储已售票航班号,快速去重 |
frozenset |
| 不可变 | union, intersection | 可作为 dict key | 固定航班组合标识 |
4️⃣ 映射类型(Mapping)
| 类型 | 核心特性 | 可变性 | 常用操作 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
dict |
| 可变 | get, setdefault, pop, keys, values, items |
| 航班信息存储 {"航班号": "状态"} |
collections.OrderedDict | 有序字典 | 可变 | 保持插入顺序 | Python 3.7+,普通 dict 已默认有序 | 有序航班列表 |
defaultdict | key 不存在 → 自动创建 | 可变 | 自动初始化 key | 避免 KeyError | 航班票价分类统计 |
5️⃣ 二进制类型(Binary)
| 类型 | 核心特性 | 可变性 | 常用操作 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
bytes | 不可变字节序列 | 不可变 | b'abc', decode, split | 字符编码、网络传输 | 航班 API 数据传输 |
bytearray | 可变字节序列 | 可变 | append, extend | 网络/文件操作 | 二进制缓存处理 |
memoryview | 内存视图 | 可变 | cast | 零拷贝,节省内存 | 大规模航班数据处理 |
6️⃣ 面试连环题
可变 vs 不可变:理解引用、拷贝行为
在 Python 中,变量并不直接保存数据本身,而是保存对象的引用。不可变对象(如 int、str、tuple)一旦创建,其内部值就不能被修改,任何“修改操作”本质上都会创建一个新对象并让变量指向新的引用;因此多个变量指向同一个不可变对象时,不会因为其中一个变量的运算而影响其他变量。相反,可变对象(如 list、dict、set)允许原地修改内容,当多个变量引用同一个可变对象时,对其中任意一个变量的修改,都会反映到所有引用该对象的地方,这也是可变对象在共享场景下最容易引发 Bug 的根源。
理解拷贝行为的关键在于区分“拷贝引用”还是“拷贝对象”。对不可变对象而言,所谓拷贝通常只是增加一个新的引用指向同一个对象,因为对象本身无法被修改,复用是安全的;而对可变对象,直接赋值或浅拷贝(如 list[:]、copy.copy)只会创建一个新的容器,但容器内部的元素仍然是对原对象中元素的引用,一旦内部元素本身也是可变的,修改就会相互影响。只有深拷贝(copy.deepcopy)才会递归地复制所有层级的对象,从而实现真正意义上的“完全独立”,这也是在复杂业务和并发场景中隔离状态时必须慎重选择的拷贝方式。
为什么 x in set 比 x in list 快?
-
list:顺序遍历 → O(n) -
set:hash 定位 → O(1)
set 底层是 hash table,本质是 dict 的 key。
set 为什么无序?Python 3.7+ dict 却有序?
-
set:只关心 hash 分布
-
dict:在 hash table 之外维护插入顺序数组
dict 是“有序的 hash table”,set 不是。
set 里为什么不能放 list?
{[1,2]} # TypeError
-
set 元素必须 可 hash
-
list 可变 → hash 值不稳定
hash 的前提是不可变。
dict 查找一定是 O(1) 吗?
-
平均 O(1)
-
极端 hash 冲突 → O(n)
-
hash 是什么?
-
Python 如何解决 hash 冲突?
-
rehash(扩容)什么时候发生?
Python 使用开放寻址法 + 动态扩容,极端情况已被极大避免。
一、什么是开放寻址法(Open Addressing)
冲突了,不建链表,继续找下一个空位
Python 的
dict和set在底层使用开放寻址法(Open Addressing)来解决 hash 冲突。当一个 key 经过 hash 计算后映射到的槽位已经被占用时,并不会像链表法那样在该位置挂链表,而是按照一套探测规则(扰动探测序列)继续在同一个数组中寻找下一个可用槽位。所有 key 都存放在一块连续的内存中,这种设计减少了指针跳转,提高了 CPU cache 命中率,使得查找、插入和删除在平均情况下都能保持接近 O(1) 的性能。二、动态扩容(rehash)
为什么一定要扩容?
由于开放寻址法在表过满时探测成本会急剧上升,Python 为
dict和set设计了动态扩容机制。当哈希表的装载因子超过一定阈值(约为 2/3)时,系统会触发 rehash:申请一块更大的连续内存作为新表,然后将旧表中的所有 key 重新计算位置并插入到新表中。通过这种“低装载因子 + 扩容重排”的策略,Python 能有效避免性能退化,保证在数据量不断增长的情况下,哈希表操作依然稳定高效。扩容过程
1. 申请更大的 table(通常 ×2)
2. 遍历旧 table
3. 对每个 key 重新 hash
4. 插入新 table
7️⃣ 航班系统【完整数据结构选型表】
| 场景 | 类型 | 原因 |
|---|---|---|
| 航班列表 | list | 有序、可遍历 |
| 固定航班信息 | tuple | 不可变、安全 |
| 航班编号生成 | range | 省内存 |
| 已售航班号 | set | O(1) 查重 |
| 航线组合 | frozenset | 无序 + 可 hash |
| 航班状态 | dict | key-value |
| 有序航班缓存 | OrderedDict | move_to_end |
| 票价统计 | defaultdict(int) | 自动初始化 |
| 航班信息存储 | dict | key-value 快速访问 |
| 售票用户集合 | set | 去重快速查找 |
| 不可修改的航班组合 | tuple / frozenset | 不可变,保证安全 |
| 大批量航班数据迭代 | generator | 节省内存,按需生成 |
| 二进制 API 数据 | bytes / bytearray | 网络传输和缓存处理 |
不推荐搭配
| 反例 | 原因 |
|---|---|
| list 查重 | O(n) |
| dict 当队列 | pop 慢 |
| defaultdict 存业务数据 | 隐式创建 |
| set 存顺序数据 | 顺序丢失 |
Python 基础
1️⃣Python 数据类型 & 内存管理
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| list 和 tuple 区别 | list 可变,tuple 不可变 | list 底层动态数组;tuple 固定大小,连续内存 | tuple 可 hash,可做 dict key,内存更小 | 不可修改 tuple 内部对象;tuple 内嵌可变对象仍可修改 | 坐标点保存为 tuple,节省百万条数据内存 |
| dict 查找复杂度 | 平均 O(1),最坏 O(n) | 哈希表实现;Py3.6+ dict 有插入顺序 | 讲解 hash 冲突解决:开放寻址或红黑树 | hash 冲突导致最坏情况 O(n) | 用户 ID 映射信息查找 |
| set 和 dict 区别 | set 只有 key,没有 value,都是哈希表 | 底层和 dict 类似 | set 运算:交集、并集、差集 | set 中元素必须可 hash,否则报错 | 去重用户列表 |
| 浅拷贝 vs 深拷贝 | 浅拷贝只复制引用,深拷贝递归复制 | copy 模块底层实现 浅拷贝: 创建新对象,其内容是原对象的引用。 深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。 | 对嵌套结构理解,避免共享副作用 | 浅拷贝嵌套可变对象会被修改 | 复制航班票价结构体 |
| 可变 vs 不可变对象 | 可变对象传入函数会修改,immutable 不会 | 内存引用 + 栈帧 | 对函数参数传递理解 | 使用 immutable 做 key 时需确保不可变 | 配置 dict vs tuple key 缓存结果 |
| 内存管理与 GC | 引用计数 + 循环引用垃圾回收 | PyObject 引用计数,gc 模块触发代际回收 | gc.collect() 强制回收 | 循环引用导致内存泄漏 | 大批量订单对象处理 |
Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。
1 引用计数
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
优点:
- 简单
- 实时性
缺点:
- 维护引用计数消耗资源
- 循环引用
2 标记-清除机制
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
3 分代技术
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
举例: 当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
2️⃣函数、闭包、装饰器
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 参数传递 | Python 传对象引用,mutable 会修改 | 栈帧存储引用,函数局部不复制对象 | 对 list/dict/int 不同表现理解 | mutable 被意外修改 | API 配置 dict 传入多个函数 |
| *args / **kwargs | *args → tuple, **kwargs → dict | Python 可变参数实现 | 调用灵活 | 拼写错误导致收集不到参数 | 通用函数包装任意参数 API |
| 闭包 | 内层函数引用外层变量形成闭包 | closure 保存外层变量 cell 对象 | 使用 nonlocal 修改外层变量 | 闭包引用循环引用造成内存问题 | 装饰器缓存内部状态 |
| 装饰器 | 函数套函数,返回新函数,@语法糖 | 闭包 + 高阶函数 | functools.wraps 保留元信息 | 多层装饰器顺序错误 | 权限校验、缓存、日志装饰器 |
| lambda | 匿名函数,单表达式 | 编译为函数对象 | 搭配 map/filter/comprehension | 单表达式限制 | 列表字段提取 |
3️⃣并发与异步
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| threading vs multiprocessing | threading 受 GIL 限制,多用于 IO,multiprocessing 真并行 | GIL 只允许一个线程执行字节码,进程有独立 GIL | 选择适用场景 | CPU 密集型用 thread 会慢 | 高并发 HTTP 请求 vs 数据计算 |
| asyncio | 单线程事件循环协程切换 | Event Loop + Task Scheduling | async/await | 忘记 await,任务不执行 | 异步爬虫、异步 API 调用 |
| queue / asyncio.Queue | 线程安全 / 协程安全 | 内部锁机制 | 生产者-消费者模式 | 阻塞与非阻塞使用混乱 | 日志异步写入 |
| futures / concurrent.futures | 封装线程池/进程池 | submit/map 任务调度 | 任务结果异步获取 | 忘记 shutdown 导致进程泄露 | 并行数据处理 |
4️⃣ 面向切面编程AOP和装饰器
这个AOP一听起来有点懵,同学面阿里的时候就被问懵了...
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
AOP 是 面向切面编程(Aspect-Oriented Programming) 的缩写。它是一种 程序设计思想,用于 将程序中的横切关注点(cross-cutting concerns)从业务逻辑中分离出来,让核心业务逻辑更清晰,同时可复用、可维护。
1️⃣ 核心概念
| 概念 | 解释 |
|---|---|
| 切面(Aspect) | 横切关注点的封装,例如日志、事务、安全校验 |
| 连接点(Join point) | 程序执行中的一个点,比如方法调用、异常抛出 |
| 通知(Advice) | 在连接点上执行的操作,可以是: - 前置通知(Before) - 后置通知(After) - 环绕通知(Around) |
| 切入点(Pointcut) | 指定哪些连接点需要被通知 |
| 目标对象(Target) | 被增强的业务对象 |
| 织入(Weaving) | 将切面应用到目标对象的过程 |
2️⃣ 作用
-
解耦:日志、事务、安全检查等不污染核心业务逻辑
-
复用:同一个切面可以应用到多个类或方法
-
统一管理:系统级功能集中管理,提高维护性
3️⃣ 典型场景
| 场景 | 示例 |
|---|---|
| 日志记录 | 在方法执行前后打印日志 |
| 权限校验 | 在方法执行前检查用户权限 |
| 事务管理 | 方法执行出错时自动回滚 |
| 缓存 | 方法执行结果自动缓存 |
4️⃣ Python 中 AOP 实现思路
Python 没有原生 AOP 框架,但可以用 装饰器、上下文管理器或 元类实现 AOP 风格:
# 前置 + 后置通知的装饰器实现
def aspect(func):
def wrapper(*args, **kwargs):
print(f"[Before] 调用 {func.__name__}")
result = func(*args, **kwargs)
print(f"[After] 调用 {func.__name__}")
return result
return wrapper
@aspect
def business_logic(x, y):
print("核心业务逻辑执行")
return x + y
business_logic(2, 3)
输出:[Before] 调用 business_logic
核心业务逻辑执行
[After] 调用 business_logic
5️⃣ 鸭子类型
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
比如在python中,有很多file-like的东西,比如StringIO,GzipFile,socket。它们有很多相同的方法,我们把它们当作文件使用。
又比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等.
鸭子类型在动态语言中经常使用,非常灵活,使得python不像java那样专门去弄一大堆的设计模式。
6️⃣Python中重载
引自知乎:http://www.zhihu.com/question/20053359
http://www.zhihu.com/question/20053359
函数重载主要是为了解决两个问题。
- 可变参数类型。
- 可变参数个数。
另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。
好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。
那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。
好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。
7️⃣新式类和旧式类
这篇文章很好的介绍了新式类的特性: http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html
http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html
新式类很早在2.2就出现了,所以旧式类完全是兼容的问题,Python3里的类全部都是新式类.这里有一个MRO问题可以了解下(新式类继承是根据C3算法,旧式类是深度优先),<Python核心编程>里讲的也很多.
一个旧式类的深度优先的例子
class A():
def foo1(self):
print("A")
class B(A):
def foo2(self):
pass
class C(A):
def foo1(self):
print("C")
class D(B, C):
pass
d = D()
d.foo1()
# A
按照经典类的查找顺序从左到右深度优先的规则,在访问d.foo1()的时候,D这个类是没有的..那么往上查找,先找到B,里面没有,深度优先,访问A,找到了foo1(),所以这时候调用的是A的foo1(),从而导致C重写的foo1()被绕过
8️⃣ Python自省
这个也是python彪悍的特性.
自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对象的类型.比如type(),dir(),getattr(),hasattr(),isinstance().
a = [1,2,3]
b = {'a':1,'b':2,'c':3}
c = True
print(type(a),type(b),type(c)) # <type 'list'> <type 'dict'> <type 'bool'>
print(isinstance(a,list)) # True
Python 并发与异步(线程 / 进程 / 协程)
| 对比项 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 调度者 | OS | OS | 用户 |
| 内存 | 独立 | 共享 | 共享 |
| GIL 影响 | ❌ | ✅ | ✅ |
| CPU 并行 | ✅ | ❌ | ❌ |
| 切换成本 | 高 | 中 | 极低 |
| 编程难度 | 中 | 高 | 中 |
| 适合场景 | 计算 | I/O | 高并发 I/O |
进程解决隔离问题,线程解决并行执行问题,协程解决高并发等待问题。
进程:
进程是资源隔离的最小单位,崩一个不影响其他。
线程:
线程是 CPU 调度的最小单位,切换由操作系统完成。
协程:
协程是用户态调度的执行单元,切换由程序控制。
| 问题 | 高分回答模板 |
|---|---|
| 1️⃣ Python 多线程能并行吗? | 不能完全并行(受 GIL 限制),但 I/O 密集型可以并发。CPU 密集型任务应使用多进程。 |
| 2️⃣ 协程能替代线程吗? | 不能,协程只能优化 I/O 等待,阻塞或 CPU 任务仍需线程/进程。 |
| 3️⃣ asyncio 任务遇到阻塞函数怎么办? | 会卡死整个事件循环,需要 run_in_executor 或使用异步库。 |
| 4️⃣ 线程和进程切换成本哪个高? | 进程高,线程中等,协程最低(用户态切换)。 |
| 5️⃣ 多进程共享内存怎么办? | 进程独立内存,通信需 Queue、Pipe 或共享内存模块。 |
1️⃣ 线程(Thread)
特点: 共享进程内存、轻量执行单元、阻塞时可并发
优势: 适合 I/O 密集型任务、轻量并发
劣势: CPU 密集受 GIL 限制,锁竞争复杂
-
解决阻塞问题:一个线程阻塞时,其他线程继续运行
-
I/O 密集型任务可以通过几十、几百线程实现并发
| 场景 | 原因 |
|---|---|
| 网络爬虫(几十到百线程) | 网络请求 I/O 阻塞,可并发 |
| 文件读写/数据库操作 | I/O 阻塞可并行 |
| GUI 主线程 + 后台任务 | 主线程保持响应,后台线程处理耗时任务 |
问题:
-
线程开销大
-
每个线程有独立栈空间,创建和切换成本高
-
数百/上千线程时,内存占用高,CPU 上下文切换频繁
-
-
调度受操作系统控制
-
上千线程并发时,切换效率低
-
-
GIL 限制
-
Python 中 CPU 密集型任务无法在多线程中真正并行
-
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| Python 线程概念 | 线程是轻量级进程,共享内存空间 | 使用 threading.Thread | 多线程适合 I/O 密集型 | GIL 限制 CPU 密集型性能 | Python 爬虫、网络请求 |
| GIL(全局解释器锁) | 同一时刻只有一个线程执行 Python 字节码 | 防止对象操作冲突 | 理解 Python 多线程局限 | CPU 密集型无性能提升 | I/O 密集型任务多线程提升效率 |
| 线程同步 | Lock, RLock, Event, Condition, Semaphore | 防止数据竞争 | 理解死锁风险 | 忘记释放锁导致死锁 | 多线程修改航班缓存计数器 |
| 线程池 | concurrent.futures.ThreadPoolExecutor | 线程复用减少开销 | submit 与 map 使用 | 阻塞调用导致线程池堵塞 | 批量发送航班 API 请求 |
| daemon 线程 | 后台线程,主线程结束自动退出 | 线程标记属性 | 守护线程应用 | 忘记 join 导致数据未写完 | 后台日志写入 |
2️⃣ 进程(Process)
特点: 独立内存空间、资源隔离、CPU 调度单位
优势: 能利用多核 CPU,适合 CPU 密集型任务
劣势: 启动开销大,通信复杂
| 场景 | 原因 |
|---|---|
| 图像/视频处理 | CPU 密集,进程独立,避免 GIL 限制 |
| 数据科学/矩阵计算 | 大量浮点运算,可多核并行 |
| 独立服务隔离 | 崩一个进程不影响其他进程 |
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| Python 进程 | 独立内存空间,完全并行 | 使用 multiprocessing.Process | CPU 密集型任务优先 | 启动开销大 | 航班票价计算任务并行化 |
| 进程池 | multiprocessing.Pool | 复用进程减少创建开销 | apply/apply_async/map | 进程间共享数据需 Queue / Manager | 批量航班计算 |
| 进程间通信 | Queue, Pipe, Manager | IPC 数据交换 | 理解共享 vs 拷贝 | 不可直接传复杂对象 | 多进程统计航班票价 |
| fork vs spawn vs forkserver | 不同启动模式 | Linux fork 拷贝父进程内存 | Windows 默认 spawn | 数据复制大内存占用 | 跨平台多进程启动 |
| GIL 与进程 | GIL 不影响多进程并行 | 每个进程独立 Python 解释器 | CPU 密集型任务并行 | 共享数据需 IPC | CPU 密集型计算优化 |
3️⃣ 协程(Coroutine / async / await)
-
用户态调度:协程切换不依赖 OS,上下文切换开销非常小
-
轻量高并发:单线程可以管理成千上万协程
-
非阻塞 I/O:协程
await可以让出 CPU,单线程就能处理多个 I/O -
逻辑清晰:避免大量锁竞争,代码可读性好
| 场景 | 原因 |
|---|---|
| 高并发网络请求(数千/万连接) | 协程轻量,不用线程切换开销 |
| 异步爬虫/异步 Web 框架(FastAPI / aiohttp) | I/O 非阻塞,单线程管理大量请求 |
| 异步任务队列/事件处理 | 高并发事件处理无需线程切换 |
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| I/O 密集型 | 使用多线程或 asyncio | 网络/文件 I/O 阻塞 | asyncio 高并发 | 阻塞操作仍会堵塞 | 爬取航班信息 |
| CPU 密集型 | 使用多进程 | 充分利用多核 CPU | multiprocessing + pool | 进程间通信开销大 | 航班票价计算任务 |
| 混合任务 | async + 线程池/进程池 | I/O 协程 + CPU 并行 | 异步+并行组合 | 混合模式复杂 | 高并发查询 + 票价计算 |
| 死锁 / 饥饿 | Lock / Condition 使用不当 | 线程 / 进程同步 | 理解锁粒度 | 阻塞任务无限等待 | 多线程缓存更新 |
-
进程 → 资源隔离、适合 CPU 密集型任务
-
线程 → 解决阻塞问题、适合 I/O 密集型任务
-
协程 → 轻量高并发、适合大量 I/O 操作
-
为什么线程防止阻塞?
-
线程之间可以并行执行,某个线程阻塞不会影响其他线程。
-
-
为什么协程能提高并发?
-
协程在用户态调度,切换开销小,可以在单线程中管理成千上万 I/O 任务。
-
-
为什么进程要隔离?
-
每个进程独立内存空间,CPU 密集型任务不会被 GIL 限制,同时一个进程崩掉不会影响其他进程。
-
4️⃣ Python 并发设计选择
在多线程 + 协程组合下:
-
线程隔离:一个线程阻塞不会影响其他线程,当一个任务被阻塞时,别的任务还能继续
-
协程协作调度:线程内部阻塞会影响该线程内的协程,协程不是为了“少用线程”,而是为了“不浪费线程”
-
整体执行:100 个任务最终都会执行完,只是被阻塞的协程可能稍微延迟

| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| I/O 密集型 | 使用多线程或 asyncio | 网络/文件 I/O 阻塞 | asyncio 高并发 | 阻塞操作仍会堵塞 | 爬取航班信息 |
| CPU 密集型 | 使用多进程 | 充分利用多核 CPU | multiprocessing + pool | 进程间通信开销大 | 航班票价计算任务 |
| 混合任务 | async + 线程池/进程池 | I/O 协程 + CPU 并行 | 异步+并行组合 | 混合模式复杂 | 高并发查询 + 票价计算 |
| 死锁 / 饥饿 | Lock / Condition 使用不当 | 线程 / 进程同步 | 理解锁粒度 | 阻塞任务无限等待 | 多线程缓存更新 |
5️⃣ Python 并发实战案例
协程和线程解决的问题不一样,协程不能完全替代线程。
协程适合的是高并发 I/O 场景,它通过 await 在等待时主动让出执行权;
线程解决的是阻塞问题,当一个任务阻塞时,其他任务还能继续执行。
在 Python 里,由于 GIL 的存在,线程本身就不适合 CPU 密集任务;
而协程如果遇到阻塞代码或非 async 库,会直接把整个事件循环卡死。
所以在实际场景中,协程通常是用来减少线程数量,而不是取代线程,协程解决的是并发等待,线程解决的是阻塞执行
常见的做法是:
-
I/O 用协程
-
阻塞或 CPU 任务交给线程或进程
| 场景 | 实现方式 | 面试加分点 | 典型坑 | 说明 |
|---|---|---|---|---|
| 批量航班 API 请求 | asyncio.gather / ThreadPoolExecutor | 高并发处理 I/O | 忘记 await 或线程阻塞 | 高并发 I/O 任务 |
| CPU 密集型票价计算 | multiprocessing.Pool | 多进程并行 | IPC 数据传输 | 多核 CPU 利用最大化 |
| 异步 + 队列 | asyncio + aiohttp + asyncio.Queue | 控制并发量 | 队列未 await 导致阻塞 | 并发请求控制 |
| 线程安全缓存更新 | threading.Lock | 防止数据竞争 | 忘记释放锁 | 更新航班缓存计数器 |
Python 高级特性
1️⃣ 闭包 (Closure)
闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
感觉闭包还是有难度的,几句话是说不明白的,还是查查相关资料.
重点是函数运行后并不会被撤销,就像16题的instance字典一样,当函数运行完后,instance并不被销毁,而是继续留在内存空间里.这个功能类似类里的类变量,只不过迁移到了函数上.
闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样.
def count_time_wrapper(func):
def improved_func():
start_time = time.time()
func()
end_time = time.time()
print(f"it takes {end_time - start_time}s to find all the olds")
return improved_func
# 闭包本质上是一个函数
# 闭包函数的传入参数和返回值都是函数
# 闭包函数的返回值函数是对传入函数进行增强的函数
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 闭包的定义 | 内层函数引用外层函数变量,形成闭包 | Python 用 cell 对象保存外层变量 | nonlocal 修饰可修改外层变量 | 忘记 nonlocal,外层变量不可修改 | 缓存计算结果、延迟计算 |
| 闭包使用场景 | 数据封装、装饰器实现、回调 | 内层函数保持外层状态 | 减少全局变量使用 | 循环闭包共享变量问题 | 缓存航班价格、装饰器状态保持 |
| 闭包 vs lambda | lambda 是匿名函数,闭包可保存状态 | lambda 可以生成闭包 | 两者结合使用提高代码简洁性 | lambda 只适合单表达式 | map/filter 中用闭包生成函数 |
2️⃣ 装饰器 (Decorator)
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 装饰器定义 | 函数作为参数,返回新函数 | 高阶函数 + 闭包 | @语法糖,functools.wraps 保留原函数信息 | 不加 wraps,元信息丢失 | 权限校验装饰器、日志装饰器 |
| 带参数装饰器 | 外层函数接收参数,返回装饰器 | 三层嵌套闭包 | 支持灵活配置 | 参数传递顺序错 | 缓存 TTL 装饰器 |
| 多层装饰器执行顺序 | 从内向外应用,调用从外向内 | 栈式闭包 | 结合 wraps 保留元信息 | 调用顺序理解错误 | 日志 + 缓存装饰器叠加 |
3️⃣ 迭代器 (Iterator) 与生成器 (Generator)
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 迭代器 | 实现 __iter__() 和 __next__() 的对象 | 迭代器协议 | 支持 for 循环 | 忘记 StopIteration | 遍历大文件、分页数据 |
| 生成器 | 使用 yield 生成迭代器 | 保存状态在 frame 对象中 | 节省内存、惰性计算 | 多线程中共享生成器需加锁 | 处理百万条航班数据流 |
| 生成器表达式 vs 列表推导 | generator expression 惰性,列表推导立即计算 | 节省内存 | 大数据量处理 | 忘记 list() 转化 | 分页结果流式处理 |
| send() 与 yield | yield 可接收 send 值 | 协程初级实现 | 实现双向数据流 | 忽略 send 语义 | 协程任务调度、异步生成器 |
4️⃣ 魔法函数 (Dunder / Special Methods)
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
__init__ vs __new__ | __new__ 创建对象,__init__ 初始化 | 元类 + 对象创建链 | 单例模式 | 忘记返回对象 | Redis 客户端单例模式 |
__str__ vs __repr__ | str 用户友好,repr 调试友好 | 调用顺序 | 调试与日志打印 | 覆盖不当 | 日志打印对象信息 |
| 算术运算符重载 | __add__, __sub__, __mul__ | 支持自定义对象运算 | 可用于向量、矩阵等 | 返回类型错误 | 航班票价对象加法 |
| 比较运算符 | __lt__, __eq__ 等 | 支持 sorted / min / max | 与 total_ordering 配合 | 未实现全序 | 航班排序、过滤 |
| 可调用对象 | __call__ | 对象像函数调用 | 实现策略模式 | 返回值忽略 | 配置对象直接调用计算票价 |
| 容器类型 | __getitem__, __setitem__, __len__ | 支持索引访问、切片 | 可实现自定义 dict / list | 切片返回新对象注意浅深拷贝 | 航班数据封装类 |
5️⃣ 上下文管理器 (Context Manager / with)
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
with 的作用 | 自动管理资源释放 | 调用 __enter__ 与 __exit__ | 文件、数据库连接、锁 | 忘记处理异常 | 数据库事务自动提交/回滚 |
| 自定义上下文管理器 | 实现 __enter__ 和 __exit__ | 可抛出异常控制 | 提高代码可读性与安全性 | exit 忘记返回 False 导致异常吞掉 | Redis 连接池获取/释放 |
contextlib.contextmanager | 使用生成器装饰函数 | 自动生成上下文管理器 | 简化自定义管理器 | yield 异常处理 | 数据库事务、文件处理 |
高并发航班订票系统 —— 分层组件与技术选型详解
1️⃣ DNS / 调度层(Global Traffic Management)
这是“系统真正的入口”,不是域名解析那么简单
一、选型组件(What we choose)
1. 智能 DNS(Smart DNS / GSLB)
-
本质是 全局流量调度系统(Global Server Load Balance)
-
常见实现:
-
云厂商:阿里云 / 腾讯云 / AWS Route53
-
自建:DNS + 健康探测 + 权重系统
-
2. Geo DNS
-
基于:
-
用户 IP → 地理位置
-
运营商(电信 / 联通 / 移动)
-
-
返回 不同机房 / 不同 VIP
3. 健康检查系统
-
HTTP / TCP 探活
-
机房级、集群级健康状态
二、作用(What)
在 用户请求真正进入任何服务器之前 完成:
-
跨地域调度
-
就近接入
-
机房级容灾
-
大促 / 春运期间的流量削峰前置
这是 唯一一个不依赖后端服务的调度层。
三、为什么一定要在 DNS 层调度(Why)
1️⃣ 调度越前,代价越小
-
DNS 层调度 = 请求还没产生服务器资源消耗
-
LB 层调度 = 已经进机房了
2️⃣ 机房级故障只能靠 DNS
-
LB / 服务治理只能解决「机房内问题」
-
机房断网、断电、光纤被挖 → 只有 DNS 能切
3️⃣ DNS 是天然的“全局视角”
-
LB 只能看到自己
-
DNS 可以看到 全球所有机房
四、技术能力(How)
-
基于权重返回 IP
-
70% → 主机房
-
30% → 灰度机房
-
-
基于健康状态动态摘流
-
结合 TTL 控制切流速度
-
运营商调度
-
电信 → 电信机房
-
联通 → 联通机房
-
五、不这样做的后果(Risk)
-
所有流量压到一个机房
-
跨地域 RTT 飙升(抢票直接失败)
-
机房级事故 = 全站事故
六、航班订票系统中的价值(Business)
春运抢票场景下
同一秒进来的百万请求,被 DNS 在全球范围内拆散
2️⃣ 安全防护层(WAF / DDoS)
这层的目标只有一个:让“脏流量”死在业务之外
一、选型组件
1. WAF(Web Application Firewall)
-
防的不是黑客
-
防的是:
-
非正常行为
-
非人类请求
-
非业务流量
-
2. DDoS 高防
-
SYN Flood
-
UDP Flood
-
HTTP Flood
3. 行为风控系统
-
IP / UA / Cookie / 行为轨迹
-
设备指纹
二、作用(What)
-
在业务逻辑之前
-
在缓存之前
-
在限流之前
直接把攻击流量 剪掉
三、为什么必须独立一层(Why)
-
攻击流量:
-
不遵守业务规则
-
不关心返回结果
-
-
如果让它进入业务层:
-
Redis 会被打满
-
DB 会被拖死
-
限流形同虚设
-
👉 安全是基础设施能力,不是业务能力
四、技术能力(How)
-
CC 攻击识别(频率 + 行为模式)
-
人机识别(JS Challenge)
-
IP 动态封禁
-
请求特征建模
五、不做的后果
-
黄牛脚本 24h 不间断刷接口
-
正常用户永远抢不到票
-
系统看起来“没挂”,但已经“死了”
六、航班系统价值
抢票系统的第一竞争力:
不是并发高,而是“刷子进不来”
3️⃣ CDN / LB 层(流量承载层)
负责“扛流量”,不是做业务
一、选型组件
CDN
-
静态资源
-
半静态页面
-
热点查询接口(可选)
LVS(四层)
-
高性能转发
-
无业务逻辑
Nginx(七层)
-
路由
-
Header 处理
-
反向代理
二、作用(What)
-
把 最廉价的请求挡在最外层
-
把 昂贵请求均匀分发
三、为什么这么组合(Why)
-
CDN:最便宜的算力
-
LVS:最强的转发
-
Nginx:最灵活的控制
👉 各司其职,不越界
四、技术能力(How)
-
四层负载(TCP)
-
七层路由(URL / Header)
-
动静分离
-
后端健康检查
| 策略 | 原理 | 面试加分点 | 典型应用场景 | 实战案例 |
|---|---|---|---|---|
| 轮询 (Round Robin) | 每个请求按顺序分配到后端服务器,简单公平 | 适合请求处理能力相近的服务器,简单高效 | 小型 Web 服务、API 服务,服务器性能差异不大 | Python 航班查询 API,每个实例处理能力相同,平均分发请求 |
| 最少连接 (Least Connections) | 将新请求分配给当前连接数最少的服务器 | 适合请求处理时间长、服务器性能不同的场景 | 文件上传、长连接 API、WebSocket | Python 文件上传服务,长请求分发给负载较轻的实例 |
| 源地址哈希 (IP Hash) | 根据客户端 IP 哈希计算分配到固定后端服务器 | 保证同一客户端请求分配到同一台服务器(会话保持) | 会话依赖场景(未使用 Redis Session) | 航班预订系统,用户操作需要保持会话状态,避免跨实例丢失缓存 |
五、不这样做的后果
-
应用服务器被 JS / 图片 拖死
-
单点 LB 成为瓶颈
-
扩容成本极高
六、航班系统价值
图片、航班列表、静态配置
永远不进应用层
4️⃣ 接入层 / API Gateway(系统“城门”)
这一层不是为了“转发请求”,而是为了“控制系统生死”
一、选型组件(What we choose)
1️⃣ Nginx + Lua(OpenResty 体系)
-
Nginx:成熟、稳定、高性能
-
Lua:可编程、可热更新、低侵入
👉 适合:
-
超高 QPS
-
简单但极其关键的逻辑(限流、鉴权)
2️⃣ Kong / APISIX(现代 API 网关)
-
基于 Nginx + Lua
-
插件化能力极强
-
天然支持:
-
限流
-
鉴权
-
灰度
-
监控
-
👉 适合:
-
微服务规模较大
-
接口数量多
-
需要统一治理
二、这一层到底干什么(What)
只干三件事,而且每一件都“决定系统能不能活”:
-
谁可以进来(鉴权)
-
进来多少(限流)
-
往哪去(路由 / 灰度)
🚫 不写任何业务逻辑
三、为什么必须独立一层(Why)
1️⃣ 这是“系统边界”
-
所有外部请求 只能从这里进
-
一旦绕过,后果不可控
2️⃣ 横切能力不属于业务
-
鉴权
-
限流
-
灰度
-
黑白名单
👉 如果散落在服务中:
-
策略不一致
-
改一处要改 N 个服务
-
必然出事故
四、技术能力(How)
① 鉴权机制
JWT / OAuth
-
Token 在网关校验
-
后端服务 不再关心用户身份
优势:
-
减少服务复杂度
-
防止未授权请求进入核心链路
② 限流(⚠️ 极其重要)
常见限流维度
-
IP
-
用户 ID
-
接口
-
全局 QPS
常用算法
-
令牌桶(允许突发,适合下单)
-
漏桶(平滑流量,适合写操作)
👉 限流一定在业务之前
③ 路由 & 灰度
-
按 Header / Cookie 路由
-
新版本只接 1% 流量
-
出问题立即回滚
五、如果没有这一层(真实后果)
-
下单接口被直接打穿
-
每个服务各自限流 → 策略冲突
-
无法在事故时“一刀切流量”
六、航班订票系统中的价值
锁座 / 下单接口的并发上限
不是靠数据库扛,而是靠网关“掐住”
5️⃣ 服务治理 & 配置中心(系统“自保机制”)
这层的目标不是“调用成功”,而是“别一起死”
一、选型组件
1️⃣ Nacos / Consul
-
服务注册
-
服务发现
-
配置管理
2️⃣ Sentinel
-
熔断
-
降级
-
流量控制
二、这一层解决什么问题(What)
👉 服务之间的调用不可靠
-
网络会抖
-
服务会慢
-
依赖一定会失败
这一层负责:
-
控制失败的影响范围
-
阻断级联故障
三、为什么必须有服务治理(Why)
没有治理的微服务 = 定时炸弹
-
A 调 B
-
B 调 C
-
C 慢了 → B 卡 → A 卡 → 全站卡死
👉 这不是 bug,这是必然
四、技术能力(How)
① 熔断(Circuit Breaker)
触发条件:
-
RT 超过阈值
-
错误率升高
-
QPS 异常
行为:
-
直接失败
-
返回降级结果
-
不再发请求
② 降级(Degrade)
-
非核心功能直接关闭
-
返回兜底数据
例子:
-
推荐航班不可用
-
但下单仍可用
③ 动态配置
-
限流阈值实时调整
-
熔断策略在线生效
-
不重启服务
五、不做的后果
-
一个慢服务拖垮整个系统
-
高峰期雪崩
-
无法止血
六、航班系统价值
支付、通知、积分 可以失败
下单主链路必须活
6️⃣ 应用层(Python Async 计算层)
这里才是真正的“业务心脏”
一、选型组件
FastAPI / Tornado
-
原生异步
-
高性能
-
生态成熟
asyncio + uvloop
-
事件驱动
-
单线程高并发
-
uvloop 替代默认事件循环
多进程模型
-
每核一个进程
-
避开 GIL 影响
二、这一层干什么(What)
-
下单
-
锁座
-
状态流转
-
业务校验
👉 只关心业务正确性
三、为什么 Python 还能扛高并发(Why)
-
抢票系统:
-
90% 时间在等 I/O
-
DB / Redis / MQ
-
👉 协程 ≠ 线程
👉 一个进程能跑几万协程
四、技术能力(How)
-
非阻塞 DB / Redis / MQ
-
协程调度
-
连接池复用
-
超时控制
五、不这样做的后果
-
线程数爆炸
-
上下文切换耗尽 CPU
-
RT 不稳定
六、航班系统价值
高峰期
查询不拖慢下单,下单不阻塞支付
7️⃣ 缓存层(Redis · 系统真正的“抗压核心”)
如果你只能在整个系统里选一层重点设计,那一定是这一层
一、选型组件(What we choose)
1️⃣ Redis(核心)
为什么是 Redis,而不是别的缓存?
-
内存级访问(微秒级)
-
丰富数据结构:
-
String(余票数)
-
Hash(航班信息)
-
ZSet(热门航班排行)
-
-
原子操作(INCR / DECR / Lua)
-
单线程模型(避免锁竞争)
👉 Redis 天然适合“高并发 + 强一致的局部场景”
2️⃣ 布隆过滤器(Bloom Filter)
-
解决“查不到的数据被反复请求”的问题
-
本质是:
-
用很小的内存
-
判断「某个 key 一定不存在」
-
3️⃣ 分布式锁(Redis Lock / Lua)
-
只用于:
-
热点 key
-
短时间互斥
-
-
不是全局锁
二、这一层的核心职责(What)
缓存层不是“加速器”,而是三件事:
-
挡流量
-
保数据库
-
稳 RT
👉 在高并发系统里:
Redis ≈ 系统的“缓冲垫 + 防爆阀”
三、为什么 DB 前一定要有缓存(Why)
如果没有缓存:
-
所有查询直打 DB
-
DB QPS 一到阈值 → RT 飙升 → 连接池耗尽
-
整个系统雪崩
👉 数据库不是为高并发设计的
四、缓存三大致命问题 & 工程解法(How)
⚠️ 1️⃣ 缓存穿透(最基础但最致命)
问题本质
-
请求一个 DB 中根本不存在的数据
-
每次都绕过缓存 → 直打 DB
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命 中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,产生穿透问题
缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高 并发性,甚至可能造成后端存储宕掉

工程解法
✅ 布隆过滤器
-
所有合法航班号提前写入
-
查询前先判断:
-
不存在 → 直接返回
-
在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截,即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行

这种方法适用于数据命中不高、数据相对固定、实时性低(通常是数据 集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少
✅ 空值缓存
-
DB 查不到 → 缓存一个短 TTL 的空值
当存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源

当然缓存空对象会有两个问题:
第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象
缓存空对象与布隆过滤器比较

⚠️ 2️⃣ 缓存击穿(热点 Key 爆炸)
问题本质
-
某个热点 key(如热门航班余票)
-
同一时刻失效
-
大量请求同时打 DB
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题
击穿其实可以看做是雪崩的一个子集,解决方法一般有两种,设置热点数据永不过期和设置互斥锁
工程解法
✅ 热点 key 互斥锁
-
第一个请求回源
-
其他请求等待或快速失败
所谓的互斥锁,就是保证同一时间只有一个业务线程更新缓存,对于没有获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值

✅ 逻辑过期
-
value 中存:
-
数据
-
逻辑过期时间
-
-
后台异步刷新
⚠️ 3️⃣ 缓存雪崩(系统级灾难)
问题本质
-
大量 key 同时过期
-
DB 扛不住瞬时洪峰
由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况

工程解法
✅ TTL 随机化
-
过期时间加随机偏移
针对大量缓存同时过期的情况,可以通过缓存 reload 机制,预选去更新缓存,在即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀
✅ 多级缓存
-
本地缓存 + Redis
出现服务不可用的情况,我们第一时间想到的肯定是高可用,甚至是异地容灾等机制
✅ 限流兜底
-
缓存异常 → 快速失败
无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部阻塞(hang)在这个资源上,造成整个系统不可用。降级机制在高并发系统中是非常普遍的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面开天窗
五、缓存与一致性(极重要)
原则只有一句话:
缓存永远不作为“事实来源”
工程策略:
-
写操作:
-
先写 DB
-
再删缓存(延迟双删)
-
-
读操作:
-
缓存为主
-
DB 兜底
-
六、航班系统中的真实用法
-
航班信息:Redis Hash
-
余票数:Redis String + Lua 原子扣减
-
查询 QPS:99% 不进 DB
8️⃣ 消息队列层(MQ · 削峰与解耦的核心)
没有 MQ 的高并发系统,本质是“硬扛”
一、选型组件(What we choose)
Kafka
-
吞吐极高
-
适合日志、埋点、异步通知
RocketMQ
-
事务消息
-
顺序消息
-
业务场景友好
RabbitMQ
-
可靠性高
-
延迟队列成熟
二、MQ 这一层到底解决什么(What)
-
削峰
-
异步
-
解耦
👉 让系统“慢慢消化”流量,而不是瞬间崩溃。
三、为什么同步链路一定要被切断(Why)
-
同步链路越长:
-
RT 越高
-
失败概率越大
-
-
高峰期:
-
下单 ≠ 支付 ≠ 通知
-
👉 下单成功 ≠ 所有事立刻完成
四、MQ 的工程能力(How)
1️⃣ ACK 机制
-
消费成功才确认
-
失败可重试
2️⃣ 幂等消费(必须)
-
每条消息有唯一 ID
-
消费前先判断是否处理过
幂等(Idempotent):对同一个操作,无论执行一次还是执行多次,结果都是一样的,不会因为重复执行而产生副作用。
简单理解:多次执行等于一次执行
数学上也有幂等概念:
f(f(x)) = f(x)
场景 是否幂等 说明 HTTP GET /api/user/123 ✅ 幂等 多次请求都返回同一用户信息,不会修改数据 HTTP POST /api/order ❌ 非幂等 每次请求可能生成新的订单 HTTP PUT /api/order/123 ✅ 幂等 无论执行多少次,订单状态被更新为相同的值 Redis SET key value ✅ 幂等 不管执行多少次,key 的值最终是 value Redis INCR key ❌ 非幂等 每次执行都会加 1,结果不同
3️⃣ 顺序消息(关键场景)
-
订单状态流转
-
支付 → 出票 → 通知
五、不使用 MQ 的后果
-
高峰期下单接口直接被拖死
-
所有功能互相影响
-
一处慢,全站慢
六、航班系统中的实际应用
-
下单成功 → 发 MQ
-
异步:
-
发短信
-
写日志
-
对账
-
-
主链路 RT 稳定
9️⃣ 数据层(关系型数据库 · 最后的底线)
这是系统里“最不能错”的一层
一、选型组件(What we choose)
MySQL / PostgreSQL
-
成熟
-
强事务
-
强一致
👉 所有钱、订单、库存,最终只认这里
二、为什么 DB 一定要“减负”(Why)
-
DB 是:
-
最慢
-
最贵
-
最脆弱
-
-
一旦被打爆:
-
数据丢失
-
系统不可恢复
-
三、核心技术能力(How)
1️⃣ 主从复制
-
主写
-
从读
2️⃣ 读写分离
-
查询走从库
-
写操作走主库
3️⃣ 分库分表
-
按用户 ID / 订单 ID 分片
-
控制单表数据量
四、冷热数据分层(非常重要)
-
热数据:
-
最近订单
-
当日航班
-
-
冷数据:
-
历史订单
-
已完成航班
-
👉 冷数据可:
-
归档
-
降级存储
-
只读
五、航班系统中的数据设计
-
库存:
-
DB 兜底
-
Redis 扛并发
-
-
订单:
-
DB 强事务
-
-
查询:
-
缓存 + 搜索引擎
-
🔟 数据一致性 & 防超卖(核心中的核心)
并发系统 90% 的事故,死在这一层
一、问题先说清楚(不然全是空话)
航班订票系统的真实约束
-
库存是共享资源
-
下单是强事务
-
系统是分布式
-
并发是不可控的
👉 结论只有一个:
强一致 ≠ 高并发下可行方案
二、为什么不能“简单加锁”(Why)
❌ DB 行锁的问题
-
锁时间长
-
吞吐极低
-
高峰期必死
❌ 分布式锁全链路
-
网络不可靠
-
锁丢失、误删
-
容易死锁
👉 锁只能用于“短、快、局部”
三、工程级共识(非常重要)
库存的事实来源是 DB
库存的并发承载在 Redis
这是整个系统设计的基石。
四、真实可落地的“防超卖方案”(How)
✅ 核心思想:两阶段控制
阶段一:Redis 原子预扣库存(高并发阶段)
技术手段
-
Redis + Lua 脚本
-
单线程执行
-
原子扣减
执行逻辑
1. 用户请求下单
2. Redis 判断库存 > 0
3. 原子扣减库存
4. 成功 → 进入下单流程
5. 失败 → 直接返回“售罄”
👉 这里不碰 DB
阶段二:DB 最终确认库存(事务阶段)
技术手段
-
本地事务
-
乐观锁 / version 字段
执行逻辑
1. 开启 DB 事务
2. 校验库存版本
3. 扣减库存
4. 创建订单
5. 提交事务
阶段三:异步兜底 & 回滚(容错阶段)
失败场景
-
DB 写失败
-
服务异常
-
进程崩溃
兜底方案
-
Redis 预扣库存超时回补
-
MQ 补偿消息
-
定时任务对账
五、完整下单链路(一步不跳)
用户 → API Gateway(限流)
→ 应用层
→ Redis Lua 预扣库存
→ 成功
→ 创建订单(DB 事务)
→ 成功
→ 发 MQ(下单成功)
→ 失败
→ 发送补偿 MQ(回补库存)
→ 失败
→ 返回售罄
六、为什么这是“最终一致性”(Why)
-
Redis 和 DB 不在同一事务
-
MQ 异步
-
允许短时间不一致
👉 但系统保证:
-
不会超卖
-
最终一定对账一致
七、为什么不用 TCC 全链路
TCC 的真实代价
-
实现复杂
-
维护成本极高
-
高并发下吞吐下降明显
👉 在库存这种“高并发资源”场景下,
TCC ≠ 最优解
八、航班系统中的真实价值
春运高峰
10 万人同时抢 1 万张票
系统不超卖、不锁死、不崩溃
1️⃣1️⃣ 可观测性层(没有它 = 瞎飞)
你不是在“跑系统”,
你是在“监控一个随时会失控的复杂系统”
一、选型组件
Prometheus
-
指标采集
-
时序数据
Grafana
-
可视化
-
大盘
ELK / Loki
-
日志集中
-
快速检索
Trace(Jaeger / SkyWalking)
-
链路追踪
二、为什么监控不是“可选项”(Why)
-
故障一定会发生
-
高并发下问题是:
-
瞬时的
-
不可复现的
-
👉 没有监控 = 无法定位 = 只能背锅
三、必须监控的核心指标(How)
① 流量指标
-
QPS
-
并发数
② 性能指标
-
RT(P50 / P90 / P99)
-
慢接口
③ 稳定性指标
-
错误率
-
超时率
-
熔断次数
四、日志的工程要求
-
结构化日志(JSON)
-
必须包含:
-
trace_id
-
order_id
-
user_id
-
-
可跨服务串联
五、航班系统中的价值
抢票高峰
5 秒内定位:
是缓存?DB?MQ?还是网络?
1️⃣2️⃣ 灾备 / 多活 / 高可用
高并发 ≠ 高可用
活着比快更重要
一、选型方案
多机房部署
-
同城多活
-
异地灾备
自动切流
-
DNS
-
网关
-
LB
定期演练
-
故障注入
-
真实切换
二、为什么灾备不能“临时想”(Why)
-
灾难发生时:
-
没时间讨论方案
-
没时间写脚本
-
👉 灾备一定是“平时就跑着的方案”
三、技术能力(How)
-
数据异步复制
-
只读降级
-
非核心功能关闭
四、航班系统中的最终目标
任何单点故障
都不影响用户完成订票
高并发系统分层架构总表(航班订票系统)
| 层级 | 主要组件 / 技术 | 核心职责(What) | 技术要点(How) | 为什么选(Why) | 关键设计点 | 航班订票系统中的作用 |
|---|---|---|---|---|---|---|
| DNS / 调度层 | 智能 DNS、Geo DNS | 全局流量调度 | 基于地域、运营商、健康检查返回解析结果 | 调度越前置,后端压力越小 | 真正的流量调度从 DNS 开始,而不是 LB | 用户请求就近接入最近、最健康机房 |
| 安全防护层 | WAF、DDoS 防护 | 拦截恶意流量 | 规则匹配、IP 黑白名单、流量清洗 | 攻击流量比业务流量更致命 | 安全必须与业务彻底解耦 | 防止黄牛刷票、CC 攻击 |
| CDN / LB 层 | CDN、Nginx、LVS、F5 | 静态缓存、请求分发 | CDN 边缘缓存 + 四/七层负载均衡 | 大量读请求不应进入后端 | 四层高性能,七层更灵活 | 航班列表页、静态资源就近返回 |
| 接入层 / API Gateway | Nginx + Lua、Kong、APISIX | 统一入口、鉴权、限流 | JWT 鉴权、令牌桶 / 漏桶限流、路由 | 避免各服务重复造轮子 | 限流是系统生死线,必须前置 | 控制下单、锁座请求速率 |
| 服务治理 & 配置中心 | Nacos、Consul、Sentinel | 服务发现、熔断、降级 | RT / 错误率触发熔断,动态配置 | 防止慢服务拖垮全链路 | 熔断不是失败,而是保护 | 支付、通知异常不影响下单 |
| 应用层(计算层) | FastAPI、Tornado、asyncio、uvloop | 核心业务处理 | 多进程 + 协程,I/O 全异步 | 订票系统 I/O 密集 | Python 也能支撑高并发 | 下单、锁座、订单状态流转 |
| 缓存层 | Redis、Memcached | 热点数据缓存 | 防穿透:布隆过滤器 + 空值缓存防击穿:热点 key 互斥锁防雪崩:TTL 随机化 + 异步刷新 | DB 抗压能力有限 | 缓存不是加速器,而是 DB 保护层 | 缓存余票、航班信息 |
| 消息队列层 | Kafka、RabbitMQ、RocketMQ | 异步解耦、削峰 | ACK、重试、幂等消费、顺序保证 | 同步链路越长越不稳定 | MQ 是系统的缓冲垫 | 异步下单、通知、日志 |
| 数据层 | MySQL、PostgreSQL | 数据持久化 | 主从复制、读写分离、分库分表 | 单库容量和性能有限 | 先拆读写,再拆分片 | 存储订单、用户、支付数据 |
| 搜索 & 存储层 | Elasticsearch、S3、MinIO | 搜索与对象存储 | 倒排索引、对象 Key 存储 | DB 不适合复杂搜索 | DB 管事务,ES 管搜索 | 航班搜索、订单日志 |
| 数据一致性 / 事务层 | 本地事务、TCC、Saga | 一致性保障 | 锁座 + 本地事务 → MQ → 幂等 + 补偿 | 强一致成本极高 | 最终一致性是工程常态 | 防止重复下单、库存超卖 |
| 可观测性层 | Prometheus、Grafana、ELK、Loki | 监控、日志、告警 | Metrics / Logs / Trace | 没有监控就没有运维 | P99 比平均值更重要 | 监控抢票高峰系统健康 |
| 灾备 / 高可用层 | 多活、容灾切换、演练 | 故障恢复 | 多机房、自动切流、演练 | 故障一定会发生 | 高并发 ≠ 高可用 | 单机房故障不影响订票 |
高并发系统分层架构示意图(航班订票系统)
────────────────────────── 用户 / 客户端 ──────────────────────────┐
│ Web / App / 小程序 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── DNS / 调度层 ──────────────────────────┐
│ 智能 DNS / Geo DNS │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 安全防护层 ────────────────────────────┐
│ WAF / DDoS 防护 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── CDN / LB 层 ──────────────────────────┐
│ CDN / Nginx / LVS / F5 │
│ 静态资源缓存 / 四层 / 七层负载均衡 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 接入层 / API Gateway ──────────────────┐
│ Nginx + Lua / Kong / APISIX │
│ 鉴权 / 限流 (令牌桶 / 漏桶) / 路由 / 灰度 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 服务治理 & 配置中心 ──────────────────┐
│ Nacos / Consul / Sentinel │
│ 熔断 / 降级 / 超时控制 / 动态配置 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 应用层(计算层) ──────────────────────┐
│ FastAPI / Tornado / asyncio / uvloop │
│ 核心业务逻辑 / 多进程 + 协程 │
│ 下单 → 库存锁 → 本地事务 │
│ 幂等 + MQ 异步更新 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 缓存层 ────────────────────────────────┐
│ Redis / Memcached │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ 热数据缓存 │ │ 冷数据缓存 │ │
│ │ (实时余票 / 热门航班) │ │ (历史查询) │ │
│ └───────────────┘ └───────────────┘ │
│ 防穿透:布隆过滤器 │
│ 防击穿:互斥锁 / 单 key 回源 │
│ 防雪崩:随机 TTL / 异步刷新 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 消息队列层 ────────────────────────────┐
│ Kafka / RabbitMQ / RocketMQ │
│ 异步削峰 / 下单通知 / 支付同步 / 幂等处理 / 重试机制 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 数据层 ────────────────────────────────┐
│ MySQL / PostgreSQL │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ 主库 │ │ 从库 │ │
│ │ (写入订单/支付) │ │ (航班信息 / 历史查询) │ │
│ └───────────────┘ └───────────────┘ │
│ 分库分表 / 读写分离 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 搜索 & 存储层 ─────────────────────────┐
│ Elasticsearch / S3 / MinIO │
│ 搜索 / 对象存储 / DB 与搜索解耦 │
│ 历史订单 / 航班搜索 / 日志存储 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 数据一致性 / 事务层 ────────────────────┐
│ 本地事务 / TCC / Saga │
│ 幂等 / 补偿 / 最终一致性 │
│ 下单锁座 → MQ 异步同步 → 校验库存 / 补偿 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 可观测性层 ────────────────────────────┐
│ Prometheus / Grafana / ELK / Loki │
│ 监控 / 日志 / 链路追踪 / P99 / 错误率 │
└───────────────┬───────────────────────────────────────────────┘
│
▼
────────────────────────── 灾备 / 高可用层 ────────────────────────┐
│ 多活 / 容灾切换 / 演练 / 快速切换 │
└───────────────────────────────────────────────────────────────┘
Q1:为什么查询和下单要拆?
查询是读多写少,适合缓存;下单要求强一致,必须独立。
Q2:Redis 挂了怎么办?
本地缓存兜底 + 降级返回旧数据 + 限流。
Q3:如何保证不超卖?
Redis 原子锁库存 + 订单状态机 + 超时回滚。
Python 常见算法详解
1️⃣ 排序算法
| 算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
| 冒泡排序 | 相邻元素两两比较交换 | O(n²) | O(1) | 稳定排序,优化为提前退出 | 小规模航班价格列表排序 |
| 选择排序 | 每次选择最小元素放前面 | O(n²) | O(1) | 不稳定排序 | 小规模航班号排序 |
| 插入排序 | 将元素插入已排序部分 | O(n²) | O(1) | 稳定排序,适合近乎有序数组 | 航班按时间顺序排序 |
| 快速排序 | 分治法,基准划分左右 | O(n log n) 平均 | O(log n) | 不稳定,递归实现 | 航班票价排序 |
| 归并排序 | 分治法,递归合并 | O(n log n) | O(n) | 稳定排序 | 航班按时间 + 票价多关键排序 |
| 堆排序 | 利用堆选择最大/最小 | O(n log n) | O(1) | 不稳定,适合大数据 | 航班票价 top-k |
2️⃣ 查找算法
| 算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
| 线性查找 | 遍历元素逐个匹配 | O(n) | O(1) | 简单易实现 | 航班列表简单查找 |
| 二分查找 | 有序数组对半查找 | O(log n) | O(1) | 注意边界条件 | 航班按时间查询最早航班 |
| 哈希查找 | 利用 dict / set 哈希表 | O(1) 平均 | O(n) | 哈希冲突处理 | 快速查找航班号是否售出 |
3️⃣ 字符串与数组算法
| 算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
| 滑动窗口 | 动态维护窗口 | O(n) | O(1~k) | 处理子串/子数组问题 | 航班连续优惠查询 |
| 双指针 | 两指针从两端或同向 | O(n) | O(1) | 优化数组/链表问题 | 航班列表去重、区间查询 |
| KMP / BM | 字符串模式匹配 | O(n + m) | O(m) | 高级字符串匹配算法 | 航班号匹配 / 文本搜索 |
4️⃣ 树与图算法
| 算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
| 二叉树遍历 | 递归/迭代前中后序 | O(n) | O(h) | DFS/BFS 理解 | 航班航线树结构遍历 |
| BFS / DFS | 图遍历 | O(V+E) | O(V) | 最短路径/连通分量 | 航班航线路径搜索 |
| Dijkstra | 单源最短路径 | O(E log V) | O(V) | 堆优化 | 航班最短时间路径 |
| Floyd / Bellman-Ford | 多源最短路径 | O(V³) / O(VE) | O(V²) | 可处理负权 | 航班时间优化 |
5️⃣ 贪心与动态规划
| 算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
| 贪心 | 每步局部最优 | O(n log n) | O(1~n) | 证明贪心最优性 | 航班机位最优分配 |
| 动态规划 | 子问题最优解组合 | O(n²~n³) | O(n²) | 状态转移方程 | 航班票价组合最大收益 |
| 背包问题 | 动态规划经典 | O(nW) | O(nW) | 完整推导 | 航班优惠券最大化使用 |
6️⃣ 堆与优先队列
| 算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 面试加分点 | 实战场景 |
|---|---|---|---|---|---|
| 堆排序 | 大根堆/小根堆 | O(n log n) | O(1) | heapq 模块 | 航班票价 top-k |
| 优先队列 | 插入 O(log n),取最小 O(1) | O(log n) | O(n) | 异步任务调度 | 航班延误优先处理 |
数据库相关面试题
1️⃣ 数据库基础
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| SQL vs NoSQL | SQL: 关系型,结构化;NoSQL: 非关系型,灵活 | SQL: 表、行、列、ACID;NoSQL: KV、文档、列族、图 | 分布式事务 vs 弱一致性 | 错选数据库导致性能瓶颈 | 航班信息使用 MySQL,缓存票价用 Redis |
| 主从复制 | 主库写,从库读 | binlog + replication thread | 异地备份,提高读吞吐 | 主从延迟导致脏读 | 查询航班库存读从库 |
| 事务特性 ACID | 原子性、一致性、隔离性、持久性 | InnoDB 事务实现机制 | 面试可结合隔离级别 | 不懂隔离级别导致脏读、幻读 | 下单事务处理 |
| SQL 注入防护 | 参数化查询 / ORM | ORM 底层生成预编译 SQL | 提到 Python DB API 规范 | 拼接字符串 SQL 易被注入 | Django ORM 或 SQLAlchemy |
| 索引类型 | B-tree、Hash、Full-text、Bitmap | B-tree 平衡树,高效范围查询 | 讲主键、唯一索引、复合索引 | 滥用索引反而降低写入性能 | 航班号查询加主键索引 |
2️⃣ 高级查询与优化
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 联表查询优化 | 使用 join 而不是子查询 | MySQL 执行计划 | 使用 explain 分析 | select * 导致全表扫描 | 航班 + 舱位表查询 |
| 分页查询优化 | limit offset → 大数据量慢,推荐 keyset 分页 | 索引扫描 vs 全表扫描 | 深入 keyset 分页 | 大表 offset 导致全表扫描 | 票务列表翻页 |
| 索引覆盖 | 索引字段包含查询字段,可直接查索引 | 减少回表 | explain 显示 index-only | 字段不在索引导致回表 | 查询热门航班统计 |
| 查询缓存 | Redis / Memcached | 减少数据库压力 | 热点航班缓存 | 数据不一致 | 查询航班价格缓存 30 秒 |
| 批量写入 | insert ... values (...),(...),... 或 executemany | 减少网络开销 | 提高写入吞吐 | 一条一条 insert 慢 | 航班批量导入 |
3️⃣ 数据库事务与并发
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 事务隔离级别 | READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE | 锁粒度:行锁、表锁 | InnoDB 默认 REPEATABLE READ | 不同隔离级别导致脏读、幻读、不可重复读 | 下单扣库存 |
| 乐观锁 vs 悲观锁 | 乐观锁:版本号/时间戳;悲观锁:select ... for update | 并发冲突处理 | 讲解适用场景 | 乐观锁重试次数不足 | 多用户同时抢票 |
| 行级锁 vs 表锁 | 行锁粒度小,吞吐高;表锁阻塞 | InnoDB 锁机制 | 锁等待和死锁 | 死锁未处理 | 航班库存锁定 |
| 并发控制 | select for update + Redis 原子操作 | 跨数据库事务 | 分布式锁实现 | 锁粒度太粗 | 机票预定下单 |
| 数据库死锁处理 | 捕获异常重试 | InnoDB 自动检测死锁 | 设计重试策略 | 死锁未处理导致下单失败 | 高并发抢票场景 |
4️⃣ 分布式与高可用
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 分库分表 | 按航班/日期/用户划分 | 水平切分,减少单表压力 | 读写分离设计 | 跨库 join 慢 | 大规模航班表 |
| 分布式事务 | TCC / 2PC / Saga | 保证多库操作一致 | 异步补偿策略 | 复杂度高 | 下单 + 支付 + 航司接口 |
| 高可用 HA | 主从切换、双活 | keepalived + heartbeat | 主从切换自动化 | 数据延迟导致脏读 | 机票查询服务高可用 |
| 缓存与数据库一致性 | 缓存穿透 / 击穿 / 雪崩 | Redis + Bloom filter + TTL | 缓存更新策略 | 数据不一致 | 航班票价缓存 |
| 数据库性能监控 | slow query log、explain | 找瓶颈 | 索引、查询优化 | 不监控导致慢查询累积 | 高频查询航班表优化 |
5️⃣ ORM 与 Python 操作数据库
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| ORM 优点 | 抽象 SQL,方便 CRUD | 内部生成 SQL | 避免 SQL 注入 | 不合理 ORM 导致 N+1 查询 | Django ORM 查询航班 |
| ORM 缺点 | 性能差,复杂 join 慢 | 生成 SQL 不总最优 | 结合 raw SQL | N+1 问题 | 批量导入航班 |
| 事务操作 | with session.begin(): | 自动 commit/rollback | 自动管理事务 | session 未关闭导致连接泄漏 | 下单操作 |
| 批量操作 | session.bulk_insert_mappings | 减少循环 insert | 提高性能 | ORM 循环 insert 慢 | 航班批量导入 |
6️⃣ 面试常问数据库综合题
-
如何设计高并发机票库存表?
→ 分库分表 + Redis 原子扣库存 + 事务 + MQ 异步通知 -
机票查询 QPS 万级,如何保证性能?
→ CDN + 本地缓存 + Redis + 从库 + 限流 -
下单超卖问题如何避免?
→ Redis 原子操作 + 乐观锁/悲观锁 + 事务 -
高并发支付失败如何保证一致性?
→ 分布式事务/Saga + 异步补偿 + MQ -
数据库迁移或升级如何不影响业务?
→ 双写策略 + 灰度切换 + 回滚方案
Redis 高级面试题
1️⃣ Redis 基础与数据类型
Redis数据库
通常局限点来说,Redis也以消息队列的形式存在,作为内嵌的List存在,满足实时的高并发需求。在使用缓存的时候,redis比memcached具有更多的优势,并且支持更多的数据类型,把redis当作一个中间存储系统,用来处理高并发的数据库操作
- 速度快:使用标准C写,所有数据都在内存中完成,读写速度分别达到10万/20万
- 持久化:对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上,主要有两种策略,一是根据时间,更新次数的快照(save 300 10 )二是基于语句追加方式(Append-only file,aof)
- 自动操作:对不同数据类型的操作都是自动的,很安全
- 快速的主--从复制,官方提供了一个数据,Slave在21秒即完成了对Amazon网站10G key set的复制。
- Sharding技术: 很容易将数据分布到多个Redis实例中,数据库的扩展是个永恒的话题,在关系型数据库中,主要是以添加硬件、以分区为主要技术形式的纵向扩展解决了很多的应用场景,但随着web2.0、移动互联网、云计算等应用的兴起,这种扩展模式已经不太适合了,所以近年来,像采用主从配置、数据库复制形式的,Sharding这种技术把负载分布到多个特理节点上去的横向扩展方式用处越来越多。
Redis缺点
- 是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
- Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| Redis 数据类型 | string, list, set, sorted set, hash, bitmap, hyperloglog, stream | 内存存储,基于字典 + skip list | 了解每种类型适用场景 | 错用数据类型导致性能下降 | 航班票价缓存用 string,排行榜用 sorted set |
| String 操作 | set/get/incr/decr | 字符串 + 原子操作 | incr/decr 可实现计数器 | 超长字符串导致内存爆炸 | 票价访问计数 |
| List 操作 | lpush/rpush/lpop/rpop/lrange | 链表实现 | 队列/栈应用 | lrange 全表扫描 | 异步任务队列 |
| Hash 操作 | hset/hget/hgetall | hash table | 节省内存,适合存对象 | hgetall 大 hash 内存占用大 | 航班信息对象存储 |
| Set 操作 | sadd/srem/sismember/sinter/sunion | hash table | 去重、交集/并集 | 大量交集操作慢 | 用户已选航班去重 |
| Sorted Set | zadd/zrange/zscore | skip list + hash table | 排行榜、优先级队列 | 大量 score 频繁更新慢 | 航班热度排行榜 |
2️⃣ Redis 高级特性
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| TTL / Key 过期 | expire/setex | 内部惰性删除 + 定期删除 | 缓存自动失效 | 大量过期键可能触发删除慢 | 航班票价缓存 30s |
| Redis 持久化 | RDB / AOF / 混合 | RDB 快照,AOF 追加日志 | 数据安全 vs 性能权衡 | AOF 文件过大 | 机票历史价格持久化 |
| 发布/订阅 | pub/sub | 消息发送到 channel | 简单消息通知 | 不持久化,订阅者掉线消息丢失 | 航班变动通知 |
| 事务 | MULTI/EXEC/DISCARD/WATCH | 命令队列 + 原子执行 | watch 实现乐观锁 | 事务内命令失败不会回滚 | 扣库存 + 写日志原子操作 |
| Lua 脚本 | EVAL | 原子执行 + 避免网络延迟 | 跨命令原子操作 | 脚本错误导致事务失败 | 扣库存 + 记录订单原子执行 |
3️⃣ Redis 高并发与缓存策略
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 缓存穿透 | 使用布隆过滤器 | 请求不存在直接过滤 | 减少 DB 压力 | 未用布隆可能大量空查询 | 查询不存在航班缓存 |
| 缓存击穿 | 加锁 / 单点缓存 | 热点 key 并发访问 | 互斥锁 + 防止 DB 压力暴涨 | 锁粒度太粗 | 热门航班票价查询 |
| 缓存雪崩 | key 同时过期 | 分散 TTL 或互斥锁 | 避免大量 key 同时失效 | TTL 全一致 | 航班票价批量刷新 |
| 高并发扣库存 | Lua 原子脚本 | 避免 race condition | 原子操作,性能高 | 不用 Lua 可能超卖 | 抢票系统 |
| 分布式锁 | setnx + expire / RedLock | 单点锁 / 多节点锁 | 防止超卖 | 死锁、锁失效 | 订单扣库存 + 支付 |
4️⃣ Redis 集群与高可用
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 主从复制 | 主库写,从库读 | 异步复制,延迟 | 提高读吞吐量 | 从库延迟导致脏读 | 航班查询读从库 |
| 哨兵模式 | sentinel 监控 + 自动 failover | 自动主从切换 | 高可用 | sentinel 异常导致切换不及时 | Redis 高可用部署 |
| Redis Cluster | slot 分片 + 多节点 | hash slot + 迁移 | 水平扩展 | hash slot 不均衡导致热点 | 航班大表分片 |
| 分布式锁 RedLock | 多节点 setnx + expire | 保证跨节点锁 | 防止单点失效 | 节点网络延迟可能导致误解锁 | 高并发库存扣减 |
5️⃣ Python 与 Redis 实战
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| Python 操作 Redis | redis-py 库:StrictRedis/Redis | 连接池 + 命令封装 | connection pool 提高性能 | 不关闭连接导致泄漏 | 航班票价缓存 |
| 缓存 + DB 同步 | Cache Aside / 读写穿透 | 先读缓存,没命中查 DB 更新缓存 | 缓存策略优化 | 多线程同时更新缓存可能超卖 | 查询航班票价 + 更新缓存 |
| 分布式锁 | Python + Redis Lua 脚本 | 原子操作,避免 race | 设置合理 TTL | 锁没释放,死锁 | 下单扣库存 |
| 批量操作 | pipeline | 减少网络开销 | 提高吞吐量 | 忘记 execute | 批量更新票价 |
Docker 高级面试题
1️⃣ Docker 基础概念
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| Docker 与虚拟机区别 | Docker 轻量级容器,共享宿主内核;VM 独立内核,资源开销大 | 容器利用 Linux Namespace + Cgroups 实现隔离 | 讲解 Namespace (PID, NET, MNT, IPC) | 容器误以为完全隔离内核 | Python 服务部署,节省资源 |
| Docker 镜像与容器区别 | 镜像是静态只读模板,容器是镜像运行时实例 | copy-on-write 文件系统 | 镜像版本管理 | 容器误删导致数据丢失 | 航班票价 API 服务镜像化 |
| Dockerfile 基础指令 | FROM, RUN, COPY, ADD, CMD, ENTRYPOINT, ENV | 层级构建,每层缓存 | 多阶段构建优化镜像大小 | CMD 与 ENTRYPOINT 混用错误 | Python 微服务镜像构建 |
2️⃣ Docker 镜像与构建优化
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 镜像分层机制 | 每条 Dockerfile 指令形成一层,分层缓存 | copy-on-write | 利用缓存加速构建 | 每条 RUN 指令增加层 | Python 应用多阶段构建,减少 50% 镜像大小 |
| 镜像体积优化 | 使用轻量基础镜像、合并 RUN 指令、多阶段构建 | 每层文件系统叠加 | Alpine / slim 镜像 | RUN apt-get install 多行导致层增大 | Python Flask 服务 |
| Docker Compose | 多容器编排,定义网络和依赖 | Compose 文件解析,生成容器网络 | 服务依赖顺序,环境变量配置 | 端口冲突、依赖未启动 | MySQL + Redis + Python API 组合部署 |
3️⃣ Docker 容器运行与管理
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 容器启动方式 | docker run -d -p, CMD / ENTRYPOINT | Namespace 隔离进程,Cgroups 限制资源 | 使用 --restart 保持高可用 | 忘记挂载 volume 导致数据丢失 | Python API 容器化运行 |
| 容器网络模式 | bridge, host, overlay, none | NAT + iptables | 理解端口映射、跨主机通信 | host 模式安全风险 | 微服务之间通信 |
| 数据卷 (volume) | 持久化数据,独立于容器生命周期 | bind mount / volume | 避免数据丢失 | 容器删除数据丢失 | MySQL 数据持久化 |
4️⃣ Docker 高级特性
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 镜像缓存与加速 | 利用层级缓存,加速构建 | 分层存储 | registry 镜像加速 | 每次修改文件导致整个层重新构建 | Python 服务 CI/CD |
| 健康检查 | HEALTHCHECK 指令 | 容器自我状态检测 | 自动重启 unhealthy 容器 | 健康检查脚本返回值错误 | Python API 健康探针 |
| 多阶段构建 | 使用多个 FROM,减少最终镜像 | 构建阶段与运行阶段分离 | 减少依赖和镜像体积 | 忘记复制二进制到最终镜像 | Python 打包部署 |
5️⃣ Docker 与高可用 / DevOps
| 题目 | 核心答案 | 底层原理 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| 容器编排 | Docker Compose / Kubernetes | 多容器调度 | 了解 k8s pod、service、deployment | 端口冲突、依赖未就绪 | Python 微服务部署 |
| CI/CD 与 Docker | 自动构建镜像 + 发布容器 | Dockerfile + Registry + Runner | 镜像版本化,流水线 | 镜像未打 tag,发布混乱 | GitLab CI/CD + Python API |
| 日志管理 | docker logs / volume 持久化 | stdout/stderr 重定向 | 集中化日志收集 | 容器日志丢失 | Python API 统一日志到 ELK |
6️⃣ Python 与 Docker 实战
| 题目 | 核心答案 | 原理解析 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
| Python 应用容器化 | Dockerfile + virtualenv 或 venv | 每层独立运行环境 | 避免依赖冲突 | 容器内未安装依赖 | Flask/FastAPI 服务容器化 |
| Python 与 Redis/MySQL 容器化 | 多容器网络 | 使用 docker-compose | 配置环境变量 | 容器未链接网络 | Python API + Redis + MySQL |
| 体积优化 | 多阶段构建 + slim/alpine | 减少镜像层 | 构建缓存 | Python 编译依赖未清理 | Python 数据处理服务 |
Git面试题
1️⃣Git 常用命令
| 命令 | 核心作用 | 原理/补充说明 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
git init | 初始化仓库 | 创建 .git 目录,建立对象数据库 | 讲解 Git 数据结构 | 初始化目录错误 | 新项目 Python 服务 |
git clone <repo> | 克隆远程仓库 | 复制对象数据库 + HEAD | 指定分支 clone:-b | 未指定 branch 拉取默认分支 | 拉取团队仓库 |
git status | 查看工作区/暂存区状态 | 对比 HEAD、索引、工作区 | 快速检查修改 | 忽略 .gitignore 文件 | 开发前检查 |
git add <file> | 暂存修改 | 更新 index | 区分工作区和暂存区 | 忘记 add,commit 无效 | 提交航班模块修改 |
git commit -m "msg" | 提交到本地版本库 | 生成 commit 对象 | message 规范化 | message 不明确 | 保存功能更新 |
git log | 查看提交历史 | DAG + SHA-1 | --oneline, --graph 展示 | 历史太多未分页 | 查看 bug 提交历史 |
git diff | 对比修改内容 | 工作区 vs 暂存区 | 查看变更细节 | 忘记 add 影响 diff | 查看航班逻辑修改差异 |
git branch | 查看/创建分支 | 分支是指针 | git branch -a 显示远程 | branch 名混乱 | 创建 feature 分支 |
git checkout <branch> | 切换分支 | 更新 HEAD | -b 创建并切换 | 切换前未 stash 导致修改丢失 | 切换开发/测试分支 |
git merge <branch> | 合并分支 | DAG 合并 | fast-forward vs 3-way merge | 冲突未解决 | feature 合并到 master |
git rebase <branch> | 变基 | 线性化 commit 历史 | 理解冲突解决 | 历史被修改 | 整理 feature 分支历史 |
git pull | 拉取远程更新并合并 | fetch + merge | 避免直接 push 冲突 | 未 stash 导致冲突 | 同步团队更新 |
git fetch | 拉取远程更新,不合并 | 更新远程引用 | 可以单独检查 | 不合并误以为本地更新 | 先 fetch 再 rebase |
git push | 推送到远程仓库 | 更新远程分支 | --force 谨慎使用 | 强制 push 可能覆盖别人提交 | 发布 Python 功能模块 |
git reset | 回退 commit 或暂存区 | reset --soft/mixed/hard | 区分回退范围 | hard 可能丢失修改 | 回退误提交 commit |
git revert | 新增 commit 撤销历史 | DAG 操作 | 安全撤销 | 忽略 revert 会累积 | 撤销线上 bug commit |
git stash | 临时保存修改 | stack 保存快照 | 多任务切换 | 忘记 pop 导致修改丢失 | 切换分支前 stash 当前修改 |
git tag | 标签版本 | 指向 commit | 发布版本管理 | 忘记推送远程 | v1.0 发布航班服务 |
git remote | 查看/管理远程仓库 | 保存远程引用 | origin/add/remove | URL 配置错误 | 多仓库协作 |
git cherry-pick <commit> | 选择单个 commit 应用 | 复制 commit 对象 | 修复 bug | 忽略依赖 commit | 热修复 |
git reflog | 查看 HEAD 历史操作 | 本地操作日志 | 恢复误操作 | 忘记 reflog | 恢复误删分支 |
git clean -f | 清理未追踪文件 | 删除工作区未追踪文件 | - | 删除重要文件 | 清理临时生成文件 |
git blame <file> | 查看每行修改记录 | 注释每行 commit | 定位责任人 | large file 慢 | 查找航班功能修改责任人 |
git diff --staged | 查看已暂存修改 | 对比 index 与 HEAD | - | 混淆未暂存修改 | 检查 commit 内容 |
git log --graph --oneline --all | 图形化提交历史 | DAG 可视化 | 面试加分 | log 太长未分页 | 分支合并查看历史 |
2️⃣Git 高级常用操作命令
| 命令 | 核心作用 | 原理/补充说明 | 面试加分点 | 典型坑 | 实战案例 |
|---|---|---|---|---|---|
git remote -v | 查看远程仓库列表和 URL | 显示 fetch/push 地址 | 理解远程仓库管理 | 混淆 origin 与 upstream | 查看项目仓库源 |
git remote add <name> <url> | 添加远程仓库 | 创建远程引用 | 多远程仓库协作 | URL 错误导致 push/pull 失败 | Python 服务关联多个仓库 |
git remote set-url <name> <newurl> | 修改远程仓库 URL | 更新远程引用 | GitHub/GitLab 切换仓库 | 忘记更新 origin | 项目迁移到新仓库 |
git remote remove <name> | 删除远程仓库 | 删除远程引用 | 清理无用远程 | 删除错误远程 | 清理废弃仓库 |
git clone <repo> [<dir>] | 克隆仓库,可指定目录 | copy 对象 + checkout | 指定分支 clone: -b | 克隆大仓库慢 | 拉取航班 API 仓库到指定目录 |
git checkout <branch> | 切换本地分支 | HEAD 指针切换 | -b 创建新分支 | 未保存修改导致丢失 | 切换 feature 分支开发 |
git checkout -b <branch> | 创建并切换分支 | 指针新建 + HEAD 切换 | 快速创建分支 | 同名分支报错 | 新功能开发 |
git branch -a | 查看本地 + 远程分支 | 显示所有分支引用 | - | 不清楚远程分支 | 查看团队分支结构 |
git branch -r | 查看远程分支 | 显示 origin/* | 分支同步管理 | 误以为本地存在 | 拉取远程 hotfix 分支 |
git fetch <remote> | 拉取远程更新,不合并 | 更新远程引用 | 先 fetch 再 rebase | 忘记 merge/pull | 拉取最新远程分支 |
git pull <remote> <branch> | 拉取并合并远程分支 | fetch + merge | 避免直接 push 冲突 | 未 stash 导致冲突 | 同步远程 master 分支 |
git push <remote> <branch> | 推送本地分支到远程 | 更新远程引用 | push 新分支时加 -u | push 被拒绝 | 发布 feature 分支 |
git push -u origin <branch> | 推送并设置上游分支 | 建立跟踪关系 | 下次可直接 git push | 忘记设置 -u 需指定远程 | 新创建分支推送远程 |
git fetch --all | 拉取所有远程更新 | 更新所有远程分支 | 大型项目多远程仓库 | 未检查本地分支 | 拉取团队所有更新 |
git branch -d <branch> | 删除本地分支 | 指针删除 | 确保合并到主分支 | 删除未 merge 分支报错 | 清理完成的 feature 分支 |
git push <remote> --delete <branch> | 删除远程分支 | 删除远程引用 | 清理废弃分支 | 删除错误分支 | 删除已合并 feature 分支 |
git config --global user.name/email | 配置用户名/邮箱 | 保存配置文件 | 面试可展示规范操作 | 忘记配置导致 commit 不规范 | 新环境配置 |
git init | 初始化仓库 | 创建 .git 目录 | 从零开始 | 误操作目录 | 新 Python 项目版本管理 |
git log --oneline --graph --all | 查看所有分支图形化历史 | DAG 可视化 | 快速定位 commit | 历史太长未分页 | 多分支合并查看历史 |
Python 编程题整理
1️⃣ 台阶问题 / 斐波那契
题目:
一只青蛙一次可以跳 1 级或 2 级台阶,求跳上 n 级台阶的总跳法。
思路:
-
递归:
f(n) = f(n-1) + f(n-2) -
记忆化递归:缓存中间结果
-
循环迭代:动态规划
实现:
# 递归
fib = lambda n: n if n <= 2 else fib(n-1) + fib(n-2)
# 记忆化递归
def memo(func):
cache = {}
def wrap(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrap
@memo
def fib(n):
if n < 2:
return 1
return fib(n-1) + fib(n-2)
# 循环迭代
def fib(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return b
面试加分点:
-
对比递归、记忆化、迭代时间复杂度
-
斐波那契公式 / 动态规划优化
2️⃣ 变态台阶问题
题目:
青蛙一次可以跳 1~n 级台阶,求总跳法。
思路:
-
动态规划:
f(n) = 2 * f(n-1)
fib = lambda n: n if n < 2 else 2 * fib(n - 1)
3️⃣ 矩形覆盖问题
题目:
用 21 小矩形覆盖 2n 大矩形,无重叠。
思路:
-
动态规划:
f(n) = f(n-1) + f(n-2)
f = lambda n: 1 if n < 2 else f(n-1) + f(n-2)
4️⃣ 杨氏矩阵查找
题目:
二维数组每行递增,每列递增,判断是否存在某整数。
思路:
-
Step-wise 线性搜索,从右上角开始
def find(matrix, x):
m, n = len(matrix)-1, len(matrix[0])-1
r, c = 0, n
while c >= 0 and r <= m:
if matrix[r][c] == x:
return True
elif matrix[r][c] > x:
c -= 1
else:
r += 1
return False
5️⃣ 去除列表重复元素
方法:
# 用集合
l2 = list(set(l1))
# 用字典
l2 = list({}.fromkeys(l1))
# 保持顺序
l2 = list(dict.fromkeys(l1))
# 列表推导式
l2 = []
[l2.append(i) for i in l1 if i not in l2]
6️⃣ 链表成对调换
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def swapPairs(self, head):
if head and head.next:
next = head.next
head.next = self.swapPairs(next.next)
next.next = head
return next
return head
7️⃣ 创建字典的方法
# 直接创建
d1 = {'name':'earth', 'port':'80'}
# 工厂方法
items = [('name','earth'),('port','80')]
d2 = dict(items)
# fromkeys
d3 = {}.fromkeys(('x','y'), -1)
8️⃣ 合并两个有序列表
循环法:
def merge_sortedlist(a, b):
c = []
while a and b:
if a[0] <= b[0]:
c.append(a.pop(0))
else:
c.append(b.pop(0))
c.extend(a)
c.extend(b)
return c
递归法:
def _recursion_merge(l1, l2, tmp):
if not l1 or not l2:
tmp.extend(l1 or l2)
return tmp
if l1[0] < l2[0]:
tmp.append(l1[0])
return _recursion_merge(l1[1:], l2, tmp)
else:
tmp.append(l2[0])
return _recursion_merge(l1, l2[1:], tmp)
def recursion_merge_sort(l1, l2):
return _recursion_merge(l1, l2, [])
9️⃣ 链表求交点
思路:
-
对齐长度 → 同步遍历 → 找到相同节点
def node(l1, l2):
len1 = len2 = 0
head1, head2 = l1, l2
while head1.next: head1 = head1.next; len1 += 1
while head2.next: head2 = head2.next; len2 += 1
if head1.next != head2.next: return None
# 长链先走
head1, head2 = l1, l2
if len1 > len2:
for _ in range(len1 - len2): head1 = head1.next
else:
for _ in range(len2 - len1): head2 = head2.next
while head1 and head2:
if head1.next == head2.next: return head1.next
head1 = head1.next
head2 = head2.next
1️⃣0️⃣ 二分查找
def binary_search(lst, item):
low, high = 0, len(lst)-1
while low <= high:
mid = low + (high - low)//2
if lst[mid] == item:
return mid
elif lst[mid] < item:
low = mid + 1
else:
high = mid - 1
return None
1️⃣1️⃣ 快速排序
def quicksort(lst):
if len(lst) < 2: return lst
pivot = lst[0]
less = [i for i in lst[1:] if i <= pivot]
greater = [i for i in lst[1:] if i > pivot]
return quicksort(less) + [pivot] + quicksort(greater)
1️⃣2️⃣ 找零问题(动态规划)
def coinChange(values, money):
coinsUsed = [0]*(money+1)
for cents in range(1, money+1):
minCoins = cents
for v in values:
if v <= cents:
minCoins = min(minCoins, coinsUsed[cents-v]+1)
coinsUsed[cents] = minCoins
return coinsUsed[money]
1️⃣3️⃣ 二叉树遍历
class Node:
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
# 层次遍历 BFS
def level_traversal(root):
row = [root]
while row:
print([n.data for n in row])
row = [kid for node in row for kid in (node.left, node.right) if kid]
# 深度遍历 DFS
def deep(root):
if not root: return
print(root.data)
deep(root.left)
deep(root.right)
前序 / 中序 / 后序遍历:只改变打印顺序即可
1️⃣4️⃣ 树相关问题
-
求最大树深:
def maxDepth(root):
if not root: return 0
return max(maxDepth(root.left), maxDepth(root.right)) + 1
-
判断两棵树是否相同:
def isSameTree(p, q):
if not p and not q: return True
if p and q:
return p.val==q.val and isSameTree(p.left,q.left) and isSameTree(p.right,q.right)
return False
-
前序 + 中序求后序:
def rebuild(pre, center):
if not pre: return
root = Node(pre[0])
idx = center.index(pre[0])
root.left = rebuild(pre[1:idx+1], center[:idx])
root.right = rebuild(pre[idx+1:], center[idx+1:])
return root
1️⃣5️⃣ 单链表逆置
def rev(head):
prev, cur = None, head
while cur:
nxt = cur.next
cur.next = prev
prev = cur
cur = nxt
return prev
1️⃣6️⃣ 字符串是否为变位词(Anagram)
# 方法1:计数比较
def isAnagram1(s1,s2):
return sorted(s1) == sorted(s2)
# 方法2:计数数组
def isAnagram2(s1,s2):
c1 = [0]*26
c2 = [0]*26
for ch in s1: c1[ord(ch)-ord('a')] += 1
for ch in s2: c2[ord(ch)-ord('a')] += 1
return c1==c2
1865

被折叠的 条评论
为什么被折叠?



