面试总结复习知识点

一、python数据类型说一下

可变:字典、列表、集合    不可变:数字、字符串、元组
有序:数字、字符串、元组、列表 无序:字典、集合

二、列表和元组有什么区别

元组与列表不同点:
语法差异:第一个不同点是元组的声明使用小括号,而列表使用方括号,当声明只有一个元素的元组时,需要在这个元素的后面添加英文逗号;
是否可变:第二个不同点是元组声明和赋值后,不能像列表一样添加、删除和修改元素。如果需要对元组做出改变只能重新开辟一块内存,重新生成新的tuple。
存储方式差异:第三个不同点因为列表是可变的,为了减少每次增加/删减元素时空间分配的开销(增减元素时为了减少分配空间的操作次数),python在分配空间时会额外分配多一些,时间复杂度为O(1)。
元组长度固定大小,元素不可变,所以存储空间固定。


注意说明:声明一个空列表分配40字节的内存,增加一个元素后,给列表分配了72字节的内存,一个字符8个字节,那就是分配了4个字符的内存空间

三、字典内部是怎么存储数据的            https://blog.csdn.net/qq_41556318/article/details/84241909

python调用内部的散列函数(哈希函数),将键(key)作为参数进行转换,得到一个唯一的地址,然后将值(value)存放在改地址中,这也可以解释为什么字典的键(key)不能重复,
如果重复就会覆盖之前的内存地址。

四、多线程和多进程的区别

进程:是操作系统进行资源分配和调度的基本单位,进程就是由一个或者多个线程构成的,进程之间的数据是分开的
线程:是CPU进行资源分配和调度的基本单位,一个进程间的线程之间数据是共享的,线程是进程中最小的运行单位
多进程:计算机上运行中的应用程序,通常称为进程。当你运行一个程序,你就启动了一个进程。每个进程都有子集独立的内存空间。
多进程就是指计算机同时执行多个进程,一般是同时运行多个软件。
多线程:多线程就是指一个进程中同时有多个线程在执行。
总结:多线程是异步的,但这不代表多线程真的是几个线程同时在运行,实际上是系统不断的在各个线程之间来回切换

学习点:

windows系统下开进程开销很大,因此windows多线程学习重点是大量面对资源争抢与同步方面的问题。

Linux系统下开进程开销很小,因此需要学习在Linux下学习重点是进程间通讯的方法

五、进程间怎么通信        https://blog.csdn.net/wm12345645/article/details/82381407

队列(管道+锁)

六、数据库怎么提高查询效率?索引建多了有什么缺点     https://weibo.com/ttarticle/p/show?id=2309404290091374752084

1.查询优化:建立索引
2.SQL语句优化
查询优化:建立索引需要注意的一些点
1.给经常需要查询的字段设置索引
2.尽量避免在where子句中对字段进行null值判断,会导致索引失效进行全表扫描,所以字段值最好不要是null值,可以写默认值0
select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值
3.对经常需要更新的字段,不建议设置索引,因为索引是按顺序排列的,改值将导致整个表记录顺序需要调整。
4.尽量避免在where子句中使用or来连接条件,如果连接的两个条件其中一个没有建索引,另一个条件的索引也会失效
如: select id from t where num=10 or num=20

可以这样查询: select id from t where num=10 union all select id from t where num=20
5.like查询注意开头不用通配符一类的,比如%,_
select id from t where name like ‘%abc%’
6.最左原则
索引建多了有什么不好:1.创建索引的时候也会创建索引文件,占用过多磁盘空间
2.索引固然可以提高select效率,但是也降低了insert效率和update效率,因为insert和update会使索引重建,所以怎么建索引需要慎重考虑

索引为什么会快

1.索引就是通过事先排好序,从而在查找时可以应用二分查找等高效率的算法。
2.一般的顺序查找,复杂度为O(n),而二分查找复杂度为O(log2n)。当n很大时,二者的效率相差及其悬殊

七、MySQL事务、锁(乐观锁、悲观锁)、四种隔离级别、脏读、幻读、不可重复读

https://www.cnblogs.com/wangcuican/p/12730205.html

八、MySQL主从配置原理,做Django读写分离

MySQL主从同步原理:主库专门做写数据,从库读数据
配置好主从信息,当主库数据发生改变
1)master会将变动记录到二进制日志(Bin Log)里面;
2)master有一个I/O线程将二进制日志发送到slave;
3) slave有一个I/O线程把master发送的二进制写入到relay日志里面;
4)slave有一个SQL线程,按照relay日志处理slave的数据;
Django中配置读写分离
在settings中DATABASES配置读写分离的数据库基本信息
在view视图中可以手动读写分离,在使用数据库时,通过using(数据库名)来手动指定要使用的数据库

九、Django请求生命周期、url是怎么找到views的、Django中间件、CSRF/CORS

浏览器页面输入URL,根据URL匹配相应的视图函数,view视图去models中取数据,models去数据库取数据,将数据返回给view视图,
view视图把需要展示的数据返回给模板,模板渲染数据就是html文件在浏览器展示。

https://www.jianshu.com/p/9e4e3195d731

url怎么找到view:基于类视图函数处理请求
先看url,Django的CBV在url中的书写是view.类名后加as_view(),函数名加括号会优先执行,as_view()返回值是view。
当用户访问url时,对应url会执行view函数,进入到dispatch方法,通过反射找到request.method对应的请求方式的类方法(get方法或者post方法),最后执行view视图中对应的方法。

Django中间件/CSRF、CORS(跨域请求)  https://www.cnblogs.com/wangcuican/p/12685056.html

十、多线程和多进程怎么开启、GIL锁

Threading模块和线程池

十一、map/reduce函数

十二、__init__,__new__

十三、深浅拷贝

https://www.cnblogs.com/wangcuican/p/11215728.html

十四、数据库的连表有哪些    https://www.cnblogs.com/wangcuican/p/11395635.html

1.inner join     #内连接,只连接两张表相同的地方
select * from emp inner join dep on emp.dep_id = dep.id;   #on后面跟的是条件

2.left join      #左连接,在内连接的基础上,保留左边表没有对应的数据
select * from emp left join dep on emp.dep_id = dep.id;

3.right join    #右连接,在内连接的基础上,保留右边表没有对应的数据
select * from emp right join dep on emp.dep_id=dep.id;

4.union        #全连接,就是左连接和右连接用union连起来,但是不包括重复行
select * from emp left join dep on emp.dep_id = dep.id
union
select * from emp right join dep on emp.dep_id = dep.id;

5.union all    #左连接和右连接用union连起来,包括重复行,即所有结果
select * from emp left join dep on emp.dep_id = dep.id
union all
select * from emp right join dep on emp.dep_id = dep.id;

十五、装饰器、迭代器   https://www.cnblogs.com/wangcuican/p/12709397.html

装饰器定义:在不改变被装饰对象的源代码和调用方式的前提下,为其加上新的功能。
代码举例:
def outter(func):
    def inner(*args,**kwargs):
        res=func(*args,**kwargs)   #res=index(*args,**kwargs)
        print(args,kwargs)
        print(res)
    return inner

@outter         #index=outter(index)
def index(a,b):
    return '原函数'

index(2,b=2)    #inner(2,b=2)

#结果
(2,) {'b': 2}
原函数

简易版装饰器

def wrapper(func):
    def inner(*args,**kwargs):
        return func(*args,**kwargs)     #给被装饰函数传参    
    return  inner

@wrapper                 #当遇到@wrapper,立即执行wrapper函数,并且把index当做参数传递进来:index=wrapper(index)
def index(request):
    pass

十六、Docker怎么启动一个容器,怎么新建一个容器    https://www.cnblogs.com/wangcuican/p/12132881.html

启动已有的容器: docker start 容器id
新建容器两种方式:
1.以交互方式创建容器
docker run -it 镜像名称:标签   /bin/bash      #如果退出容器,需要重新开启
2.守护方式创建(后台运行)
docker run -id 镜像名称:标签

进入容器内部:
docker exec -it  容器名称(或者容器ID)  /bin/bash

十七、JWT是干嘛的,怎么实现的    http://www.manongjc.com/detail/10-jfpsyicugnzdwvj.html

JWT是做用户认证的
JWT-token分为三段式:头部,载荷和签名
1.头部的内容是基本信息和采用的加密方式
2.载荷存放一些用户不敏感的信息:用户ID,过期时间戳等
3.签名存放的是编码过后的头部和载荷以及一个密钥,然后使用头部中指定的签名算法加密

签发:登录接口调用签发token
1.头部信息采用base64编码得到头部字符串
2.载荷信息采用base64编码得到载荷字符串
3.用头部、载荷字符串和密钥,采用hash算法加密得到签名字符串
在后台三段拼接起来生成token,返回给前台

校验:根据客户端带token的请求,反解出user对象
1.token拆分成三段,第一段头部加密字符串,获取加密方式
2.第二段载荷字符串反解出用户ID,通过此ID可以查询出登录的用户,过期时间确保token是否过期
3.再用第一段+第二段+密钥,采用头部信息保存的加密方式加密,与第三段签名字符串进行校验是否一致。
校验通过才能代表第二段得到的用户是合法的登录用户

jwt开发流程:
1、用户请求登录服务器,登录接口调用签发token算法,得到token,返回给客户端,客户端自己保存在cookies中。
2.每次请求带着这个token比较是否被更改过,可以反解出user对象,在视图中用request.user就能访问登录的用户,认证通过就可以返回对应的response。
总结:
1.可附带用户信息,后端直接通过JWT获取相关信息
2.本地保存

签发token的源码方法

1.authenticate用户认证返回用户对象
2.jwt_payload_handler方法传入user对象生成payload(载荷)
3.jwt_encode_handler方法传入payload生成token

传统用户认证一般流程是下面这样。 

1.用户向服务器发送用户名和密码
2.服务器验证通过后,在session中保存相关数据,比如用户角色、登陆时间等
3.服务器向用户返回一个session_id,写入用户的cookie
4.用户随后的每一次请求,都会通过cookie,将session_id传回服务器
5.服务器接收到session_id,找到前期保存的用户数据,以此来验证用户

十八、Django的Auth模块

用户校验怎么实现的?浏览器怎么携带和设置Cookie,HTTP的请求数据格式?request.user怎么拿到的(原理)?

cookie如何产生:
在浏览器访问服务器时由服务器返回一个Set-Cookie响应头,当浏览器解析这个响应头时设置cookie
Auth模块中几个常用的方法:
1.authenticate():认证模块,用户校验
提供用户认证,需要传入用户名和密码两个参数,用来验证用户名和密码是否正确。
如果认证成功,则会返回一个User对象,并且会对该User对象设置一个属性标识后端已经认证了该用户
2.login(HttpRequest,user):登录模块
此方法接受一个HttpRequest对象,以及一个认证了的User对象。
执行这步操作之后,用户浏览器保存cookies,并为该用户生成对应的session数据

request.user原理(怎么获得到登录用户的)
1.请求经过session的中间件,通过cookie中的session_id得到服务器的session
2.在经过auth中间件,通过上一步取到session获取到user_pk,就能取到登录的用户

十九、cookie和session

Session是保存在服务端,用来跟踪用户的状态
Cookie是保存在客户端的,用cookie来实现session跟踪的,创建session的时候,服务端会告诉客户端,在cookie里面记录一个session ID,
以后每次请求把这个ID发送到服务端就知道是哪个用户了。

二十、celery工作原理 

celery是一个实现异步任务的工具,只要由三部分组成。
消息中间件(broker)、任务执行单元(worker)、任务执行结果存储
消息中间件
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成,比如:MQ,Redis
任务执行单元
worker是celery提供的任务执行单元
任务结果存储
用来存储worker执行的任务结果,使用Redis/MQ存储

二十一、索引有哪些、索引是怎么加快查询速度的、索引底层是什么数据结构 

索引有哪些
1.从数据结构角度
hash类型的索引:查询单条快,范围查询慢
B+树类型的索引:B+树,层数越多,数据量指数级增长(InnoDB默认是B+树类型)
2.按照功能逻辑来分
普通索引:只是增加查询速度
唯一索引:除了增加查询速度,而且限制数据的唯一性
主键索引:在唯一索引的基础上增加了不为空的约束
全文索引:用的不多,mysql自带的只支持英文,一般全文搜索使用ES
3.按照物理存储来分
聚集索引的叶子节点存放的是索引和真实数据
非聚集索引的叶子节点存放的不是实际数据,而是指向实际数据的指针
索引是怎么加快查询的
1.索引有序
InnoDB默认的索引的数据结构是B+树,数据都是在叶子节点有序的排列,在查找的时候能快速定位到。
2.索引高度不会太高
当数据量大的话,没建立索引,会全表扫描查询满足条件的数据。创建索引之后,虽然数据量大,一般B+树不会超过7层,所以查询7次就能查询出来了。层数越少,I/O查询次数越少,所以越快。

图解B+树与查找过程

如上图,是一颗b+树,关于b+树的定义可以参见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)
和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,
P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、591013152829366075799099
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中

B+树的查找过程

如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,
内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,
发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,
那么总共需要百万次的IO,显然成本非常非常高。

二十二、全局捕获异常

当try中的代码块没有检测到异常时候,就会走else代码,finally无论有没有错误最后都会走。

try:
   需要检验的代码
except Exception:  # 万能异常  所有的异常类型都被捕获
    print('老子天下无敌')
else:
    print('被检测的代码没有任何的异常发生 才会走else')
finally:
    print('无论被检测的代码有没有异常发生 都会在代码运行完毕之后执行我')

二十三、面向对象、with管理上下文源码怎么写、property是什么、__setattr__是什么 

面向对象_继承:经典类和新式类

property

装饰器property,可以将类中的函数伪装成对象的属性,对象在访问该特殊属性会触发功能的执行,然后将该方法的返回值作为属性的值。
class A:

    def __init__(self, x):
        self.x = x

    @property
    def t(self):
        return self.x

    @t.setter
    def t(self, value):
        return value

a=A(2)
print(a.t)

b=a.t=3   #a.t=3修改类属性,触发t.setter
print(b)

二十四、DRF序列化、前后端框架分离实现原理

原生Django请求生命周期

原生Django请求生命周期:从urls.py中as_view()入手,函数名加括号优先执行as_view(),该方法返回值是view。
当你在浏览器输入请求地址时,其实是执行view函数。然后走view函数中的dispatch方法,先会判断当前的请求方式是否在默认的八个方法内,
如果存在,就使用反射getattr获取到自定义类中的对应的方法(get,post等),然后执行视图类中对应的请求方法。

DRF请求生命周期

DRF请求生命周期:根据urls.py,走as_view()进入,因为自定义视图类没有as_view()方法,只能走父类APIview的as_view方法。
在APIView也是调用父类(Django原生View)的as_view方法,同时还禁用了CSRF认证。在as_view中执行dispatch方法,
因为APIView中有dispatch方法,所以不再需要走View的dispatch,而是走自己的dispatch。dispatch包括请求模块,渲染模块,三大认证等

DRF序列化组件三种

Serializer/ModelSerializer/ListModelSerializer

为什么使用序列化组件:因为视图中查询到的对象和queryset类型不能直接作为数据返回给前台,所以要使用序列化组件

二十五、Redis五大数据类型、主从、主从缺点、持久化、哨兵

string类型
添加/修改数据   set key value     没有就增加新的,如果原来存在就覆盖之前的
获取数据       get key
删除数据       del key

添加/修改多个数据    mset key1 value  key2 value  ...
获取多个数据     mget  key1 key2 

设置生命周期
setex key seconds value             #设置多少秒之后数据过期   如果设置相同的key会把之前的清掉
psetex  key milliseconds value      #设置多少毫秒之后过期
Hash类型
添加/修改数据   hset key field value     #hset  kk name  wang
获取数据      
hget key field          (******)获取字段field的值
hgetall key        #获取key下面所有数据数据,显示field和value  
删除数据  hdel key field1  [field2]

添加/修改多个数据   hmset key field1 value1  field2  value2  ...
获取多个数据   hmget key field1 field2 ...
List类型
添加/修改数据
lpush  key value1 value2          #从左边一个个插入
rpush  key  value2  value2        #从右边一个个插入

获取数据
lrange key start stop     #start和stop都是指索引位置(从0开始),如果不知道末尾索引是多少可以写-1
lindex key index     #根据索引位置获取数据,index是索引

获取并移除数据
lpop key        #从左边开始移除数据,并且返回移除的数据
rpop key
set类型
添加数据   sadd key member1  member2 ...
获取全部数据    smembers key
删除数据    srem key member1   member2 ...
获取集合数据总量  scard key 
判断集合中是否包含指定数据 sismember key member    #如果存在返回1,不存在返回0
zset有序集合
添加数据
zadd key score1 member1 [score2 member2]

获取全部数据
zrange key start stop [withscores]         #如果不写withscores就只会显示member,默认是升序排名
zrevrange key start stop [withscores]    #降序排名

删除数据
zrem key member [member]

 RDB和AOF持久化

1.RDB将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据              
2.AOF将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程    
RDB两种保存方式:save和bgsave指令,bgsave在后台保存数据
bgsave指令工作原理:
1.客户端发送bgsave指令到redis服务端
2.系统调用fork函数,生成子进程
3.创建rdb文件
4.完成之后会返回redis服务端消息,告诉已经保存完毕
RDB的弊端:
1.存储数据量较大时,效率较低
2.基于fork创建子进程,内存产生消耗
3.宕机带来数据风险
AOF写数据的三种策略:
1.always(每次)    2.everysec(每秒)  3.no(系统控制)
AOF写数据过程:客户端发出指令给服务端,服务端并没有马上记录,而是放到AOF写命令刷新缓存区,到一定时间之后将命令同步到AOF文件中

Redis过期删除策略

1.定时删除:到了设置的过期时间就会删除
2.惰性删除:数据到期了先不做处理,等下次访问该数据时再来删除
3.定期删除:activeExpireCycle()函数对每个Redis数据库进行检测,随机挑选w个key检测。Key删除就删除数据,如果一轮中key删除的数量>w*25%,
则循环这个过程,如果小于25%,继续检查下一个数据库

数据逐出策略(内存不足时候)

在执行每一个命令前,会调用freeMemorylfNeeded()检测内存是否充足。
三种数据逐出策略:
FIFO(First In First Out):先进先出,淘汰最先进来的页面,新进来的页面最迟被淘汰,符合队列
LRU(Least recently used):最近最少使用,淘汰最近不使用的页面
LFU(Least frequently used):最近使用次数最少,淘汰最少使用的页面

主从复制

主从复制的三个阶段:
1.建立连接
2.数据同步阶段
3.命令传播阶段
建立连接过程
1.slave发送指令slaveof ip port
2.master接收指令,响应slave,slave保存master的ip和端口
3.建立socket连接,master保存slave的端口号
数据同步过程:
1.slave发送数据同步指令
2.master执行bgsave生成RDB同步数据文件,通过socket传递给slave
3.slave接收RBD文件,清空之前的所有数据,执行RDB文件恢复过程
命令传播:时时保持数据同步
当master数据库被修改时,此时需要让主从数据同步到一致状态。master将接收到的数据变更发送给slave,slave接收到命令后执行。

哨兵工作原理

一、监控阶段:先启动主从服务器再启动哨兵
1.启动哨兵,连接上master之后发送info指令,获取master的信息
2.哨兵和master之间建立cmd连接方便发送指令,同时在哨兵端保存了所有信息包括master/slaves/sentinels,在master端也保存了所有信息包括master/slaves/sentinels
3.哨兵根据获得的slave信息去连接每一个slave,发送info指令获取slave详细信息
4.启动第二个哨兵也会自动去连接master,发送info指令,会发现master已经和其他哨兵连接过同时建立cmd连接,此时哨兵端保存信息包括master/slave/sentinels(包括之前连接的哨兵),两个哨兵之间也会建立连接。
5.当第三个哨兵启动的时候,也会进行之前的操作

二、通知阶段
哨兵之间自己形成内网

三、故障转移阶段
1.其中一个sentinel向master发送信息,一段时间内master没有回应,标记状态SRI_S_DOWN(主观下线)
2.sentinel在自己内网中告诉其他哨兵,master挂了。其他哨兵也去连接master,半数以上的哨兵发现master挂了,那就是真的挂了,状态改成SRI_O_DOWN(客观下线)
3.所有的哨兵推选出一个哨兵领导,哨兵根据自己的原则从所有的slave中挑选出新的master,其他slave切换新的master,故障恢复后原master作为slave

二十六、linux查看端口号、查看内存占用、设置权限

原文地址:https://www.cnblogs.com/wangcuican/p/12986148.html