面经内容整理和学习
1.语言相关
1.1 Python
1.1.1 Python的类方法(实例方法、静态方法、类方法)
这三种方法在Python中都有自己的特征。
在类定义的时候,类内方法的实例方法,通常不需要使用装饰器,并且传参的时候第一项是self关键字。self指的是实例,在调用该类方法的时候,相当于调用了
self.func()
。
而对于类方法的话,通常需要
@classmethod
装饰器来修饰,并且函数传入的参数必须有类本身,常用cls
。
对于静态方法的话,需要使用
@staticmethod
装饰器来进行装饰。并且可以不传入参数。
实例方法 | 静态方法 | 类方法 | |
---|---|---|---|
装饰器 | 无 | @staticmethod | @classmethod |
参数 | self | 无 | cls |
行为 | 将实例作为参数进行传递 | 可以直接通过类或者使用实例调用 | 不与类或实例绑定 |
场景 | 解决和对象绑定问题 | 管理类中的数据,对数据进行重构 | 验证数据等 |
1.1.2 进程与线程
多进程
Python使用多进程在Windows端常用模块为
multiprocessing
,该模块提供了Process
类来代表一个进程对象。即创建一个Process
实例,然后使用start()
方法启动进程。并且常常使用join()
方法,该方法保证主进程会等待子进程结束后再执行下一步,常常用于进程间的同步。同样,若要启动大量子进程,可以使用进程池的方式批量创建子进程,同样,该方法也是用
join()
方法来等待子程序结束,但是区别是在使用join()
之前必须要调用close()
,在调用close()
后就无法在添加新的进程了。值得注意的是,Pool()
的大小取决于CPU的核数。Python中进程间的通信方法常用
Queue
与Pipes
方式来实现,其中Queue
常用的方法有put
与get
。在使用多线程的时候,往往牵扯到锁的使用。多线程中所有的变量都由所有的线程共享,任何一个变量都可以被任何一个线程修改。因此,线程之间的共享数据最大的危险在于多个线程同时修改一个变量。为了保证共享数据的安全性,就需要给当前变量操作上一把锁,即当某个进程要执行某些操作时,必须时获得锁的状态。并且此时,别的线程不能同时执行这个操作,除非锁释放了。Python中通常使用threading.Lock()
来实现。将其实例化后,可以使用lock.acquire()
来获取锁。而且,锁在使用完毕后一定要释放,因此常用try....finally
结构。由于Python中每个线程都会持有自己的局部变量,因此需要传参的时候,代码往往非常冗余。Python提供了
ThreadLocal
方法,在实例化后得到的对象可以看成全局变量,但是当线程实例使用它时,它对每个线程都是局部变量。
多线程
Python中的多线程主要是靠
_thread
和threading
模块提供,通常使用threading
模块,使用时,将一个函数传入并创建Thread
实例,然后使用start()
开始执行。
1.1.3 多进程+协程的使用以及为何使用
首先,介绍什么是协程:
协程是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换的时候,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态。
用协程的好处在于避开进程、线程的系统调用设计问题,避开抢占式调度执行顺序无法确定的问题。协程是由用户自己来编写调度逻辑的,对CPU来说,它是单线程的,所以CPU不用去考虑怎么调度、切换上下文,甚至不需要多线程的锁机制,省去了CPU的开销。
协程和多线程比较起来有如下优势:
- 协程执行效率极高,因为子程序切换不是线程切换,而是程序自身控制,没有线程切换的开销,尤其是在线程数量比较多的时候,协程的性能优势就越明显;
- 不需要多线程的锁机制,因为协程运行的时候相当于单线程,不存在同时写变量的冲突,控制共享资源不需要锁,只需要判断状态就可。
由于协程是单线程,因此要使用多个CPU需要使用到多进程+协程的方式。协程是使用生成器来实现得,在使用时C端向S端通过send()
方法传递消息。但是在进行send()
操作前,需要将协程进行预激处理,即需要通过next(gen)
或者gen.send(None)
来激活协程。不过在通常情况下可以直接使用装饰器来进行自动的预激处理,常用的有coroutine()
方法。值得注意的是,如果使用yield from
来实现的协程,是与这些装饰器冲突的,因为其会自动预激。
协程还有一项任务是终止协程和异常处理:
协程中未处理的异常会向上冒泡,传给
next
或者send
方法的调用方。因此终止协程可以使用某个哨符值,让协程退出。在Python中提供了在客户代码可以直接调用的方法,generator.throw
以及generator.close
。
使用多进程+协程的原因在于,既可以充分利用多核,又可以充分发挥协程的高效率。
1.1.4 进程通信与线程通信
进程 | 线程 | |
---|---|---|
管道 | 锁 | |
消息队列 | 信号量 | |
共享内存 | 信号 | |
信号 | ||
信号量 |
2.数据库相关
2.1 Mysql
2.1.1 索引
2.1.1.1 索引的实现方式
2.1.1.2 聚簇索引与非聚簇索引
什么是聚簇索引:
表数据按照索引的顺序来存储,也就是说索引项的顺序与表中记录的物理顺序一致。对于聚集索引,叶子结点就是存储了真实的数据行的节点,不再有单独的数据页。在一张表上最多只能创建一个聚簇索引。
什么是非聚簇索引:
表数据存储与索引顺序无关,对于非聚簇索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针。
总的来说,聚簇索引是一种稀疏索引,数据页上一级的索引页存储的是页指针,而不是行指针。而非聚簇索引是密集索引,在数据页的上一级索引页,它为每一个数据行存储一条索引记录。
2.1.2 事务
事务其实就是由多条数据库操作语句构成的。主要用于处理操作量大、复杂度高的数据。
事务主要拥有四个特性,即ACID:
- 原子性:操作不可拆分,要么全成功,要么全部失败进行回滚。
- 一致性:事务开始前和结束后,数据库的完整性没有遭到破坏。总是从一个一致性状态转换到另一个一致性状态。
- 隔离性:一个事务所做的修改在最终提交前,对其他事务都是不可见的。
- 持久型:一旦事务提交,则其所做的修改会永久到保存到数据库中。
其中由并发事务带来一系列的问题:
- 更新丢失:当两个或多个事务选择同一行,然后基于最初选定的值更新行的时候,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题。即最后更新会覆盖由其他事务进行的更新。
- 脏读:一个事务正在对一条记录进行修改,再还没有提交这次修改之前,另一个事务也来读取这一条数据,若没合适的控制机制,第二个事务就会读取这些不一致状态下的数据。
- 不可重复读:一个事务在读取某些数据后的某个时间,再次读取该数据,此时读出的数据可能已经发生了变化,或者某些记录已经被删除了。
- 幻读:一个事务按之前相同的查询条件重新读取之前检索过的数据,却发现其他事务插入了满足其查询条件的新数据。
根据上述问题,SQL标准定义了4类隔离级别:
-
Read Uncommitted(读取未提交内容)
该级别会引发脏读问题,即读取到未提交的数据。该级别中,所有事物都可以看到其他未提交事务的执行结果。
-
Read Committed(读取提交内容)
该级别满足了隔离的简单定义,即一个事务只能看见已经提交事务所作的改变。这种隔离方式引发的问题是不可重读。
-
Repeatable Read(可重读)
Mysql的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据的时候,会看到同样的数据行。这个隔离级别会出现的问题是幻读。通过MVCC和间隙锁解决幻读问题。
-
Serializable(可串行化)
MVCC:多版本并发控制
MVCC是用于Read Committed与Repeatable Read下的控制机制。实际上是保存了数据在某个时间节点的快照。可以理解为对每一个表,都隐藏了两列,这两列的内容分别为create_version
与delete_version
。在事务操作过程中,每一条操作语句会对current_version
进行更新。MVCC的原理就是查找创建版本小于等于当前事务版本,删除版本为空或者大于当前事务版本的项。
2.1.3 Mysql的常用数据类型
名称 | 类型 | 说明 |
---|---|---|
INT | 整型 | 4字节整数类型 |
BIGINT | 长整型 | 8字节整数类型 |
REAL | 浮点型 | 4字节浮点数 |
DOUBLE | 浮点型 | 8字节浮点数 |
CHAR(N) | 定长字符串 | 指定长度字符串 |
VARCHAR(N) | 变长字符串 | 存储可变长度字符 |
BOOLEAN | 布尔类型 | True或False |
DATE | 日期类型 | 存储时间,如2020-12-12 |
TIME | 时间类型 | 存储时间,如16:31:22 |
DATETIME | 日期和时间类型 | 上面两项的组合 |
2.1.4 drop、delete与truncate的区别
名称 | 作用 |
---|---|
drop | 直接删掉表 |
delete | 删掉的是表中的数据,事务会记录 |
truncate | 删除表中数据,可以在后面添加where语句。不会记录,删除行无法恢复。该关键字只能对某个表进行操作。 |
2.2 数据库设计的三范式
- 要求有主键,并且要求每一个字段原子性不可再分;
第一范式要求数据库的每一行必须唯一,也就是每个表必须有一个主键。这是数据库设计的最基本要求。
- 要求所有非主键字段完全依赖主键,不能产生部分依赖;
若违背该范式,会造成数据出现大量的冗余。此时可以改变设计为“多对多”。
- 所有非主键字段和主键字段之间不能产生传递依赖;
【例子参考】三范式例子
3.计算机网络相关
3.1 Session 和 Cookie
Cookie
Cookie是一段由Server送给使用者浏览器的一小块文档。浏览器储存他,并且在浏览器下次发送请求的时候将他送回原来的服务端。
Cookie是用来区分两个请求是否来自同一个浏览器,以此去保持使用者的登陆状态的方案。
其具备两个特性,并且向domain的服务发送请求的时候,Cookie也会一并在请求中发送:
- 只针对原本的网段起作用。
- 有生命周期,到了所设定的生命周期后会失效。
Session
Session负责记录在使用端上的使用者讯息,会在一个用户完成身份认证后,存下所需要的用户资料,接着产生一组对应的ID存入Cookie后,传回客户端。一句话就是Session是账户登陆过后,Sever端所发的识别证。
3.2 HTTP状态码
大方向上区分:
分类 | 描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作来完成请求 |
4** | 客户端错误,请求包含语法错误或者无法完成 |
5** | 服务端错误,服务器在处理请求过程中发生错误 |
具体的常问的:
名称 | 内容 |
---|---|
200 | 请求成功,一般用于get和post |
301 | 请求的资源被永久移动到的新的url |
302 | 临时移动 |
400 | 客户端请求语法错误 |
保留,将来使用 | |
服务器理解请求,但是拒绝执行 | |
服务器无法根据客户端的请求找到资源 | |
500 | 服务器内部错误,无法完成请求 |
502 | 作为网关或者代理工作的服务器尝试请求执行时,从远程服务器接收到一个无效响应 |
4.设计模式
4.1 单例模式
饿汉式
#include <iostream>
class singleton{
private:
static singleton* p;
singleton;
public:
static singleton * getInstance(){
return p;
}
}
singleton* singleton::p = new singleton();
int main(){
singleton* p = singleton::getInstance();
singleton* p2 = singleton::getInstance();
std::cout << p <<std::endl;
std::cout << p2 << std::endl;
return 0;
}
饿汉式是线程安全的实现方法,因为p在进入main函数之前就由单线程方式实例化了。
懒汉式
class singleton{
private:
static singleton* p;
singleton();
public:
static singleton* getInstance(){
if (p == NULL){
p = new singleton();
std::cout << "onece" << std::endl;
}
else std::cout << "not once" <<std::endl;
return p;
}
}
懒汉式存在线程安全问题,必须加锁。
杂项
1. 什么是计算密集型、什么是IO密集型
计算密集型任务:
计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
IO密集型任务:
涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
2.什么是线程安全
线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现错误。
3. kill -9 与 kill -15的差别
在使用kill -9 or kill -15
的时候,实际上是执行Linux中的中止信号。他们的差别主要在于:
kill -9 | kill -15 |
---|---|
立即杀死进程,并且该信号不可被中断、阻塞 | 正常退出进程,该信号可以回调或者阻塞 |