多有参考
全栈修炼手册
Python常见的170道面试题
关于python的面试题
python语言特性
-
python2 和 python3 的区别?
- print在python3中是函数必须加括号,在python2中为class。
- python2只能够默认的字符串类型是ASCII,python3中默认字符串类型是Unicode。
- python2中
/
的结果是整型python3中的是浮点型 - python2中使用xrange,python3中使用range。
xrange生成一个list对象,range生成一个range对象(可迭代,但不是迭代器)
python3中的基本数据类型
字符串(string)、数字(digit)、列表(list)、元组(tuple)、集合(sets)、字典(dictionary)。-
python3常用方法函数
- python内置结构时间复杂度
- 字符串内建函数
- python内置函数
- python中的拷贝(引用和copy()、deepcopy()的区别)
- 对于不可变对象没有意义
- 浅拷贝构造一个新的复合对象,然后(尽可能)将对它的引用插入到原始对象中。
- 深拷贝构造一个新的复合对象,然后递归地将复制对象插入到原始对象中找到的对象中。
- 了解enumerate嘛?
enumerate可以在迭代一个对象的时候同时获取当前对象的索引和值。for index, value in enumerate([a,b,c,d])
- isinstance 作用以及应用场景?
判断一个对象是否是另一个对象的子类
print(isinstance(True,int))
-
lambda
表达式格式以及应用场景?
匿名函数lambda x: x表达式
按照字典的值排序 d = {'a':2,'b':1,'c':3} print(dict(sorted(d.items(), key=lambda x:x[1]))) # {'b':1,'a':2,'c':3}
-
map()
函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
推荐使用列表推导式def f(x): return x * x r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) list(r) #[1, 4, 9, 16, 25, 36, 49, 64, 81] 列表推导式 [i*i for i in range(1,10)]
-
reduce()
把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
把序列[1, 3, 5, 7, 9]变换成整数13579,reduce就可以派上用场: from functools import reduce def fn(x, y): return x * 10 + y reduce(fn, [1, 3, 5, 7, 9]) #13579
-
filter()
也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
同样推荐使用列表推导式def is_odd(n): return n % 2 == 1 list(filter(is_odd, [1, 4, 9, 16, 25])) # 结果: [1, 9, 25] [i for i in [j*j for j in range(1,6)] if i%2 == 1]
-
zip()
函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。
我们可以使用 list() 转换来输出列表。
如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。#把元组 ("a","b") 和元组 (1,2),变为字典 {"a":1,"b":2} a = ("a", "b") b = (1, 2) print(dict(zip(a, b)))
#交换字典的键和值 s = {"A":1,"B":2} #方法一: dict_new = {value:key for key,value in s.items()} # 方法二: new_s= dict(zip(s.values(),s.keys()))
什么是闭包?
当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:
必须有一个内嵌函数
内嵌函数必须引用外部函数中的变量
外部函数的返回值必须是内嵌函数
装饰器本质就是一个闭包-
什么是装饰器?
装饰器就是一个函数,它可以在不需要做任何代码变动的前提下给一个函数增加额外功能,启动装饰的效果。比如插入日志def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper @log def now(): print("2019.8.20") >>> now() call now(): 2019.8.20 但是now.__name__变成了'wrapper',因为我们返回的就是wrapper,不过python早都考虑到了,完整实现如下: import functools def log(func): @functools.wraps(func) def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper
-
python解包?
解包在英文里叫做 Unpacking,就是将容器里面的元素逐个取出来a,b,c = [1,2,3]
这个过程就是解包,python中解包是自动完成的.-
代码中经常遇到的*args, **kwargs 含义及用法。
在函数定义中使用 args 和*kwargs 传递可变长参数。 *args 用来将参数打包成 tuple 给函数体调用。 **kwargs 打包关键字参数成 dict 给函数体调用。
合并两个字典 a = {"A":1,"B":2} b = {"C":3,"D":4} # a.update(b) print({**a,**b})
n = [1,2,3,4] a,*b,c = n a,b,c #1,[2,3],[3]
- 自动解包支持一切可迭代对象
- 可以用星号操作使等号左边的变量个数少于右边的元素个数
1.函数调用时,可以使用* 或**解包可迭代对象
-
-
python函数传参?
python中传的是对象的引用。
默认参数只初始化一次且必须指向不变对象from datetime import datetime def test(t=datetime.today()): print t if __name__ == "__main__": test() test() 两次调用输出为同一个值 建议常用下面一种方式 from datetime import datetime def test2(t = None): if t is None: t = datetime.today() print t if __name__ == "__main__": test() test()
def func(item, item_list=[]): item_lsit.append(item) print(item_lsit) func('xiaomi') #[xiaomi] func('iphone',item_list=['xiaomi','huawei'])#[xiaomi,huawei,iphone] func('vivo')#[xiaomi,vivo] 第一次初始化def时会生成可变对象的内存地址,然后将这个默认参数item_list与内存地址绑定。 在后面的调用中,如果指定了新的默认值,就会将原来的默认值覆盖掉,如果没有指认的话,就会用原来的默认值。
-
什么是列表推导式?字典推导式呢?集合推导式?
[x for x in range(10)]
#[[1,2],[3,4],[5,6]] 一行代码展开该列表,得出 [1,2,3,4,5,6] l = [[1,2],[3,4],[5,6]] x=[j for i in l for j in i] print(x)
d = {key: value for (key, value) in iterable}
# 把字典的key和value调换 d = {'a':'1', 'b':'2'} print({v:k for k,v in d.items()})
#如何把元组 ("a","b") 和元组 (1,2),变为字典 {"a":1,"b":2} a = ("a", "b") b = (1, 2) print(dict(zip(a, b)))
-
squared = {x**2 for x in [1, 1, 2]}
与列表推导式类似,唯一区别在于它使用大括号
什么是迭代器?
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter(可迭代对象) 和 next(迭代器)-
如何创建一个迭代器?
把一个类作为迭代器使用需要在类中实现__iter__()
和__next__()
__iter__()
方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。
__next__()
方法会返回下一个迭代器对象。class MyNumbers: def __iter__(self): self.a = 1 return self def __next__(self): if self.a <= 20: x = self.a self.a += 1 return x else: raise StopIteration myclass = MyNumbers() myiter = iter(myclass) for x in myiter: print(x)#1-20
-
什么是生成器?
- 在 Python 中,使用了 yield 的函数被称为生成器(generator)。
- 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
- 在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
- 调用一个生成器函数,返回的是一个迭代器对象。
#请将 [i for i in range(3)] 改成生成器 (i for i in range(3)) 不同的是生成式不必创建完整的list,从而节省大量的空间
单下划线和双下划线?
__foo__
: 一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例如__init__()
,__call__()
这些特殊方法
_foo
:一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.不能用from module import * 导入,其他方面和公有一样访问;
__foo:
这个有真正的意义:解析器用classname__foo来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随便访问,通过对象名.类名__xxx这样的方式可以访问.hasattr()、getattr()、setattr() 、delattr()的用法(python中的反射)?
配合dir()获取一个对象的所有属性和方法
hasattr(obj,'attr')可以判断一个对象是否含有某个属性,getattr(obj,'attr',default) 可以充当 get 获取对象属性的作用相当于obj.attr
。 setattr(obj,'attr','abc') 可以充当 obj.attr = "abc"的赋值操作,delattr(obj,'attr')相当于del obj.attr魔法方法以及用途。
init
类的初始化方法
new
对象实例化时第一个调用的方法
slots
动态的给类添加属性和方法
str
友好打印
repr
程序友好打印
call
对实例进行调用实例名()
property
使实例方法可以像实例属性一样访问
使用时只需要在对应的方法上加@property
同时会生成@方法名.setter
、@方法名.deleter
修饰的方法可用来升级取代getattr
和setattr
python中的元类
这个不常用,但是像ORM中还是需要的,比如在ORM中想定义一个父类而不是生成一张表就要用元类。-
@staticmethod和@classmethod
- 相同之处:@staticmethod和@classmethod都可以直接类名.方法名()来调用,不用实例化一个类。
- @staticmethod 经常有一些和类相关的功能但是运行时又不需要实例和类参数的参与。
@classmethod 需要在类中运行而不再实例中运行的方法
-
类变量和实例变量
- 类变量:是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。例如下例中,num_of_instance 就是类变量,用于跟踪存在着多少个Test 的实例。
- 实例变量:实例化之后,每个实例单独拥有的变量。
-
with的作用?
with 提供了一种机制,可以在进入和退出(无论正常退出,还是异常退出)某个语句块时,自动执行自定义的代码。
对这个机制的封装,叫做上下文管理器。with 是对上下文管理器的调用。一般访问文件时的操作 f = open( 'test.txt', 'r') try : data = f.read() finally: f.close() 同样的语句我们可以用with来实现 with open('test.txt', 'r') as f: data = f.read()
-
如何实现上下文管理器?
在python中实现了__enter__()
和__exit__()
方法对象就可以称为上下文管理器class File(object): def __init__(self, file_name, file_model): self.file_name = file_name self.file_model = file_model def __enter__(self): self.f = open(self.file_name, self.model) return self.f def __exit__(self): self.f.close() with File('test.txt', 'r') as f: data = f.read()
-
w、a+、wb文件写入模式的区别
- w打开一个文件用与写入,如果该文件存在则会将原有的文件覆盖,如果该文件不存在,则会创建一个新文件
- wb以二进制的格式去写入数据,如果该文件已经存在则会将原有的文件覆盖,如果该文件件不存在,则会创建一个新文件
- a+ 打开一个文件,用于读写,如果该文件已经存在,文件的指针将会放在文件的结尾位置,新的内容会被写入到已有内容的后面。如果该文件不存在,则会创建新文件用于读写
-
read、readline、readlines的区别?
- read 读取整个文件
- readline 读取下一行,使用生成器方法
- readlines 读取整个文件到一个迭代器以供我们遍历
鸭子类型
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
比如在python中,有很多file-like的东西,比如StringIO,GzipFile,socket。它们有很多相同的方法,我们把它们当作文件使用。
又比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等.-
单例模式
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
__new__()
在__init__()
之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例使用__new__方法实现 class Singleton(object): def __new__(cls, *args, **kw): if not hasattr(cls, '_instance'): orig = super(Singleton, cls) cls._instance = orig.__new__(cls, *args, **kw) return cls._instance class MyClass(Singleton): a = 1 import方法 作为python的模块是天然的单例模式 # mysingleton.py class My_Singleton(object): def foo(self): pass my_singleton = My_Singleton() # to use from mysingleton import my_singleton my_singleton.foo() 装饰器版本 def singleton(cls): instances = {} def getinstance(*args, **kw): if cls not in instances: instances[cls] = cls(*args, **kw) return instances[cls] return getinstance @singleton class MyClass: ... 共享属性实现 创建实例时把所有实例的__dict__指向同一个字典,这样它们具有相同的属性和方法. class Borg(object): _state = {} def __new__(cls, *args, **kw): ob = super(Borg, cls).__new__(cls, *args, **kw) ob.__dict__ = cls._state return ob class MyClass2(Borg): a = 1
字符串格式化方式有哪些?
%s(不推荐了),format,fstring(推荐)序列化(pickle和JSON)
通过dumps
和loads
进行序列化和反序列化
json序列化保存中文`json.dumps({"name":"小明"},ensure)ascii=False)-
python字符串编码?
"S".encode("gbk").decode("utf-8") 将编码为GBK的字符“S”解码成Unicode再编码成utf-8
操作系统
-
多进程、多线程和协程的区别?
- 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,
而进程有自己独立的地址空间 - 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
- 线程是处理器调度的基本单位,但进程不是
- 二者均可并发执行
- 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,
但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制 - 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
- 线程进程都是同步机制,而协程则是异步
- 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
- 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,
-
线程同步方式?
- 互斥锁:通过互斥机制防止多个线程同时访问公共资源
1.信号量:控制同一时刻多个线程访问统一资源的线程数 - 事件:通过通知的方式保持多个线程同步
- 互斥锁:通过互斥机制防止多个线程同时访问公共资源
-
进程间通信的方式
- 管道:管道是通过调用 pipe 函数创建的,fd[0] 用于读,fd[1] 用于写。
- 消息队列: 间接(内核)
相比于 FIFO,消息队列具有以下优点:
消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。 - 信号量:它是一个计数器,用于为多个进程提供对共享数据对象的访问。
- 共享内存:允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。
需要使用信号量用来同步对共享存储的访问。
多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用使用内存的匿名段。 - 套接字:与其它通信机制不同的是,它可用于不同机器间的进程通信。
python中如何使用多线程?
threading模块
threading.Thread类用于创建线程
start()方法启动线程
可以用join()等待线程结束python中如何使用多进程
multiprocessing模块
Multiprocessing.Process类实现多进程-
解释什么是同步、异步、阻塞、非阻塞?
引用知乎回答
1.同步与异步
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由调用者主动等待这个调用的结果。
而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 -
什么是死锁?
原因:- 竞争资源
- 程序推进顺序不当
必要条件:
- 互斥条件:每个资源要么已经分配给了一个进程,要么就是可用的。
- 请求和保持条件:已经得到了某个资源的进程可以再请求新的资源
- 不剥夺条件:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
- 环路等待条件:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
处理死锁基本方法:
- 预防死锁(摒弃除1以外的条件)
- 避免死锁(银行家算法)
- 检测死锁(资源分配图)
- 解除死锁
- 剥夺资源
- 撤销进程
-
什么是分页机制
逻辑地址和物理地址分离的内存分配管理方案- 程序的逻辑地址划分为固定大小的页(page)
- 物理地址划分同样大小的帧
- 通过页表对应逻辑地址和物理地址
-
什么是分段机制
- 分段是为了满足代码的一些逻辑需求
- 数据共享,数据保护,动态链接等
- 通过段表实现逻辑地址和物理地址的映射关系
每个段内部是连续内存分配,段和段之间是离散分配的
页是出于内存利用率的角度提出的离散分配机制
段是出于用户角度,用户数据保护,数据隔离等用途的管理机制
页的大小是固定的,操作系统决定;段大小不确定,用户程序决定 -
什么是虚拟内存
通过把一部分暂时不用的内存信息放到硬盘上- 局部性原理(一块内存被访问,时间上不远的将来还可能被访问,空间上周围的内存也可能被访问),程序运行时候只有部分必要的信息装入内存
- 内存中暂时不用的内容放到硬盘上
- 系统似乎提供了比实际内存大得多的容量,称之为虚拟内存
-
什么是内存抖动
本质上是频繁的页调动行为- 频繁的页调度,进程不断产生缺页中断
- 置换一个页,又不断的再次需要这个页
- 运行程序太多,页面替换策略不好。终止进程,或增加物理内存
-
python的垃圾回收机制
Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。- 引用计数
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。 - 标记-清除机制
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。 - 分代技术
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
举例: 当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
- 引用计数
当退出python时是否释放所有内存分配?
循环引用其他对象或引用自全局命名空间的对象的模块,在 Python 退出时并非完全释放。另外,也不会释放 c 库保留的内存部分GIL全局解释器锁
每个线程在执行的过程都需要先获取 GIL,保证同一时刻只有一个线程可以执行代码。
线程释放 GIL 锁的情况:在 IO 操作等可能会引起阻塞的 systemcall 之前,可以暂时释放 GIL,但在执行完毕后, 必须重新获取 GIL,Python3.x 使用计时器(执行时间达到阈值后,当前线程释放 GIL。
对于io密集型任务,python的多线程起到作用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。
计算机网络
TCP和UDP?
TCP提供面向连接的、可靠的数据流传输,而UDP提供的是非面向连接的、不可靠的数据流传输。
TCP传输单位称为TCP报文段,UDP传输单位称为用户数据报。
TCP注重数据安全性,UDP数据传输快,因为不需要连接等待,少了许多操作,但是其安全性却一般。
TCP对应的协议和UDP对应的协议
TCP对应的协议:
(1) FTP:定义了文件传输协议,使用21端口。
(2) Telnet:一种用于远程登陆的端口,使用23端口,用户可以以自己的身份远程连接到计算机上,可提供基于DOS模式下的通信服务。
(3) SMTP:邮件传送协议,用于发送邮件。服务器开放的是25号端口。
(4) POP3:它是和SMTP对应,POP3用于接收邮件。POP3协议所用的是110端口。
(5)HTTP:是从Web服务器传输超文本到本地浏览器的传送协议。
UDP对应的协议:
(1) DNS:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。
(2) SNMP:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。
(3) TFTP(Trival File Transfer Protocal),简单文件传输协议,该协议在熟知端口69上使用UDP服务。-
三次握手和四次挥手?
三次握手- 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三次握手的一部分。客户端把这段连接的序号设定为随机数 A。
- 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK 的确认码应为 A+1,SYN/ACK 包本身又有一个随机序号 B。
- 最后,客户端再发送一个ACK。当服务端受到这个ACK的时候,就完成了三路握手,并进入了连接创建状态。此时包序号被设定为收到的确认号 A+1,而响应则为 B+1。
四次挥手
注意: 中断连接端可以是客户端,也可以是服务器端. 下面仅以客户端断开连接举例, 反之亦然.- 客户端发送一个数据分段, 其中的 FIN 标记设置为1. 客户端进入 FIN-WAIT 状态. 该状态下客户端只接收数据, 不再发送数据.
- 服务器接收到带有 FIN = 1 的数据分段, 发送带有 ACK = 1 的剩余数据分段, 确认收到客户端发来的 FIN 信息.
- 服务器等到所有数据传输结束, 向客户端发送一个带有 FIN = 1 的数据分段, 并进入 CLOSE-WAIT 状态, 等待客户端发来带有 ACK = 1 的确认报文.
- 客户端收到服务器发来带有 FIN = 1 的报文, 返回 ACK = 1 的报文确认, 为了防止服务器端未收到需要重发, 进入 TIME-WAIT 状态. 服务器接收到报文后关闭连接. 客户端等待 2MSL 后未收到回复, 则认为服务器成功关闭, 客户端关闭连接.
TCP连接状态?
CLOSED:初始状态。
LISTEN:服务器处于监听状态。
SYN_SEND:客户端socket执行CONNECT连接,发送SYN包,进入此状态。
SYN_RECV:服务端收到SYN包并发送服务端SYN包,进入此状态。
ESTABLISH:表示连接建立。客户端发送了最后一个ACK包后进入此状态,服务端接收到ACK包后进入此状态。
FIN_WAIT_1:终止连接的一方(通常是客户机)发送了FIN报文后进入。等待对方FIN。
CLOSE_WAIT:(假设服务器)接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之后,自然是需要立即回复ACK包的,表示已经知道断开请求。但是本方是否立即断开连接(发送FIN包)取决于是否还有数据需要发送给客户端,若有,则在发送FIN包之前均为此状态。
FIN_WAIT_2:此时是半连接状态,即有一方要求关闭连接,等待另一方关闭。客户端接收到服务器的ACK包,但并没有立即接收到服务端的FIN包,进入FIN_WAIT_2状态。
LAST_ACK:服务端发动最后的FIN包,等待最后的客户端ACK响应,进入此状态。
TIME_WAIT:客户端收到服务端的FIN包,并立即发出ACK包做最后的确认,在此之后的2MSL时间称为TIME_WAIT状态。在浏览器中输入网址后执行的全部过程
1.查询DNS获取域名对应IP 2.浏览器与对应IP发起HTTP三次握手 3.TCP/IP连接建立起来之后浏览器就可以向服务器发送HTTP请求了 4. TLS握手 5. HTTP服务器请求处理并根据请求参数返回一些经过后端处理生成的HTML页面代码 6.浏览器拿到代码开始解析和渲染,最终把完整的页面呈现给用户-
HTTP是无状态的如何实现识别用户呢?(Cookie和Session)
- session一般是服务器生成之后给客户端(通过url参数或cookie)
- cookie是实现session的一种机制,通过HTTPcookie字段实现
- session通过在服务器保存sessionid识别用户客户端
-
HTTP和HTTPS的区别
- http是HTTP协议运行在TCP之上。所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。
- https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份,如果配置了客户端验证,服务器方也可以验证客户端的身份。
- https协议需要到ca申请证书,一般免费证书很少,需要交费。
- http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议
- http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的
- HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全
请求和响应的组成?
请求:状态行 (POST / HTTP/1.1)请求头(常用HTTP请求头)消息主体(GET为空)
响应:状态行(HTTP/1.1 200 OK)响应头(常见HTTP响应头)响应正文-
常见响应状态码?
状态码 类别 原因短语 1XX Informational(信息性状态码) 接收的请求正在处理 2XX Success(成功状态码) 请求正常处理完毕 3XX Redirection(重定向状态码) 需要进行附加操作以完成请求 4XX Client Error(客户端错误状态码) 服务器无法处理请求 5XX Server Error(服务器错误状态码) 服务器处理请求出错 100 Continue :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
200 OK
301 Moved Permanently :永久性重定向
302 Found :临时性重定向
400 Bad Request :请求报文中存在语法错误。
401 Unauthorized :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
403 Forbidden :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
404 Not Found
500 Internal Server Error :服务器正在执行请求时发生错误。
503 Service Unavailable :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 GET和POST的区别
GET 被强制服务器支持
浏览器对URL的长度有限制,所以GET请求不能代替POST请求发送大量数据
GET请求发送数据更小
GET请求是不安全的
GET请求是幂等的
幂等的意味着对同一URL的多个请求应该返回同样的结果
POST请求不能被缓存
POST请求相对GET请求是「安全」的
这里安全的含义仅仅是指是非修改信息
GET用于信息获取,而且是安全的和幂等的
所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。
POST是用于修改服务器上的资源的请求
发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠
引申:说完原理性的问题,我们从表面上来看看GET和POST的区别:
GET是从服务器上获取数据,POST是向服务器传送数据。 GET和 POST只是一种传递数据的方式,GET也可以把数据传到服务器,他们的本质都是发送请求和接收结果。只是组织格式和数据量上面有差别,http协议里面有介绍
GET是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。POST是通过HTTP POST机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。 因为GET设计成传输小数据,而且最好是不修改服务器的数据,所以浏览器一般都在地址栏里面可以看到,但POST一般都用来传递大数据,或比较隐私的数据,所以在地址栏看不到,能不能看到不是协议规定,是浏览器规定的。
对于GET方式,服务器端用Request.QueryString获取变量的值,对于POST方式,服务器端用Request.Form获取提交的数据。 没明白,怎么获得变量和你的服务器有关,和GET或POST无关,服务器都对这些请求做了封装
GET传送的数据量较小,不能大于2KB。POST传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。 POST基本没有限制,我想大家都上传过文件,都是用POST方式的。只不过要修改form里面的那个type参数
GET安全性非常低,POST安全性较高。 如果没有加密,他们安全级别都是一样的,随便一个监听器都可以把所有的数据监听到。长/短连接的优缺点?
长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。
对于频繁请求资源的客户来说,较适用长连接。
client与server之间的连接如果一直不关闭的话,会存在一个问题,
随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,
如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;
如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,
这样可以完全避免某个蛋疼的客户端连累后端服务。
短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。
但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。TCP长/短连接的应用场景?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。
每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,
再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,
再次处理时直接发送数据包就OK了,不用建立TCP连接。
例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,
而且频繁的socket 创建也是对资源的浪费。
而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,
那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。什么是IO多路复用?
操作系统提供的同事监听多个socket的机制,一般阻塞 I/O 只能阻塞一个 I/O 操作,而 I/O 复用模型能够阻塞多个 I/O 操作,所以才叫做多路复用。
I/O 多路复用是用于提升效率,单个进程可以同时监听多个网络连接 IO。 在 IO 密集型的系统中, 相对于线程切换的开销问题,IO 多路复用可以极大的提升系统效率。select、poll、epoll 模型的区别?
select,poll,epoll 都是 IO 多路复用的机制。I/O 多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
select 模型: select 目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select 的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。
poll 模型: poll 和 select 的实现非常类似,本质上的区别就是存放 fd 集合的数据结构不一样。select 在一个进程内可以维持最多 1024 个连接,poll 在此基础上做了加强,可以维持任意数量的连接。
但 select 和 poll 方式有一个很大的问题就是,我们不难看出来 select 是通过轮训的方式来查找是否可读或者可写,打个比方,如果同时有 100 万个连接都没有断开,而只有一个客户端发送了数据,所以这里它还是需要循环这么多次,造成资源浪费。所以后来出现了 epoll 系统调用。
epoll 模型: epoll 是 select 和 poll 的增强版,epoll 同 poll 一样,文件描述符数量无限制。但是也并不是所有情况下 epoll 都比 select/poll 好,比如在如下场景:在大多数客户端都很活跃的情况下,系统会把所有的回调函数都唤醒,所以会导致负载较高。既然要处理这么多的连接,那倒不如 select 遍历简单有效。
数据库
什么是事务?
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
AUTOCOMMIT
MySQL 默认 自动提交模式。也就是说,如果不显式使用 START TRANSACTION 语句来开始一个事务,那么每个查询都会被当做一个事务自动提交-
数据库ACID?
原子性:一个事务中所有操作全部成功或失败
一致性:事务开始和结束之后数据完整性没有被破坏
隔离性:允许多个事务同时对数据库修改和读取
持久性:事务结束后,修改是用久不会丢失的
三范式?
1NF:属性不可分
2NF:属性完全依赖于主键 [消除部分子函数依赖]
3NF:属性不依赖于其它非主属性 [消除传递依赖]并发一致性问题?
丢失修改:并发的写入造成其中一些修改丢失
脏读:一个事务读取到另一个事务没有提交的修改
幻读:在一个事务第二次查出现第一次没有的结构
非重复读:一个事务重复读两次得到的结果不一样事务隔离级别(为解决并发异常)
每种隔离级别解决一个问题
读未提交:的的事务可以读取到未提交的改变
读已提交:只能读取已经提交的数据
可重复读:同一事务先后查询结果一样(mysql innoDB默认实现可重复读级别)
串行化:事务玩去串行化的执行,隔离级别最高,执行效率最低-
你所知道的锁?
乐观锁(先修改,更新时发现数据已经变了就回滚)
用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
悲观锁(一锁二查三更新)
与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。-
排它锁
使用方式:在需要执行的语句后面加上 for update 就可以了
排它锁exclusive lock(也叫 writer lock)又称写锁。排它锁是悲观锁的一种实现。
若事务 1 对数据对象 A 加上 X 锁,事务 1 可以读 A 也可以修改 A,其他事务不能再对 A 加任何锁,直到事物 1 释放 A 上的锁。这保证了其他事务在事物 1 释放 A 上的锁之前不能再读取和修改 A。排它锁会阻塞所有的排它锁和共享锁
读取为什么要加读锁呢:防止数据在被读取的时候被别的线程加上写锁
-
共享锁
共享锁又称读锁(read lock),是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
如果事务 T 对数据 A 加上共享锁后,则其他事务只能对 A 再加共享锁,不能加排他锁。获得共享锁的事务只能读数据,不能修改数据
在查询语句后面增加 lock in share mode,MySQL 会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。
加上共享锁后,对于 update,insert,delete 语句会自动加排它锁。
-
什么是索引?
索引是数据表中一个或多个列进行排序的数据结构
索引能大幅度提升检索速度
创建、更新索引本身也会消耗时间和空间什么是B-Tree?
多路平衡查找树(每个节点最多m(m>=2)个孩子,称为m阶或者度)
叶节点具有相同的深度
节点中的数据key从左到右是递增的什么是B+Tree?
mysql实际使用B+Tree作为输赢的数据结构
只在叶子节点带有指向记录的指针(可以增加树的度)
叶子节点通过指针相连(实现范围查找)mysql索引类型
普通索引CREATE INDEX
唯一索引,索引列的值必须唯一CREATE UNIQUE INDEX
主键索引PRIMARY KEY
一个表只能有一个
全文索引FULLTEXT INDEX
,InnoDB不支持什么时候创建索引?
经常用到查询条件的字段WHERE条件
经常用作表连接的字段
经常出现在ORDER BY, GROUP BY
之后的字段知道Redis吗?
是一个完全开源免费的key-value内存数据库
通常被认为是一个数据结构服务器,主要是因为其有着丰富的数据结构
string:可以是字符串、整数或浮点数
list:一个链表,链表上的每个节点都包含了一个字符串
set:包含字符串的无序收集器,并且被包含的每个字符串都是独一无二的、各不相同
zset:字符串成员与浮点数分值之间的有序映射,元素的排列顺序由分值的大小决定
hash:包含键值对的无序散列表-
了解Redis的事务吗?
简单理解,可以认为 redis 事务是一些列 redis 命令的集合,并且有如下两个特点: 1.事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 2.事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。 一般来说,事务有四个性质称为ACID,分别是原子性,一致性,隔离性和持久性。 一个事务从开始到执行会经历以下三个阶段:开始事务、命令入队、执行事务
代码示例:import redis import sys def run(): try: conn=redis.StrictRedis('192.168.80.41') # Python中redis事务是通过pipeline的封装实现的 pipe=conn.pipeline() pipe.sadd('s001','a') sys.exit() #在事务还没有提交前退出,所以事务不会被执行。 pipe.sadd('s001','b') pipe.execute() pass except Exception as err: print(err) pass if __name__=="__main__": run()