题目部分参考:https://www.cnblogs.com/JetpropelledSnake/p/9396511.html#_label1
python入门与数据结构
1、常见的PEP8规范?
缩进:4个空格
空行:函数与函数之间空两行,类内部的函数之间空一行
命名:
1、函数名小写,可采用下划线加字母;类名单词第一个字母大写,采用驼峰式命名;
2、命名必须有意义,可识别性,不能重复;
长度:每行长度不能超过79,可以采用下划线隔开并另起一行
空格:逗号之后和操作符前后采用空格隔开;
import:不要一次导入多个不是一个类型的库;
常量采用大写
2、进制之间转换?
bin:十进制转二进制,例如 bin(10)=0b1010
oct:十进制转八进制, octonary number system(八进制)
hex:十进制转十六进制,hexadecimal
int:将对应的进制转换成十进制
手动转换参考:https://jingyan.baidu.com/article/495ba84109665338b30ede98.html
3、请编写一个函数实现将IP地址转换成一个整数?
def convert_to_int(ip):
lst = [int(item) for item in ip.split('.') if item]
return sum(lst)
4、python最大递归层数?996
def foo(n):
print(n)
n += 1
foo(n)
if __name__ == '__main__':
foo(1)
5、and or or?
x or y:若x为真,则为x,否则为y;
x and y:若x为真,则为y,反之为x。
x and y or z:等价于 x and (y or z)
# 运算符优先级:比较运算符< 高于 等于运算符==
1 < (2==2) # False,等价与1<1
1 < 2 == 2 # True,等价与 1<2 and 2
6、按位运算符:
二进制下运算符号,可以理解为与、或、非。
&:按位与,都为1则为1,例如 print(10&8)=0b1010 & 0b1000 = 0b1000 = 8
。
|:按位或,有一个为真则为真。
:当两个对应的二进制相异时,则结果为1,例如print(10^8)=0b0010=2
7、ascii、unicode、utf8、gbk的区别?
它们都是编码类型,都包含二进制与字符的对应关系。
ascii:仅仅包含 阿拉伯字母以及某些运算符与二进制的对应关系,最早的编码类型,但最多只能表示一个字节,即256个。
unicode:全球通用的编码,包含所有字符与二进制的对应关系,任何字符==2Bytes
,所以用来存储英文字符浪费空间。
gbk:本国语言与二进制的对应关系,每个国家都有自己的GBK编码,没有统一起来,其中一个中文字符==2Bytes,一个英文字符==1Bytes
。
utf8:unicode的升级版本,一个中文字符==3Bytes,英文字符==1Bytes,欧洲字符==2Bytes
。
8、字节码和机器码的区别?
机器码:Native code,原生码,cpu可以直接解读的数据,执行速度最快;
字节码:Byte-code中间状态的二进制代码(文件),需要直译器转译才难成为机器码。
9、is 和 ==?
is比较的是内存地址,可以用id()查看;
==比较的是 值是否相等。
10、可变数据类型和不可变数据类型?
可变数据类型:变量的值发生改变(指的是对它进行增改操作,而非重新赋值),那么它对应的内存地址不会发生改变。例如:list、dict、set。
In [32]: l=[1,2]
In [33]: id(l)
Out[33]: 4495524232
In [34]: l.append(3)
In [35]: id(l)
Out[35]: 4495524232
不可变数据类型:变量的值发生改变,那么它对应的内存地址会发生改变,例如:str,int,float,tuple。
In [28]: a=1
In [29]: id(a)
Out[29]: 4462312528
In [30]: a=2
In [31]: id(a)
Out[31]: 4462312560
变量本质上是对内存地址的引用。
题目:
v = dict.fromkeys(['k1', 'k2'], []) # 该字典val是列表,且都指向此列表
v['k1'].append(666)
print(v)
v['k1'] = 777
print(v)
# 结果:
{'k1': [666], 'k2': [666]}
{'k1': 777, 'k2': [666]}
v1 = dict.fromkeys(['k1', 'k2'])
print(v1) # {'k1': None, 'k2': None}
v2 = dict.fromkeys(['k1', 'k2'], [])
print(v2) # {'k1': [], 'k2': []}
12、小数据池子
int和str都存在一个小数据池,在这个范围内,若变量值相同,都会指向同一个内存地址,超过这个池子的范围,会另外开辟新的内存空间。
int小数据池:int在【-5,256】范围内,创建相同的数字会指向同一个内存地址,超过了这个范围,就会指向不同的地址。
字符串小数据池范围:
1、不能有空格:若有空格,则指向两个内存地址;
2、不能包含特殊字符,若包含特殊字符,则指向两个内存地址。
13、三元运算写法以及应用场景?
结构:结果1 + if + 条件 + else + 结果2,若条件为True,则返回结果1,反之返回结果2。
14、py2和py3的区别?
print格式不同。
range()不同:在py2中是range是列表,xrange才是生成器,在py3只有range且是生成器。
默认编码不同:py2的默认编码是ascii,py3的默认编码是utf8。
15、一行代码实现数值交换?
a,b=b,a
16、列举布尔值为False的常见值?
可以通过三元表达式判断,例如:'true' if 1 else 'else'
数字类型:0为False,其余的为True
None为False
列表、tupe、字典、set等数据类型长度为0为false
17、list、str、tuple、dict每个常用的5个方法?
str类型为不可变类型,没有增删改查方法,常用方法如下:
统计:
count(s):统计s出现的次数
查询:
index(s):在指定的索引范围内从左到右找出元素s的索引并返回;
find(s,begin,end):同index方法;
格式输出:
lower():将字母改为小写格式;
upper():将字母改为大写格式;
capitalize():将首个字母改为大写;
strip():删除字符串两侧的空格并返回,但是不改变原字符串;
format():
In [32]: '{}-{}-{}'.format('alex','kate','bo')
Out[32]: 'alex-kate-bo'
In [33]: '{1}-{0}-{2}'.format('alex','kate','bo')
Out[33]: 'kate-alex-bo'
swapcase(): 大小写反转。
类型转换:
s.split(sep):转换成列表
替换:
s.replace(old,new)
判断is系列:
isdigits()
isalpha()
isnumeric()
islowe()
isupper()
判断开头和结尾:
startwith(s)
endwith(s)
list类型为可变数据类型,可以进行增删改查,常用方法如下:
创建列表:list('iterable')
增加:
append(s):在列表右侧追加元素;
extend(iterable):循环可迭代对象并添加到列表中,
例如:[1].extend('abcd')=[1,'a','b','c','d'];
删除:
pop(index):根据index删除元素并返回index;
clear():删除所有元素;
remove(s):从左至右删除找到的第一个元素,没找到匹配的元素则ValueError异常;
统计:
count(s):统计s出现的次数
排序:
sort(reverse=False):升序,可以指定为降序
reverse():仅仅颠倒当前列表顺序
字典dict常用方法如下:
创建字典:
{}.fromkeys(iterable,val)
增加或修改:
d.update({'k1':'v2'}):若字典d存在键k1,则更新其值为v2;若不存在键k1,则增加键值对;
d.setdefault('k1','v2'):若字典d存在k1,则返回其对应的值,若不存在,则增加键值对{'k1':'v2'}
删除:
d.pop('k1'):若k1存在,则删除该键值对,并返回value;若不存在,则报KeyError异常
d.clear():清空字典;
d.popitem():随机删除键值对;
查询:
get('k1','not found')
d.keys():结构类似[k1,k2,k3],主要这个不是列表,还需要经过转换才难成为列表
d.values():结构类似[v1,v2,v3]
d.items():返回结构类似 [(k1,v2),(k2,v2)]
tuple元组类型为不可变数据类型:
创建字典:tuple(iterable)
统计:
count(s)
index(s)
集合set类型无序,且不重复:
增加:
add(item)
s.update(s1)
s.union(s1)
删除:
discard(item)
差集:
s1中但是s2中没有的元素:s1.difference(s2) == s1 - s2
交集:
s1.intersection(s2)
也可以运用 +、- 运算符。
18、lambda表达式以及应用场景?
函数名=lambda 参数(多个时以逗号隔开):返回值
格式:f=lambda x, y : x*y
,参数可以有多个,且只能写一行。
19、pass的作用?
保证程序结构完整,无其他任何作用。
20、args 和 kwargs作用?
在函数不确定有多少个参数时候,可以采用可变参数。
args:会将接收到的所有的位置参数转换成列表格式,存放在函数内部作用域内;
kwargs:会将接收到的键值对参数转换成字典格式,存放在函数内部作用域内。
21、深浅拷贝:
浅拷贝只是增加了一个指针指向一个存在的内存地址。
而深拷贝是增加了一个指针并且重新开辟一个内存空间。
单层数据结构:
import copy
# 浅拷贝
li1 = [1, 2, 3]
li2 = li1.copy()
li1.append(4)
print(li1, li2) # [1, 2, 3, 4] [1, 2, 3,4]
# 深拷贝
li1 = [1, 2, 3]
li2 = copy.deepcopy(li1)
li1.append(4)
print(li1, li2) # [1, 2, 3, 4] [1, 2, 3]
多层数据结构:
import copy
# 浅拷贝 指向共有的地址
li1 = [1, 2, 3,[4,5],6]
li2 = li1.copy()
li1[3].append(7)
print(li1, li2) # [1, 2, 3, [4, 5, 7], 6] [1, 2, 3, [4, 5, 7], 6]
# 深拷贝 重指向
li1 = [1, 2, 3,[4,5],6]
li2 = copy.deepcopy(li1)
li1[3].append(7)
print(li1, li2) # [1, 2, 3, [4, 5, 7], 6] [1, 2, 3, [4, 5], 6]
22、python垃圾回收机制?
参考:
http://python.jobbole.com/87843/
http://www.cnblogs.com/Xjng/p/5128269.html
22.1、引用计数
python里每一个东西都是对象,当对象多了一个引用,则此对象的(PyObject)的计数变量(ob_refcnt)+1
,反之-1,当ob—refcnt=0
,此对象会被系统回收。
1.1、循环引用:当对象A和B相互引用,但是没有外部再引用它们任何一个,它们的引用计数虽然都为1,但显然应该被回收。
22.2、标记清除
解决循环引用的问题,主要是list、set、dict、instance等容器对象,步骤如下:
I、先标记上所有活动对象
II、再将所有未活动的对象清除
怎么标记?从根对象(全局变量、调用栈、寄存器)出发,以引用作为线,来连接内存中的对象。
缺点:简单粗暴,清除非活动对象前必须顺序扫描整个堆内存。
22.3、分代回收
将内存根据对象存活的时间由短到长分为三代:年轻代、中年代、老年代,垃圾回收频率以此减少,所以老年代的对象可能存活于系统的整个内存空间。
以空间换时间,具体步骤如下:
1、新创建的对象会被存放在年轻代;
2、当链表总数(gc计数器)达到上限时,会触发垃圾回收(gc.collect),将那些非活动对象和循环引用的对象回收,将剩余的活动对象转移到中年代;
3、以此类推,活动时间最久的对象最后都保存在老年代,且老年代的回收频率是最低的。
23、匿名函数的使用
题目1:
def multipliers():
# 返回包含四个匿名函数的列表
return [lambda x:i*x for i in range(4)]
# 注意,若返回生成器,则结果未[0,2,4,6]
print([m(2) for m in multipliers()])
结果分析:由于函数未被调用,循环中的i值未被写入函数,经过多次替代,循环结束后i值为3,
故结果为:6,6,6,6,此处容易犯错,不是很好想通。
题目二:现有两个元组(('a'),('b'))
,(('c'),('d'))
,请使用python中匿名函数生成列表[{'a':'c'},{'b':'d'}]
?
# 1、最笨的办法
ret = lambda s1,s2:[{s1[0][0]:s2[0][0]},{s1[1][0]:s2[1][0]}]
# 2、利用zip先连接两个列表;2、再利用dict构造方法直接生成字典
In [8]: s1
Out[8]: ('a', 'b')
In [9]: s2
Out[9]: ('c', 'd')
In [10]: zip(s1,s2)
Out[10]: <zip at 0x108587c08>
In [12]: list(zip(s1,s2))
Out[12]: [('a', 'c'), ('b', 'd')]
In [11]: dict(zip(s1,s2))
Out[11]: {'a': 'c', 'b': 'd'}
# 利用map(func,iterable),匿名函数可以结合map使用。
dict(lambda item:list(item),zip(s1,s2)))
24、常见的内置函数?
24.1、filter函数
filter(func,iterable)
返回一个生成器g,调用g时会遍历iterable传入func,并将判断func(item)若未真,则显示,未False,则直接过滤掉。
def is_odd(x):
return x % 2 == 1
v=list(filter(is_odd, [1, 4, 6, 7, 9, 12, 17]))
print(v) #[1, 7, 9, 17]
24.2、filter 与 map 总结
参数: 都是一个函数名 + 可迭代对象
返回值: 都是返回可迭代对象。
区别:
1、filter 是做筛选的,结果还是原来就在可迭代对象中的项;
2、map 是对可迭代对象中每一项做操作的,结果不一定是原来就在可迭代对象中的项。
24.3、isinstance\type
isintance(对象,类)
:判断一个对象是否是一个已知的类型;type(对象)
:直接返回给定对象的类型:list,str,dict,等等,不考虑类的继承关系;
# type() 与 isinstance() 区别
class A:
pass
class B(A):
pass
print("isinstance",isinstance(A(),A)) # isinstance True
print("type",type(A()) == A) # type True
print('isinstance',isinstance(B(),A) ) # isinstance True
print('type',type(B()) == A) # type False
24.4、zip函数
zip(*iterable)
会将同时对每个可迭代对象中的元素按照相同索引则打包成一个个元祖,然后返回由这些元祖组成的列表迭代器,若各个可迭代对象长度不一致,以最短的长度为准。
print(list(zip([0,1,3],[5,6,7],['a','b'])))
# [(0, 5, 'a'), (1, 6, 'b')]
24.5、reduce函数
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
运算过程:((((1+2)+3)+4)+5)
,属于累加计算类型(calculatively)。
from functools import reduce
def add(x,y):
return x + y
print(reduce(add,[1,2,3,4,5]))
# 15
print(reduce(lambda x, y: x+y, [1,2,3,4,5])) # 15
print(reduce(add,range(1,101)))
# 5050
25、一行代码实现9*9乘法表
大致结构如下:
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
...
9*1=9 9*2=18 9*3=27 ... 9*9=81
# 多行方法,直接print
for i in range(1, 10):
for j in range(1,i+1):
item = '%sx%s=%s' % (i,j,i*j)
print(item,end=' ')
print()
# 先将每一行组装成一个列表项,最后调用'\n'.join(lst)最后打印出每一项
ret_list=[]
for i in range(1, 10):
temp = ''
for j in range(1,i+1):
item = '%sx%s=%s ' % (i,j,i*j)
temp += item
ret_list.append(temp)
print('\n'.join(ret_list))
# 一行代码
print(
'\n'.join(
[
' '.join(['%s*%s=%s' % (j, i, i * j) for j in range(1, i + 1)]) for i in range(1, 10)
]
)
)
26、python链式比较
参考:https://docs.python.org/2.3/ref/comparisons.html
1 < 2 == 2
等价与1<2 and 2==2
27、(1,) 和(1)的区别
(1,)
是tuple类型,但是(1)
是整数类型。
28、列表切片
l[start:end:step]
:start表示其实索引,end表示结束索引,step表示步长。
In [46]: l=list('123456789')
In [47]: l
Out[47]: ['1', '2', '3', '4', '5', '6', '7', '8', '9']
# 表示步长为2
In [48]: l[::2]
Out[48]: ['1', '3', '5', '7', '9']
# 步长为-1
In [49]: l[::-2]
Out[49]: ['9', '7', '5', '3', '1']
# 整个列表
In [50]: l[:]
Out[50]: ['1', '2', '3', '4', '5', '6', '7', '8', '9']
# 同上
In [51]: l[::]
Out[51]: ['1', '2', '3', '4', '5', '6', '7', '8', '9']
In [52]: l[1:4:2]
Out[52]: ['2', '4']
# 结束索引为-1,不包括-1的值
In [53]: l[:-1]
Out[53]: ['1', '2', '3', '4', '5', '6', '7', '8']
30、实现栈stack
栈(stack)和队列(queue)是两种数据结构,与具体的语言无关。
它们都属于线性表结构。
实现栈stack,后进先出,只能在表的一端进行插入和删除操作,不同与队列queue结构(在表的一端进行插入在另一端进行删除)
class Stack(object):
def __init__(self):
self.items = []
def push(self,item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[-1]
def is_empty(self):
return not bool(len(self.items))
def size(self):
return len(self.items)
s=Stack()
s.push('a')
s.push('b')
s.push('c')
print(s.peek())
print(s.pop())
print(s.pop())
print(s.pop())
print(s.is_empty())
31、生成器、迭代器、可迭代对象 以及应用场景?
可迭代对象:可以进行for循环遍历,内置方法__iter__
,python中的str、list、tuple、dict都是可迭代对象。
生成器:保存的算法,而非数据结构,生成器表达式:(i for i in range(10))
。生成器内置__iter__
和__next__
方法。生成器的函数表达:
def fib(max):
n, prev, curr = 1,0, 1
while n<=max:
yield prev
n+=1
prev, curr = curr, curr + prev
ite = fib(10)
print(ite) # <generator object fib at 0x10ac3ea98>
迭代器:生成器也是迭代器的一种,且所有可迭代对象也属于迭代器。
32、装饰器
在不改变原函数的基础上,在函数执行前后进行定制操作。
闭包函数: 1、嵌套函数;2、外层函数返回内层函数;3、内层函数引用外层函数的变量。
33、用Python实现一个二分查找的函数
优点:时间复杂度O(logN)
缺点:必须为有序序列。
def count_time(func):
def inner(*args, **kwargs):
import time
start = time.time()
handle=func(*args,**kwargs)
end = time.time()
print('%s 耗时<%s>'% (func.__name__,(end-start)))
return handle
return inner
@count_time
def bin_search(val,arr):
# 记录数组的最高位和最低位
min = 0
max = len(arr) - 1
if val in arr:
# 建立一个死循环,直到找到val
while True:
# 得到中间索引
mid = int((min + max) / 2)
# val在中间值左边,已经排除mid
if val<arr[mid]:
max = mid-1
# val在中间值右边,已经排除mid
elif val>arr[mid]:
min = mid+1
# val等于中间值
elif arr[mid] == val:
return arr[mid]
else:
print("没有该数字!")
@count_time
def common_search(val,arr):
for item in arr:
if item ==val:
return item
if __name__ == "__main__":
lst = list(range(10000000))
common_search(9999999,lst)
bin_search(9999999,lst)
34、遍历文件下的所有文件以及文件大小
import os
for root,holder,files in os.walk('/Users/mac/Desktop/f1'):
print('root<%s> holder<%s> files<%s>'% (root,holder,files))
# root为当前路径,holder为当前路径下的一级文件夹列表,files为当前路径下的文件列表
# root < / Users / mac / Desktop / f1 > holder < ['f3', 'f2'] > files < ['.DS_Store', '1'] >
# root < / Users / mac / Desktop / f1 / f3 > holder < [] > files < ['3', '2'] >
# root < / Users / mac / Desktop / f1 / f2 > holder < [] > files < ['4'] >
for f in files:
print('%s<%s>'%(os.path.join(root,f),os.path.getsize(os.path.join(root,f))))
35、os和sys模块?
os模块负责程序与操作系统的交互,提供了访问操作系统底层的接口;
sys模块负责程序与python解释器的交互,提供了一系列的函数和变量,用于操控python的运行时环境。
os与sys模块的官方解释如下:
os: This module provides a portable way of using operating system dependent functionality.
这个模块提供了一种方便的使用操作系统函数的方法。
sys: This module provides access to some variables used or maintained by the interpreter and to
functions that interact strongly with the interpreter.
这个模块可供访问由解释器使用或维护的变量和与解释器进行交互的函数。
os 常用方法
os.remove() 删除文件
os.rename() 重命名文件
os.walk() 生成目录树下的所有文件名
os.chdir() 改变目录
os.mkdir/makedirs 创建目录/多层目录
os.rmdir/removedirs 删除目录/多层目录
os.listdir() 列出指定目录的文件
os.getcwd() 取得当前工作目录
os.chmod() 改变目录权限
os.path.basename() 去掉目录路径,返回文件名
os.path.dirname() 去掉文件名,返回目录路径
os.path.join() 将分离的各部分组合成一个路径名
os.path.split() 返回( dirname(), basename())元组
os.path.splitext() 返回 (filename, extension) 元组
os.path.getatime\ctime\mtime 分别返回最近访问、创建、修改时间
os.path.getsize() 返回文件大小
os.path.exists() 是否存在
os.path.isabs() 是否为绝对路径
os.path.isdir() 是否为目录
os.path.isfile() 是否为文件
sys 常用方法
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.modules.keys() 返回所有已经导入的模块列表
sys.exc_info() 获取当前正在处理的异常类,exc_type、exc_value、exc_traceback当前处理的异常详细信息
sys.exit(n) 退出程序,正常退出时exit(0)
sys.hexversion 获取Python解释程序的版本值,16进制格式如:0x020403F0
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值
sys.maxunicode 最大的Unicode值
sys.modules 返回系统导入的模块字段,key是模块名,value是模块
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
sys.stdout 标准输出
sys.stdin 标准输入
sys.stderr 错误输出
sys.exc_clear() 用来清除当前线程所出现的当前的或最近的错误信息
sys.exec_prefix 返回平*立的python文件安装的位置
sys.byteorder 本地字节规则的指示器,big-endian平台的值是'big',little-endian平台的值是'little'
sys.copyright 记录python版权相关的东西
sys.api_version 解释器的C的API版本
总结:
os模块负责程序与操作系统的交互,提供了访问操作系统底层的接口;
sys模块负责程序与python解释器的交互,提供了一系列的函数和变量,用于操控python的运行时环境。
面向对象部分
1、谈谈你对面向对象的理解?
一般有面向过程和面向对象两种思想。
面向过程指的是将问题分解,进而流程化,进而简单化,缺点是可扩展性差。
面向对象是利用类和对象来描述现实世界的所有事物。优点是复杂度高,缺点是可扩展性强。
类是具有相似属性和方法的对象的模板,对象是类的实例化,类模板必须经过实例化才能被赋予行为与特征。
2、面向对象的三大特性
1、继承:将多个类的共同属性和方法封装到一个类下,然后派生类继承这个类,就可以使用这个类下的所有方法与熟悉,应用场景:django-rest-framework中视图类的继承。
2、封装:将数据属性与方法封装到类下,那么类和类的实例化对象可以通过.
操作来调用这些方法与数据属性;还可以将设置私有属性和方法,只在类内部调用,限制外部访问。应用场景:在flask的上下文管理中将session和request封装到了RequestContext对象中,将app和g封装到了AppContext对象中。
3、多态:同一个事物的多种形态,具体指的是基类的不同方法在派生类中有着不同的实现方式。
3、对象的内置方法有哪些?
# __init__() 初始化
# __new() 创建对象
# __call__() 类A的实例化对象a加()会调用A中的__call__()方法
# __del__ 析构方法,当对象在内存中被释放时,自动触发执行。如当 del obj 或者应用程序运行完毕时,执行该方法里边的内容。
# __enter__和__exit__ 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量;with中代码块执行完毕时执行__exit__里边的内容。
class Message(object):
def __init__(self,name,mode):
self.name = name
self.mode = mode
def __enter__(self):
print("打开文件<%s>..." % self.name)
self.filehander = open(self.name,self.mode)
return self.filehander
def __exit__(self,*para):
print("关闭文件<%s>.."%self.name)
self.filehander.close()
with Message('1.txt','rb') as f:
for i in f:
print(i)
# __module__:表示当前操作的对象在那个模块 obj.__module__
class A:
def __init__(self):
print('A>>',self.__class__)
pass
class B(A):
def __init__(self):
print('B>>',self.__class__)
super().__init__()
b = B()
# __doc__:类的描述信息,该描述信息无法被继承
# __str__:改变对象的字符串显示 print函数 --->obj.__str__()
# __repr__:改变对象的字符串显示 交互式解释器 --->obj.__repr__()
# __format__:自定制格式化字符串
# __slots__:一个类变量 用来限制实例可以添加的属性的数量和类型
# 本质上操作的是obj.__dict__这个字典
__getattr__():obj.xx
__setattr__():
__delattr__():
__getitem__():obj[xxx]
__setitem__()
__delitem__()
__mro__():查找继承顺序时使用
__str__():
__repr__():
__iter__():
__dict__():
__add__():
4、super函数的作用?
主要用于 子类 继承 基类 的方法。对于单继承,直接调用当前类的父类的对应的方法,对于多继承关系,super(cls, inst) 获得的是 cls 在 inst 的 MRO 列表中的下一个类。
参考:https://www.cnblogs.com/silencestorm/p/8404046.html
# 多继承关系示例
class Base:
def __init__(self):
print('in Base')
class A(Base):
def __init__(self):
super(A,self).__init__()
print('in A...')
class B(Base):
def __init__(self):
super(B,self).__init__()
print('in B...')
class C(A,B):
def __init__(self):
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
# <class '__main__.Base'>, <class 'object'>]
print(C.mro())
super(C,self).__init__()
print('in C...')
c = C()
具体流程如下图所示:
5、functiontools
import functools
def deco(func):
@functools.wraps(func) # 加在最内层函数正上方
def wrapper(*args, **kwargs):
print('in wrapper...')
return func(*args, **kwargs)
return wrapper
@deco
def index():
"""doc说明"""
print('in index...')
index()
print('name<%s> doc<%s>'%(index.__name__,index.__doc__))
# 没有加@functools.wraps(func)装饰:name<wrapper> doc<None>
# 加之后:name<index> doc<doc说明>
6、方法和函数的区别
自动传入self的method方法;手动传入对象的function函数。
class B:
def run(self):
pass
@staticmethod
def eat():
pass
@classmethod
def sleep(cls):
pass
b = B()
print(b.run) # 对象的方法 <bound method B.run of <__main__.B object at 0x10d19f2b0>>
print(b.eat) # 函数<function B.eat at 0x10d1918c8>
print(b.sleep) # 类B的方法<bound method B.sleep of <class '__main__.B'>>
from types import FunctionType,MethodType
print(isinstance(b.run, MethodType)) # True
print(isinstance(b.eat, FunctionType)) # True
7、动态方法和静态方法的区别
class Num:
# 普通方法:能用Num调用而不能用实例化对象调用
def one():
print ('1')
# 实例方法:能用实例化对象调用而不能用Num调用
def two(self):
print ('2')
# 静态方法:能用Num和实例化对象调用
@staticmethod
def three():
print ('3')
# 类方法:第一个参数cls长什么样不重要,都是指Num类本身,调用时将Num类作为对象隐式地传入
@classmethod
def go(cls):
cls.three()
进程间实现数据共享?
from multiprocessing import Queue,Manager,Pipe
# Queue:队列,put()、get()
q=Queue()
# Pipe:主进程和子进程之间可以实现收发消息。
father_conn,son_conn=Pipe()
# Manager:
with Manager() as manager:
D=manager.dict() # 生成一个字典,可在多进程间共享和传递
L=manager.list() # 生成一个列表
并发编程
程序、进程、线程、协程的区别?
计算机组成:硬件层、操作系统(负责将硬件层封装起来,以接口的形式提供给我们使用)、app。
以一个应用软件为例,它本身就是一个程序,双击开启软件会开启多个进程,每个进程内部可能会有多个线程。
线程是一个进程内的执行单元。
线程和进程的区别:创建新的进程开销大,需要开辟度独立的内存空间,而一个进程内至少有一个线程,且当前进程内的所有线程共享当前进程的内存空间,所以创建新线程开销小;
多进程并行对于计算型任务处理效率高,而多线程可以实现并发,遇到IO操作会来回切换,适合处理阻塞性任务;
协程与线程:
(1)一个线程可以有多个协程,一个进程也可以单独拥有多个协程,这样Python中则能使用多核CPU;
(2)线程进程都是同步机制,而协程是异步;
(3)协程能保留上一次调用时的状态。
GIL锁是什么?
Global Interpreter Lock,全局解释器锁,保证同一时刻同一进程中只能有一个线程被cpu执行,例如一个进程内也存在垃圾回收线程,若垃圾回收线程要回收变量,而其他线程要修改变量,就会产生冲突,所以需要GIL????的存在,保证同一时刻同一个进程中只有线程拿到解释器执行权限。
io多路复的作用?
单线程下实现并发效果。多路复用是在单个线程中通过记录每个IO流的状态来同时管理多个IO流。相当于机场管理系统,通过记录每架飞机的状态来进行调度管理。
select和poll单线程下实现监听多个客户,但是系统不会通知,只能每次循环遍历文件描述符(fd)表来判断哪个就绪。select有最大监听数量限制1024,而poll是基于链表进行fd管理的。
epoll有函数回掉机制,直接通知应用程序哪个fd就绪,减少了轮训操作。
参考:https://www.cnblogs.com/aspirant/p/9166944.html
网络编程
OSI 7层/5层协议?
参考博客:https://blog.csdn.net/u012351051/article/details/83352021
从应用产生数据到主机将数据发送出去的逐层对数据封装的过程:
(IP 数据包在以太网数据包里面,TCP 数据包在 IP 数据包里面)
1、应用层
属于程序员与应用程序打交道。app会根据http协议(URL:请求头、请求体)解析传过来的TCP数据包,反过来就是根据http协议封装数据到tcp包中。
2、传输层
利用TCP、UDP协议,实现数据稳定性传输。
TCP的最大实际负载为1400Bytes,所以当数据很大,会分多个包发送,且每个包都带有seq序列号。TCP 数据包里面有一个端口(port)参数,就是用来指定转交给监听该端口的应用程序。TCP主要用于浏览器(http、https、ftp等协议)、邮件(POP、SMTP)。特点是:
1、数据包丢失处理(确认 以及 重传机制),例如A发送多个数据包才能完整传输数据。当发送第一个包(seq=1,8bytes_data),B返回应该返回(ack=9);若第二个包(seq=2,12bytes——data)丢包,B没收第二个数据包则每次都返回(ack=9),直到B连续三次返回ack=9,那么会通知A再次发送第二个数据包(seq=2,12bytes_data),以此保证数据的完整性,图示如下:
2、三次握手建立连接,四次挥手断开连接,也保证了数据传输的稳定可靠。缺点是不但即耗时,也会占用系统cpu和内存资源。
UDP主要用于QQ语音、QQ视频等。
优点有:
1、只要有对方的IP和port即可发送数据,效率更高;
2、可以多对多交互通讯,但是TCP只能是一对一通讯;
3、没有拥塞机制(网络出现拥塞,不会导致源主机发送速率降低),常用于IP电话、实时视频会议等。
缺点有:
1、可能产生丢包,且不能保证数据顺序,所以只能传输少量数据;
2、head只有8bytes。
3、网络层
IP地址规定了局域网之间的互通,允许某个局域网的A主机向另一个局域网的B主机发送消息。
4、链路层
Ethernet协议:规定电信号如何组成packet,只能解决局域网内点对点的通讯。
head(18bytes):本机mac(6bytes)+目标机mac(6bytes)+数据描述(6bytes)
data(46-1500bytes):真正的包裹。
5、物理层(高低电平信号)
路由级别的数据传输。
TCP的三次握手
SYN(synchronize sequence number),同步序列号,SYN=1表示需要建立TCP连接;ACK(acknowledgement),确认信号;Seq=x是随机生成的序列号。
具体步骤:
1、客户机A发送SYN=1,Seq=X(随机生成);
2、服务端B发送SYN+ACK报文,ACK=X+1(根据客户机发送的seq而定),Seq=Y;
3、客户机A发送ACK,ACK=Y+1,seq=Z以此建立连接。
TCP的四次挥手
参考:http://blog.51cto.com/10642812/2286552
1、主动断开方发送FIN(seq=X,ack=212,FIN=1),请求断开连接,不会再发送数据给被动方;
2、被动方发送ACK(seq=Y,ACK=X+1),表示收到断开连接请求,且不再发送数据;
3、被动方发送FIN(seq=Z,FIN=1,ack=Y+1),请求断开连接,保证发送的数据A已经完全收到(确认重传机制);
4、主动方发送ACK(seq=W,ack=Z+1);
断开过程详细过程:
(1)主机A发送断开TCP连接请求的报文,其中报文中包含seq序列号,是有发送端随机生成的,并且还将报文中FIN字段的值设为1,表示需要断开TCP连接。
(2)主机B会回复A发送的TCP断开请求报文,其中包含seq序列号,是由恢复段随机生成的,而且会产生ACK字段,ACK字段的数值,是在A发送过来的seq序列号基础上加1进行回复的,以便A收到信息时,知晓自己的TCP断开请求已得到了验证。
(3)在主机B回复完A的TCP断开请求后,不会马上就进行TCP连接的断开,主机B先会确认确保断开前,所有传输到A的数据是否已经传输完毕,一旦确认传输完毕就会将回复报文的FIN字段置为1,并产生随机seq序列号。
(4)主机A收到主机B的TCP断开请求后,会回复主机B 的断开请求,包含随机生成的seq字段和ack字段,ack字段会在主机B的TCP断开请求的seq的基础上加1,从而完成主机B请求的验证回复。
路由器和交换机的区别?
路由器:可以实现局域网之间的通讯;
交换机:可以实现局域网内部通讯。
ARP协议?
地址解析协议(Address Resolution Protocol),根据IP地址获取物理mac地址的一个TCP/IP协议。
DNS域名解析?
通过DNS服务器来解析域名和IP的对应关系,将域名变成IP地址。
什么是socket?简述基于tcp协议的套接字通信流程。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。
服务端:
1、创建socket对象;
2、绑定ip端口bind();
3、设置最大链接数listen();
4、accept()与客户端的connect()创建双向管道,等到联接;
5、send(), recv(), 收发数据;
6、close()。
客户端:
1、创建socket对象;
2、connect()与服务端accept()创建双向管道;
3、send()、recv();
5、close()。
什么是粘包? socket 中造成粘包的原因是什什么? 哪些情况会发生粘包现象?
在不知道数据大小的情况下,接收端会按照指定的长度从系统内存中取出数据,若指定的长度小于数据长度,那么多余的数据会遗留在系统内存中,下此再取数据时候会优先被取出。
解决粘包问题:若果可以预先知道要接受的数据大小,就可以完全解决接收数据的粘包问题。
什么是cdn?
内容分发网络,content delivery network。
目的是使用户可以就近到服务器取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。
LVS是什么及作用?
Linux virtual server,主要用于多服务器的负载均衡,可用于请求的分发、缓存、业务处理。
1、工作在网络层,可以实现高性能,高可用的服务器集群技术;
2、把许多低性能的服务器组合在一起形成一个超级服务器;
3、易用,配置非常简单,且有多种负载均衡的方法;
4、它稳定可靠,即使在集群的服务器中某台服务器无法正常工作,也不影响整体效果;
5、另外可扩展性也非常好。
Nginx是什么及作用?
Nginx是一款轻量级的Web、反向代理、邮件服务器,其特点是占有内存少,并发能力强。
什么是负载均衡?
从单一的的服务器到服务器集群,包括转发服务器群、业务服务器群,且具有故障转移能力,当其中转发服务器或业务服务器其中有一台发生故障或从故障恢复,那么没有影响(Keepalived)。
参考:
https://blog.csdn.net/guchuanyun111/article/details/52056474
https://www.cnblogs.com/wang-meng/p/5861174.html
数据库
列举常见的关系型数据库和非关系型
关系型数据库(需要有表结构):mysql、oracle 、 sqlserver、db2、sybase。
非关系型数据库(是以key-value存储的,没有表结构)(NoSQL):
1、MongoDB:一个高性能,开源,无模式的文档型数据库,开发语言是C++,它在许多场景下可用于替代传统的关系型数据库或键/值存储方式;
2、Redis:一个开源的使用C语言编写、支持网络、可基于内存或持久化的日志型、Key-Value数据库,并提供多种语言的API。目前由VMware主持开发工作。
MySQL常见数据库引擎及比较?
1、InnoDB
支持事务
支持表锁、行锁(for update)
# 表锁,当没有commit之前,此表被????定,另一终端无法使用此表
begin;
select * from tb for update
commit;
# 行锁
select id,name from tb where id=2 for update
2、myisam
查询速度快
全文索引
支持表锁:select * from tb for update
。
3、NDB:高可用、 高性能、高可扩展性的数据库集群系统
4、Memory:默认使用的是哈希索引
简述数据库三大范式?
数据库的三大特性:表、表中的数据(字段)、表与表之间的关系。
数据库设计三大范式:
1、确保每列保持原子性(即数据库表中的所有字段值是不可分解的原子值);
2、确保表中的每列都是和主键相关(表中只能保存一种数据,不可以把多种数据保存在同一张表中)--->完全属于当前表的数据;
3、确保每列都和主键直接相关,而不是间接相关(在一个数据库表中保存的数据只能与主键相关)----> 消除传递依赖(间接)。
比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系,而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。
数据库五大约束:
1、primary KEY:设置主键约束;
2、UNIQUE:设置唯一性约束,不能有重复值;
3、DEFAULT 默认值约束;
4、NOT NULL:设置非空约束,该字段不能为空;
5、FOREIGN key :设置外键约束。
什么是事务?MySQL如何支持事务?
事务(失误)用于将多个SQL作为原子性操作,一旦有某一个出现错误,即可回滚到原来的状态,从而保证数据库数据完整性。
事务的特性:
1、原子性: 确保工作单元内的所有操作都成功完成,否则事务将被中止在故障点,和以前的操作将回滚到以前的状态。
2、一致性: 确保数据库正确地改变状态后,成功提交的事务。
3、隔离性: 使事务操作彼此独立的和透明的。
4、持久性: 确保提交的事务的结果或效果的系统出现故障的情况下仍然存在。
InnoDB支持事务,MyISAM不支持。
启动事务:
start transaction;
update from account set money=money-100 where name='a';
update from account set money=money+100 where name='b';
commit;
简述数据库设计中一对多和多对多的应用场景?
FK(一对多):下拉框里面的数据就需要用FK关联另一张表。
M2M(多对多):多选的下拉框,或者checkbox。
简述触发器、函数、视图、存储过程?
触发器:对数据库某张表的增加、删除,修改的前后定义一些操作。
函数:触发函数是通过select。聚合函数(max/sum/min/avg),时间格式化(date_format),字符串拼接(concat)。
存储过程:将SQL语句保存到数据库中,并命名,以后在代码调用时,直接调用名称即可。参数类型有in(只将参数传进去)、out(只拿结果)、inout(既可以传,可以取)。
函数与存储过程区别:本质上没区别,只是函数有如:只能返回一个变量的限制,而存储过程可以返回多个;函数是可以嵌入在sql中使用的,可以在select中调用,而存储过程不行。
视图:一个虚拟表,不是真实存在的(只能查,不能改)。
MySQL索引种类
1、单列:
1.1、普通索引index:加速查找
1.2、唯一索引unique:加速查找 + 约束:唯一(只能有一个空,不然就重复了)
1.3、主键(primay key):加速查找 + 约束(唯一 + 不能为空)
2、多列:
2.1、联合索引(多个列创建索引),例如 index(id,name)
2.2、联合唯一索引,例如 unique(id,name)
2.3、联合主键索引:例如primary key(id,name)
联合索引遵循最左前缀的规则,否则无法命中索引。
索引合并:利用多个单例索引查询,例如:在数据库查用户名和密码,分别给用户名和密码建立索引。
覆盖索引:在索引表中就能将想要的数据查询到。
主键和外键的区别
主键是能确定一条记录的唯一标示,例如:身份证证号。
外键是用于与另一张表的关联,是能确定另一张表记录的字段,用于保持数据的一致性。
主键:唯一标识一条记录,不能有重复的,不允许为空。
外键:另一张表的主键,外键可以有重复的,可以为空。
作用不同:用来保证数据完整性 用来与其他表建立联系的
个数不同:主键只能有一个,而外键一个表可以有多个外键。
列举创建索引但是无法命中索引的8种情况。
1.- like '%xx'
select * from tb1 where name like '%cn';
2.- 使用函数
select * from tb1 where reverse(name) = 'wupeiqi';
3.- or
select * from tb1 where nid = 1 or email = 'seven@live.com';
特别的:当or条件中有未建立索引的列才失效,以下会走索引
select * from tb1 where nid = 1 or name = 'seven';
select * from tb1 where nid = 1 or email = 'seven@live.com' and name ='alex'
4.- 类型不一致
如果列是字符串类型,传入条件是必须用引号引起来,不然...
select * from tb1 where name = 999;
5.- !=
select * from tb1 where name != 'alex'
特别的:如果是主键,则还是会走索引
select * from tb1 where nid != 123
6.- >
select * from tb1 where name > 'alex'
特别的:如果是主键或索引是整数类型,则还是会走索引
select * from tb1 where nid > 123
select * from tb1 where num > 123
7.- order by
select email from tb1 order by name desc;
当根据索引排序时候,选择的映射如果不是索引,则不走索引
特别的:如果对主键排序,则还是走索引:
select * from tb1 order by nid desc;
8.- 组合索引最左前缀
如果组合索引为:(name,email)
name and email -- 使用索引
name -- 使用索引
email -- 不使用索引
如何开启慢日志查询?
什么是慢日志? MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,
修改配置文件:
slow_query_log = OFF # 是否开启慢日志记录
long_query_time = 2 # 时间限制,超过此时间,则记录
slow_query_log_file = /usr/slow.log # 日志文件
log_queries_not_using_indexes = OFF # 为使用索引的搜索是否记录
下面是开启:
slow_query_log = ON
long_query_time = 2
log_queries_not_using_indexes = OFF
log_queries_not_using_indexes = ON
# 注:查看当前配置信息:
show variables like '%query%'
# 修改当前配置:
set global 变量名 = 值
可以参考:
https://www.cnblogs.com/saneri/p/6656161.html
数据库导入导出命令(结构+数据)
导出现有数据库数据:
mysqldump -u用户名 -p密码 数据库名称 >导出文件路径 # 结构+数据
mysqldump -u用户名 -p密码 -d 数据库名称 >导出文件 路径 # 结构
导入现有数据库数据:mysqldump -uroot -p密码 数据库名称 < 文件路径
数据库优化方案?
1、创建数据表时把固定长度的放在前面;
2、将固定数据放入内存: 例如:choice字段 (django中有用到,数字1、2、3…… 对应相应内容)
3、char 和 varchar 的区别(char可变, varchar不可变 )
4、联合索引遵循最左前缀(从最左侧开始检索);
5、避免使用 select * ;
6、读写分离:两台服务器同步数据,利用数据库的主从分离(主 用于删除、修改、更新;从 用于查);
7、分库:当数据库中的表太多,将某些表分到不同的数据库,例如:1W张表时,缺点是连表查询。
8、分表:
- 水平分表:将记录选择性地存放到对应的表中。避免一张表出现几百万条数据,缩短了一条sql的执行时间,例如聊天信息,会讲将每个人的聊天记录分别存放到各自的记录表中,这样需要查询的时候,只需要从自己的记录表中查询即可;
- 垂直分表:拆分表,将常用的字段和不常用且数据量大的字段分别存放在两种表中。将一个表中不经常用到的,或者像text这种类型比较大的字段挪到一个表中,其他常用的(访问频繁的)字段单独一个表,两张表再通过外键关联起来。
- 参考:https://www.cnblogs.com/lucky-man/p/6207873.html
9、加缓存:利用redis、memcache (常用数据放到缓存里,提高取数据的速度)
10、如果只想获取一条数据:select * from tb where name=‘alex’ limit 1;
MySQL的执行计划?
查看有没有命中索引,让数据库帮看看运行速度快不快:explain select * from table;
1000w条数据,使用limit offset length分页时,为什么越往后翻越慢?如何解决?
原因:limit 1000000,20
的意思扫描满足条件的1000020行,扔掉前面的100w行,返回最后的20行。
解决办法:
1、子查询优化法:先找出第一条数据,然后大于等于这条数据的id就是要获取的数据:
Query_ID: 2
Duration: 0.00167000
Query: select * from Member limit 10, 100
*************************** 3. row ***************************
Query_ID: 3
Duration: 0.00112400
Query: select * from Member where MemberID >= (select MemberID from Member limit 10,1) limit 100
*************************** 4. row ***************************
Query_ID: 4
Duration: 0.00263200
Query: select * from Member limit 1000, 100
*************************** 5. row ***************************
Query_ID: 5
Duration: 0.00134000
Query: select * from Member where MemberID >= (select MemberID from Member limit 1000,1) limit 100
*************************** 6. row ***************************
Query_ID: 6
Duration: 0.09956700
Query: select * from Member limit 100000, 100
*************************** 7. row ***************************
Query_ID: 7
Duration: 0.02447700
Query: select * from Member where MemberID >= (select MemberID from Member limit 100000,1) limit 100
从结果中可以得知,当偏移1000以上使用子查询法可以有效的提高性能。
2、倒排表优化法: 倒排表法类似建立索引,用一张表来维护页数,然后通过高效的连接得到数据。缺点是只适合数据数固定的情况,数据不能删除,维护页表困难。
详情参考:https://www.jb51.net/article/85312.htm
redis和memcached比较?
1、redis不仅支持简单的key_value类型,还支持字典(hash)、字符串、列表、集合、有序集合类型。
2、内存使用效率对比:使用简单的key-value存储的话,Memcached的内存利用率更高而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached;
3、性能对比:由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高;而在100k以上的数据中,Memcached性能要高于Redis;
4、Redis虽然是基于内存的存储系统,但是它本身是支持内存数据的持久化的,而且提供两种主要的持久化策略:RDB快照和AOF日志;而memcached是不支持数据持久化操作的。
5、集群管理不同,Memcached本身并不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储。
redis中数据库默认是多少个db 及作用?
Redis默认支持16个数据库,可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库。
Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。
简述redis的有哪几种持久化策略及比较?
RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
- 优点:使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
- 缺点:RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
AOF:把所有命令保存起来,如果想到重新生成到redis,那么就要把命令重新执行一次。
- 缺点:速度慢,文件比较大
- 优点:数据完整。
参考;https://www.cnblogs.com/logo-fox/p/7810530.html
如何实现redis集群?
redis集群、分片、分布式redis
redis-py-cluster
集群方案:
- redis cluster 官方提供的集群方案。
- codis,豌豆荚技术团队。
- tweproxy,Twiter技术团队。
redis cluster的原理?
- 基于分片来完成。
- redis将所有能放置数据的地方创建了 16384 个哈希槽。
- 如果设置集群的话,就可以为每个实例分配哈希槽:
- 192.168.1.20【0-5000】
- 192.168.1.21【5001-10000】
- 192.168.1.22【10001-16384】
- 以后想要在redis中写值时,
set k1 123
将k1通过crc16的算法,将k1转换成一个数字。然后再将该数字和16384求余,如果得到的余数 3000,那么就将该值写入到 192.168.1.20 实例中。
列举redis支持的过期策略
MySQL里有2000w数据,redis中只存20w的数据,如何保证 redis 中都是热点数据?
相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略(回收策略)。redis 提供 6种数据淘汰策略:
1、volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰;
2、volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;
3、volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰;
4、allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰;
5、allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰;
6、no-enviction(驱逐):禁止驱逐数据。
前端、框架
谈谈你对http协议的认识
浏览器本质:socket客户端遵循Http协议
HTTP协议本质:通过\r\n
分割的规范+请求响应之后断开链接(无状态、短连接)
具体:
Http协议是建立在tcp之上的,是一种规范,它规范定了发送的数据的格式,然而这个数据格式是通过\r\n 进行分割的,请求头与请求体也是通过2个\r\n分割的,响应的时候,响应头与响应体也是通过\r\n分割,并且还规定已请求已响应就会断开链接,即短连接、无状态。
http1.1版本有keepalived的请求头,一次TCP连接中可以持续发送多份数据而不会断开连接,通过使用keep-alive机制,可以减少tcp连接建立次数。但是,keep-alive并不是免费的午餐,长时间的tcp连接容易导致系统资源无效占用。配置不当的keep-alive,有时比重复利用连接带来的损失还更大。所以,正确地设置keep-alive timeout时间非常重要。
keepalive_timout时间值意味着:一个http产生的tcp连接在传送完最后一个响应后,还需要hold住keepalive_timeout秒后,才开始关闭这个连接。
参考博客:https://www.cnblogs.com/freefish12/p/5394876.html#4152375
django请求的生命周期?
具体流程如下:
请求流程走向如下:
1、wsgi:就是socket服务端,用于接受用户请求,并进行初次封装,然后将请求交给web框架(flask、django);
2、中间件:帮助我们对请求进行校验或在请求中添加其他相关数据,例如 csrf、request.session;
3、路由匹配:url和视图函数或类的映射表;
4、视图函数:在视图函数中进行业务逻辑的处理,可能涉及到ORM(数据库交互组件)、templates 渲染;
6、中间件:对响应的数据进行处理;
7、wsgi:将响应的内容发送给浏览器。
谈谈你对websocket协议的认识。
也是基于tcp的三次握手和四次挥手。
相比HTTP长连接,WebSocket有以下特点:
1、是真正的全双工方式,建立连接后客户端与服务器端是完全平等的,可以互相主动请求。而HTTP长连接基于HTTP,是传统的客户端对服务器发起请求的模式,服务端永远是被动的;
2、HTTP长连接中,每次数据交换除了真正的数据部分外,服务器和客户端还要大量交换HTTP header,信息交换效率很低。Websocket协议通过第一个request建立了TCP连接之后,之后交换的数据都不需要发送 HTTP header就能交换数据,这显然和原有的HTTP协议有区别所以它需要对服务器和客户端都进行升级才能实现(主流浏览器都已支持HTML5)。此外还有 multiplexing(多路复用)、不同的URL可以复用同一个WebSocket连接等功能。这些都是HTTP长连接不能做到的。
web通讯的改进方式:
1、轮询:
以频繁请求方式来保持客户端和服务端的同步,但是若服务端的数据无变化,会造成通信低效。
2、长轮询:
当服务端没有数据更新的时候,连接会保持一段时间周期直到数据或者状态改变或者过期,依次减少无效的客户端和服务端的交互。且当服务端数据变更频繁的话,这种机制和定时轮询毫无区别。
参考大神们的回答:
https://www.zhihu.com/question/20215561
https://zhuanlan.zhihu.com/p/23386938
在前端实现轮询?
轮询:通过定时器让程序每隔n秒执行一次操作。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
</head>
<body>
<h1>请选出最帅的男人</h1>
<ul>
{% for k,v in gg.items() %}
<li>ID:{{ k }}, 姓名:{{ v.name }} ,票数:{{ v.count }}</li>
{% endfor %}
</ul>
<script>
setInterval(function () {
location.reload();
},2000)
</script>
</body>
</html>
如何在前端实现长轮询?
简述跨域问题以及其解决办法?
跨域广义上指的是指一个域下的文档或脚本试图去请求另一个域下的资源。
广义的跨域:
1、资源跳转: a链接、location重定向、form表单提交;
2、资源嵌入:<link>、<script>、<img>、<frame>
等dom标签,还有样式中background:url()
、@font-face()
等文件外链;
3、脚本请求: js发起的ajax请求、dom和js对象的跨域操作等。
狭义上指的是浏览器的同源策略,即协议、域名、端口必须一致才算同源,才能互相访问,否则就算跨域且不能访问。
Jsonp跨域
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一*立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
CORS跨域
在响应头重设置Access-Control-Allow-Origin即可:
class CORSMiddleware(MiddlewareMixin):
def process_response(self,request,response):
# 允许你的域名来获取我的数据
response[' '] = "*"
# 允许你携带Content-Type请求头,
response['Access-Control-Allow-Headers'] = "Content-Type"
# 允许你发送DELETE,PUT
response['Access-Control-Allow-Methods'] = "DELETE,PUT"
return response
浏览器将CORS请求分成两类:简单请求和赋复杂请求。
简单请求(同时满足以下两大条件)
1、请求方法是以下三种方法之一:HEAD、GET、POST
2、HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type :只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同时满足上面两个条件,就属于非简单请求。
http相关面试题
列举Http请求中常见的请求方式:
GET、POST、
PUT、patch(修改数据)
HEAD(类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头)
DELETE
列举Http请求中的状态码:
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误
Http请求中常见的请求头:
user-agent
host
referer
cookie
content-type
js相关
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
<script>
var name = 'jack';
function fun() {
var name='alex';
function inner() {
alert(name)
}
return inner
}
// 类似函数装饰器
var ret = fun();
ret()
// 输出 ‘alex’
</script>
</body>
</html>
django、flask、tornado框架的比较?
django是大而全的框架,它的内部组件比较多,内部提供 ORM、Admin、中间件、Form、ModelForm、Session、缓存、信号、CSRF。
flask是微型框架,内部组件就比较少了,但是有很多第三方组件来扩展它,比如说有那个wtform(与django的modelform类似,表单验证)、flask-sqlalchemy(操作数据库的)、flask-session、flask-migrate、flask-script、blinker可扩展强,第三方组件丰富。所以对他本身来说有那种短小精悍的感觉。
django和flask的共同点:两个框架都没有写socket,所以他们都是利用第三方模块wsgi。
django和flask的区别:
1、内部使用的wsgi也是有些不同的:django本身运行起来使用wsgiref,而flask使用werkzeug wsgi。
2、请求管理不太一样:django是通过将请求封装成request对象,再通过参数传递,而flask是通过上下文管理机制。
Tornado是一个轻量级的Web框架,异步非阻塞 + 内置WebSocket功能。可以通过一个线程处理N个并发请求(处理IO)。内部组件:内部自己实现socket、路由系统、视图、模板、cookie、csrf。
列举django的内置组件?
form组件
作用:1、对用户请求的数据进行校验;2、生成HTML标签。
form对象是一个可迭代对象。
choice的数据如果从数据库获取可能会造成数据无法实时更新。
- 重写构造方法,在构造方法中重新去数据库获取值。
- ModelChoiceField字段
from django.forms import Form
from django.forms import fields
from django.forms.models import ModelChoiceField
class UserForm(Form):
name = fields.CharField(label='用户名',max_length=32)
email = fields.EmailField(label='邮箱')
ut_id = ModelChoiceField(queryset=models.UserType.objects.all())
# 依赖
class UserType(models.Model):
title = models.CharField(max_length=32)
def __str__(self):
return self.title
信号
django的信号其实就是django内部为开发者预留的一些自定制功能的钩子。
只要在某个信号中注册了函数,那么django内部执行的过程中就会自动触发注册在信号中的函数。
pre_init # django的model执行其构造方法前,自动触发
post_init # django的model执行其构造方法后,自动触发
pre_save # django的model对象保存前,自动触发
post_save # django的model对象保存后,自动触发
场景:
在数据库某些表中添加数据时,可以进行日志记录。
CSRF
防止用户直接向服务端发起POST请求。
对所有的post请求做验证,将django生成的一串字符串发送给我们,一种是从请求体发过来,一种是放在隐藏的标签里面用的是process_view
方案:先发送GET请求时,将token保存到:cookie、Form表单中(隐藏的input标签),
以后再发送请求时只要携带过来即可。
ContentType
contenttype是django的一个组件(app),
为我们找到django程序中所有app中的所有表并添加到记录中。
可以使用他再加上表中的两个字段实现:一张表和N张表创建FK关系。 - 字段:表名称 - 字段:数据行ID
应用:路飞表结构优惠券和专题课和学位课关联。