开发python 面试题

01 python 基础

1.1 列表常用方法

# 1. append 用于在列表末尾追加新的对象
a = [1,2,3]
a.append(4)  # the result : [1,2,3,4]
# 2. count方法统计某个元素在列表中表现得次数
a = ['aa','bb','cc','aa','aa']
print(a.count('aa'))  # the result : 3
# 3.extend方法可以在列表得末尾一次性追加另一个序列中得多个值
a = [1,2,3]
b = [4,5,6]
a.extend(b)  # the result : [1,2,3,4,5,6]
# 3. index 函数用于从列表中找到某个值第一个匹配项得索引位置、
a = [1,2,3,1]
print(a.index(1))  # the result : 0
# 5. insert 方法用于将对象插入到列表中
a = [1,2,3]
a.insert(0,'aa')  # theresult : ['aa',1,2,3]
# 6.pop 方法会移除列表中得一个元素(默认是最后一个), 并且返回该元素的值
a = [1,2,3]
a.pop()  # the result : [1, 2]
a.pop(0)
​
# 7. remove方法用于移除列表中的元素
a = ['aa','bb','cc','aa']
a.remove('aa')  # the result : ['bb','cc','aa']
# 8. reverse方法将列表中的元素反向存放
a = ['a','b','c']
a.reverse()  # the result : ['c','b','a']
#9. sort 方法用于在原位置对列表进行排序,意味着改变原来的列表,让其中的元素按一定顺序排列
a = ['a','b','c',1,2,3]
a.sort()  # the result : [1,2,3,'a','b','c']
# 10. enumrate
li = [11,22,33]
for k,v in enumerate(li, 1):
    print(k,v)

 


1.2 字符串常用方法
# 1. find方法可以在一个较长的字符串中查找子串,他返回子串所在位置的最左端索引,如果没有找到则返回-1
a = 'abcdefsdada'
print(a.find('abc'))  # the result : 0
print(a. find('abc',10,100))  # the result : 11 指定查找的起始和结束查找位置
# 2. join方法是非常重要的字符串方法,他是split方法的逆方法,用来连接序列中的元素并且需要需要被连接的元素都必须是字符串。
a = ['1','2','3']
print('+',join(a))  # the result : 1+2+3
# 3. split方法,是一个非常重要的字符串,他是join的逆方法,用来将字符串分割成序列
print('1+2+3+4'.spkit('+'))  # the result : ['1','2','3','4']
# 4. strip 方法返回去除收尾空格(不包括内部)的字符串
print("   test   test    ".strip())                  #the result :“test   test”
# 5. replace 方法返回某字符串所有匹配项均匀被替换之后得到字符串
print("This is a test".replace('is','is_test'))     #the result : This_test is_test a test

 


1.3 字符串常用方法
# 1. clear方法清除字典中所有的项, 这是一个原地操, 所有无返回值(或者说返回None)
d = {'name':"tom"}
d.clear()
print(d)                                         #the result : {}
# 2. fromkeys方法使用给定的键建立新的字典,每个键都对应一个默认的值None
print({}.fromkeys(['name','age']))              #the result : {'age': None, 'name': None}
# 3. get方法是个更宽松的访问字典想的方法,如果试图访问字典中不存在的项时不会报错仅会 返回: None
d = {'Tom':8777,'Jack':8888,'Fly':6666}
print(d.get('Tom'))                              #the result :     8777
print(d.get('not_exist'))                       #the result :     None
# 4. for 循环字典有三种方法 
d = {'Tom':8777,'Jack':8888,'Fly':6666}
for k,v in d.items():
    print(k,v)
for k in d.values():
    print(k)
for k in d.keys():
    print(k)
​
# 5. pop 方法用于获得对应与给定键的值,然后将这个“键-值”对从字典中移除
d = {'Tom':8777,'Jack':8888,'Fly':6666}
v = d.pop('Tom')
print(v)                    #8777
# 6. setdefault方法在某种程度上类似与get方法,能够获得与给定键相关联的值,除此之外,setdefault还能再字典中不含有给定键的情况下设定相应的键值
d = {'Tom':8777,'Jack':8888,'Fly':6666}
d.setdefault('Tom')                           #the result : 8777
print(d.setdefault('Test'))                  #the result : None
print(d)                                      #{'Fly': 6666, 'Jack': 8888, 'Tom': 8777, 'Test': None}
# 7. update方法可以利用一个字典项更新另一个字典,提供的字典中的项会被添加到旧的字典中,如果相同的键则会被覆盖
d = {'Tom':8777,'Jack':8888,'Fly':6666}
a = {'Tom':110,'Test':119}
d.update(a)
print(d)                                       #the result :{'Fly': 6666, 'Test': 119,'Jack': 8888, 'Tom': 110}
# 8. 将两个列表组合成字典
keys = ['a', 'b']
values = [1, 2]
print(dict(zip(keys,values)))                                      # {'a': 1, 'b': 2}

 


1.4 集合常用方法
list_1 = [1,2,3,4,5,1,2]
#1、去重(去除list_1中重复元素1,2)
list_1 = set(list_1)                                    #去重: {1, 2, 3, 4, 5}
print(list_1)
list_2 = set([4,5,6,7,8])
​
#2、交集(在list_1和list_2中都有的元素4,5)
print(list_1.intersection(list_2))                      #交集: {4, 5}
#3、并集(在list_1和list_2中的元素全部打印出来,重复元素仅打印一次)
print(list_1.union(list_2))                             #并集: {1, 2, 3, 4, 5, 6, 7, 8}
#4、差集
print(list_1.difference(list_2))                        #差集:在list_1中有在list_2中没有:   {1, 2, 3}
print(list_2.difference(list_1))                        #差集:在list_1中有在list_2中没有:   {8, 6, 7}

 


1.5 进程

 

1.5.1 进程定义

  1. 进程是资源分配最小单位

  2. 当一个可执行程序被系统执行(分配内存等资源)就变成了一个进程

1.5.2 进程定义拓展回答内容

  1. 程序并不能单独运行,只有将程序装载到内容中,系统为它分配资源才能运行,这种执行的程序就称之为进程

  2. 程序和进程的区别就在于:程序是指令的集合,他是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念

  3. 在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发的执行

  4. 进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的

  5. 进程之间有自己独立的内存,各种进程之间不能相互访问

  6. 创建一个新线程很简单,创建新的进程需要对父进程进行复制

1.5.3 进程间互相访问数据的四种方法

注:不同进程间内存是不共享的,所以相互之间不能访问对方数据

  1. 利用 Queues 实现父进程到子进程(或子进程间) 的数据传递

  2. 使用管道 pipe 实现两个进程间数据传递

  3. Managers 实现很多进程间数据共享

  4. 借助redis中间件进行数据共享

1.5.4 进程池

from  multiprocessing import Process,Pool
import time,os
​
def foo(i):
    time.sleep(2)
    print("in the process",os.getpid()) #打印子进程的pid
return i+100def call(arg):
print('-->exec done:',arg,os.getpid())
​
if __name__ == '__main__':
    pool = Pool(3)                      #进程池最多允许5个进程放入进程池
    print("主进程pid:",os.getpid())     #打印父进程的pid
    for i in range(10):
       #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
        pool.apply_async(func=foo, args=(i,),callback=call)
        #用法2 串行 启动进程不在用Process而是直接用pool.apply()
        # pool.apply(func=foo, args=(i,))
    print('end')
    pool.close()    #关闭pool
    pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

 


1.5.5 进程和程序的区别
  1. 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体

  2. 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,他应创建而产生,因调度执行因等待资源或事件而被处于等待状态,因完成任务而被取消

  3. 进程是系统进行资源分配和调度的一个独立单位

  4. 一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)

  5. 一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制快来唯一的表示每个进程

1.6 线程

1.6.1 线程定义

  1. 线程是操作系统调度的最小单位

  2. 它被包含在进程之中,是进程中的实际运作单位

  3. 进程本事是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合

1.6.1.1 线程定义拓展回答内容

  1. 线程是操作系统能够进行运算调度的最小的。它被包含在进程之中,是进程中的实际运作单位

  2. 一条线程指的是进程中一个单位顺序的控制流,一个进程中可以多个线程,每条线程并行执行不同的任务

  3. 无论你启动多少个线程,你有多少个cpu,Python在执行的时候就会淡定的同一时刻只允许一条线程运行

  4. 进程本身是无法自觉执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合

  5. 所有在同一个进程里的线程是共享一块内存空间的,不同进程间内存空间不同

  6. 同一个进程中的各线程可以相互访问资源,线程可以操作同进程中的其他线程,但进程仅能操作与进程

  7. 两个进程想通信,必须要通过一个代理

  8. 对主线程的修改可能会影响其他子线程,对进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存、

1.6.2 进程和线程的区别

  1. 进程包含线程

  2. 进程共享内存空间

  3. 进程内存是独立的(不可互相访问)

  4. 进程可以生成于线程,子进程之间相互不能互相访问(相当于在父级进程克隆两个进程)

  5. 在一个进程里面线程之间可以交流。两个进程想要通信,必须通过一个中间代理来实现

  6. 创建新线程很简单,创建新进程需要对其父进程进行克隆。

  7. 一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。

  8. 父进程可以修改不影响子进程,但不能修改子进程

  9. 线程可以帮助应用程序同时做多件事

1.6.3 for 循环同时启动多个线程


import threading
import time
​
def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()

 

1.6.4 t.join(): 实现所有线程都执行结束后再执行主线程

import threading
import time
start_time = time.time()
​
def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
t_objs = []    #将进程实例对象存储在这个列表中
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()          #启动一个线程,程序不会阻塞
    t_objs.append(t)
print(threading.active_count())    #打印当前活跃进程数量
for t in t_objs: #利用for循环等待上面50个进程全部结束
    t.join()     #阻塞某个程序
print(threading.current_thread())    #打印执行这个命令进程
print("----------------all threads has finished.....")
print(threading.active_count())
print('cost time:',time.time() - start_time)

 


1.6.5 setDaemon(): 守护线程

守护线程,主线程退出时, 需要子线程随主线程退出

import threading
import time
start_time = time.time()
​
def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.setDaemon(True)  #把当前线程变成守护线程,必须在t.start()前设置
    t.start()          #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)

 


1.6.6 GIL 全局解释器锁

GIL全局解释器锁:保证同一时间仅有一个线程对资源有操作权限

作用: 在一个进程内,同一时刻只能有一个线程执行

说明:python多线程中GIL锁只是在CPU操作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多

  1. 为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL

  2. GIL在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序里实际开了多少条线程

  3. 为了解决这个问题,CPython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据

  4. python之所以会产生这种不好的状况是因为python启用一个线程是调用操作系统原生线程,就是C接口

  5. 但是这仅仅是CPython这个版本的问题,在PyPy,中就没有这种缺陷

1.6.7 线程锁

  1. 当一个线程对某个资源进行CPU计算的操作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其操作

  2. 这样就可以防止还未计算完成,释放GIL锁后其他线程对这个资源操作导致混乱问题

1.6.7.1 用户锁使用举例

import time
import threading
lock = threading.Lock()          #1 生成全局锁
def addNum():
    global num                  #2 在每个线程中都获取这个全局变量
    print('--get num:',num )
    time.sleep(1)
    lock.acquire()              #3 修改数据前加锁
    num  -= 1                   #4 对此公共变量进行-1操作
    lock.release()              #5 修改后释放

 

1.6.8 Semaphore (信号量)
  1. 互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据

  2. Semaphore 的作用就好比银行只有一个窗口可以等待,在Semaphore的作用下有多个窗口可以完成任务和供人等待的。

  3. 作用就是同一时刻允许运行的线程数量

1.6.9 线程池实现并发

import requests
from concurrent.futures import ThreadPoolExecutor
​
def fetch_request(url):
    result = requests.get(url)
    print(result.text)
​
url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]
​
pool = ThreadPoolExecutor(10)            # 创建一个线程池,最多开10个线程
for url in url_list:
    pool.submit(fetch_request,url)       # 去线程池中获取一个线程,线程去执行fetch_request方法
​
pool.shutdown(True)                      # 主线程自己关闭,让子线程自己拿任务执行

 

1.7 协程

1.7.1 什么是协程(进入上一次调用的状态)

  1. 协程,又称微线程,纤程,协程是一种用户态的轻量级线程。

  2. 线程的切换会保存到CPU的栈里,协程拥有自己的寄存器上下文和栈,

  3. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈

  4. 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态

  5. 协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)


1.7.2. 协程缺点(无法利用多核资源)

协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上,线程阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

1.7.3 协程为何能处理大并发1:Greenlet遇到I/O手动切换

  1. 协程之所以快是因为遇到I/O操作就切换(最后只有CPU运算)

  2. greenlet可以实现手动的对各个协程之间切换 。

  3. 其实Gevent模块仅仅是对greenlet的再封装,将I/O间的手动切换变成自动切换

1.7.4 协程为何能处理大并发2:Gevent遇到I/O自动切换

  1. Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程

  2. 在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程

  3. Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

  4. Gevent原理是只要遇到I/O操作就会自动切换到下一个协程

1.7.5 使用协程处理并发

注:Gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理

import gevent
import requests
from gevent import monkey
​
monkey.patch_all()
​
# 这些请求谁先回来就先处理谁
def fetch_async(method, url, req_kwargs):
    response = requests.request(method=method, url=url, **req_kwargs)
    print(response.url, response.content)
​
# ##### 发送请求 #####
gevent.joinall([
    gevent.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={}),
    gevent.spawn(fetch_async, method='get', url='https://www.google.com/', req_kwargs={}),
    gevent.spawn(fetch_async, method='get', url='https://github.com/', req_kwargs={}),
])

 


1.8 sellect, poll, epoll

1.8.1 select(能监控数量有限,不能告诉用户程序具体哪个连接有数据)

  1. select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点

  2. select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024

  3. select监控socket连接时不能准确告诉用户是哪个,比如:现在用socket监控10000链接,如果其中有一个链接有数据了,select就会告诉用户程序,你有socket来数据了,那样就只能自己循环10000次判断哪个活跃

1.8.2 poll(和select一样,仅仅去除了最大监控数量)

  1. poll和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制

  2. 可以理解为poll是一个过渡阶段,大家也都不用他

1.8.3 epoll (不仅没有最大监控数量限制,还能告诉用户程序哪个连接有活跃)

注:epoll被认为是linux下性能最好的多路io就绪通知方法

  1. epoll直到Linux2.6(centos6以后)才出现了由内核直接支持

  2. Epoll没有最大文件描述符数量限制

  3. epoll最重要的优点是他可以直接告诉用户程序哪一个,比如现在用epoll去监控10000个socket链接,交给内核去监测,现在有一个连接有数据了,在有有一个连接有数据了,epoll会直接高数用户程序哪个连接有数据了

1.8.4 epoll能实现高并发原理

  1. epoll() 中内核则维护一个链表,epoll_wait 直接检查链表是不是空就知道是否有文件描述符准备好了。

  2. 在内核实现中 epoll 是根据每个 sockfd 上面的与设备驱动程序建立起来的回调函数实现的。

  3. 某个 sockfd 上的事件发生时,与它对应的回调函数就会被调用,把这个 sockfd 加入链表。

  4. epoll上面链表中获取文件描述,这里使用内存映射(mmap)技术, 避免了复制大量文件描述符带来的开销内存映射(mmap):内存映射文件,是由一个文件到一块内存的映射,将不必再对文件执行I/O操作

1.9 装饰器

1.9.1 装饰器定义

不能修改被装饰函数的源代码,不能修改被装饰函数的调用方式,为其他函数添加其他功能

1.9.2 使用高阶函数模拟装饰器

#! /usr/bin/env python
# -*- coding: utf-8 -*-
import time
def timer(func):
    start_time = time.time()
    func()
    print '函数执行时间为', time.time() - start_time
def test():
    print '开始执行test'
    time.sleep(3)
    print 'test执行结束'
timer(test)
'''
开始执行test
test执行结束
函数执行时间为 3.00332999229
'''

 


1.9.3 计算运行时间装饰器
import time
def timer(func):   #timer(test1)  func=test1
    def deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)      #run test1
        stop_time = time.time()
        print("running time is %s"%(stop_time-start_time))
    return deco
@timer     # test1=timer(test1)
def test1():
    time.sleep(3)
    print("in the test1")
test1()

 


1.9.4 装饰器使用场景

授权:装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中

日志:在记录日志的地方添加装饰器

缓存:通过装饰器获取缓存中的值

1.10 生成器

1.10.1 生成器定义

生成器可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他数据类型需要调用自己的内置iter方法)

在Python中,一边循环,一边计算的机制,称为生成器。

1.10.2 生成器的作用

  1. 通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的。

  2. 而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

  3. 所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?

  4. 这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

1.10.3 生成器工作原理

  1. 生成器是这样一个函数,它记住上一次返回时在函数体中的位置。

  2. 对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。

  3. 生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造中的位置。

  4. 生成器是一个函数,而且函数的参数都会保留。

  5. 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的

1.10.4 yield 生成器运行机制

在Python中,yield就是这样的一个生成器。

  1. 当你问生成器要一个数时,生成器会执行,直至出现 yield 语句,生成器把yield 的参数给你,之后生成器就不会往下继续运行。

  2. 当你问他要下一个数时,他会从上次的状态开始运行,直至出现yield语句,把参数给你,之后停下。如此反复

  3. 在python中,当你定义一个函数,使用了yield关键字时,这个函数就是一个生成器

  4. 它的执行会和其他普通的函数有很多不同,函数返回的是一个对象,而不是你平常所用return语句那样,能得到结果值。如果想取得值,那得调用next()函数

  5. 每当调用一次迭代器的next函数,生成器函数运行到yield之处,返回yield后面的值且在这个地方暂停,所有的状态都会被保持住,直到下次next函数被调用,或者碰到异常循环退出。

# yield实现fib数
def fib(max_num):
    a, b = 1, 1
    while a < max_num:  # 当 a < max_num 则执行循环
        yield b
        a, b = b, a + b  # 生成一个生成器:[1,2, 3, 5, 8, 13]
​
​
g = fib(10)  
print(g.__next__())  # 第一次调用返回:1
print(g.__next__())  # 第一次调用返回:2
print(g.__next__())  # 第一次调用返回:3
print(list(g))  # 把剩下元素变成列表:[5, 8, 13]

 


1.11 迭代器

1.11.1 迭代器定义

  1. 迭代器是访问集合内元素的方式,迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束

  2. 迭代器仅是一容器对象,它有两个基本方法


next 方法:返回容器的下一个元素
__iter__ 方法:返回迭代器自身

a = iter([1,2,])              #生成一个迭代器
print(a.__next__())
print(a.__next__())
print(a.__next__())           #在这一步会引发 “StopIteration” 的异常

1.11.2 生成器和迭代器之间的区别

在使用生成器时,我们创建一个函数;在使用迭代器时,我们使用内置函数iter()和next()。 在生成器中,我们使用关键字‘yield’来每次生成/返回一个对象。 生成器中有多少‘yield’语句,你可以自定义。 每次‘yield’暂停循环时,生成器会保存本地变量的状态。而迭代器并不会使用局部变量,它只需要一个可迭代对象进行迭代。 使用类可以实现你自己的迭代器,但无法实现生成器。 生成器运行速度快,语法简洁,更简单。 迭代器更能节约内存。

1.12 面向对象

1.12.1 面向对象三大特性: 封装,继承,多态

1.12.1.1 分装

1.在类中对数据的赋值、内部调用对外部用户是透明的

  1. 这使类变成了一个胶囊或容器,里面包含着类的数据和方法

  2. 作用:

  1)防止数据被随意修改

  2)使外部程序不需要关注对象内部的构造,只需要通过对外提供的接口进行直接访问

1.12.1.2 Inheritance 继承(代码重用)

  1. 一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承

  2. 比如CS中的警察和恐怖分子,可以将两个角色的相同点写到一个父类中,然后同时去继承它

  3. 使用经典类: Person.init(self,name,age) 并重写写父类Person的构造方法,实现,先覆盖,再继承,再重构

1.12.1.3 Polymorphism 多态(接口重用)

1.多态是面向对象的重要特性,简单点说:“一个接口,多种实现”

  1. 指一个基类中派生出了不同的子类,且每个子类在继承同样的方法名的同时又对父类的方法做了不同的实现

  2. 这就是同一种事物表现出的多种形态

  3. 比如黄种人继承了人talk这个功能,但是他说的是中文,而美国人的talk是英文,但是他们是同样的talk

作用:简单的讲就是允许父类调用子类的方法

1.12.2 静态方法、类方法、属性方法

1.12.2.1 静态方法

  1. 作用:静态方法可以更好的组织代码,防止代码变大后变得比较混乱。

  2. 特性: 静态方法只是名义上归类管理,实际上在静态方法里访问不了类或则实例中的任何属性

  3. 静态方法使用场景:

  1)我们要写一个只在类中运行而不在实例中运行的方法.

  2)经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法.

  3)比如更改环境变量或者修改其他类的属性等能用到静态方法.

  4)这种情况可以直接用函数解决, 但这样同样会扩散类内部的代码,造成维护困难.

  1. 调用方式: 既可以被类直接调用,也可以通过实例调用

class Dog(object):
    def __init__(self,name):
        self.name = name
    @staticmethod
    def eat():
        print("I am a static method")
d = Dog("ChenRonghua")
d.eat()                     #方法1:使用实例调用
Dog.eat()                   #方法2:使用类直接调用

 

1.12.2.2 类方法

  1. 作用:无需实例化直接被类调用。

  2. 特性: 类方法只能访问类变量,不能访问实例变量。

  3. 类方法使用场景: 当我们还未创建实例,但是需要调用类中的方法。

  4. 调用方式: 既可以被类直接调用,也可以通过实例调用。


class Dog(object):
    name = '类变量' #在这里如果不定义类变量仅定义实例变量依然报错
    def __init__(self,name):
        self.name = '实例变量'
        self.name = name
    @classmethod
    def eat(self,food):
        print("%s is eating %s"%(self.name,food))
Dog.eat('baozi')                   #方法1:使用类直接调用
d = Dog("ChenRonghua")          
d.eat("包子")                      #方法2:使用实例d调用

 

1.12.2.3 属性方法

作用:属性方法把一个方法变成一个属性,隐藏了实现细节,调用时不必加括号直接d.eat即可调用self.eat()方法

class Dog(object):
    def __init__(self, name):
        self.name = name
        
    @property
    def eat(self):
        print(" %s is eating" % self.name)
d = Dog("ChenRonghua")
d.eat()
# 调用会出以下错误, 说NoneType is not callable, 因为eat此时已经变成一个静态属性了, 
# 不是方法了, 想调用已经不需要加()号了,直接d.eat就可以了

 


1.12.3 魔法方法

1.12.3.1 type生成类调用顺序

new : 先于init方法,每生成一个实例执行一次,new 类方法创建实例对象

init : init方法每生成一个实例就会执行一次,初始化实例对象

call : 后与init方法,C()() 使用类再加一个括号调用, C为类名称

del : 析构方法,删除无用的内存对象(当程序结束会自动自行析构方法)

1.12.3.2 类实例化时魔法方法调用顺序

class Student(object):
    def __new__(cls, *args, **kwargs):
        print('__new__')
        return object.__new__(cls)   # 必须返回父类的__new__方法,否则不不执行__init__方法,无法创建实例
def __init__(self,name):
        print('__init__')
        self.name = name
​
    def __str__(self):                # 作用:打印实例时显示指定字符串,而不是内存地址
        print('__str__')
        return self.name
​
    def __call__(self, *args, **kwargs):        # 当执行C()(*args) 或者 s1(*args) 就会执行__call__
        print('__call__',*args)
​
    def __del__(self):                # 作用:清除无用的实例对内存的暂用
        print('__del__')



#1、实例化时机会执行__new__、__init__
s1 = Student('tom')

#2、执行 实例() 就会执行__call__ 方法,并将参数传递给__call__函数
s1('call01')

#3、当打印实例时就会执行 __str__ 方法下返回的字符串(默认返回的实例地址)
print(s1)

#4、析构方法:当删除实例时就会调用 __del__ 方法
del s1
# 析构方法作用:在程序结束后会自动执行析构方法删除所有实例
# 但是在程序运行时有很多实例是无用的,但是python内存回收机制却不会自动删除他们,这样就浪费内存
# 我们可以执行 del s1 ,那么在程序运行时,python内存回收机制会检测到这些实例时无用的,才会删除
# 其实我们执行del s1,并没有回收内存,只不过是摘除门牌号,python内存回收机制发现没有门牌号后会自动回收内存

 

1.12.4 反射: hasattr、getattr、setattr 和 delattr

1.12.4.1hasattr(ogj,name_str) 判断一个对象里是否有对应的字符串方法

class Dog(object):
    def eat(self,food):
        print("eat method!!!")
d = Dog()
​
#hasattr判断对象d是否有eat方法,有返回True,没有返回False
print(hasattr(d,'eat'))     #True
print(hasattr(d,'cat'))     #False

 


1.12.4.2
getattr(obj,name_str) 根据字符串去获取obj对象里的对应的方法的内存地址
class Dog(object):
    def eat(self):
        print("eat method!!!")
d = Dog()
​
if hasattr(d,'eat'):          # hasattr判断实例是否有eat方法
    func = getattr(d, 'eat')  # getattr获取实例d的eat方法内存地址
    func()                    # 执行实例d的eat方法
#运行结果:  eat method!!!

 


1.12.4.3 使用stattr给类实例对象动态添加一个新的方法

1.12.4.4 delattr删除实例属性

1.13 深浅拷贝

1.13.1 预备知识一——python的变量及其存储

  1. python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的只本身

  2. 不管多么复杂的数据结构,浅拷贝都只会copy一层。

理解:两个人公用一张桌子,只要桌子不变,桌子上的菜发生了变化两个人是共同感受的。

1.13.2 浅copy与deepcopy

  1. 浅copy: 不管多么复杂的数据结构,浅拷贝都只会copy一层

  2. deepcopy : 深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,我们对这两个变量中任意一个修改都不会影响其他变量


import copy
​
sourceList = [1,2,3,[4,5,6]]
copyList = copy.copy(sourceList)
deepcopyList = copy.deepcopy(sourceList)
​
sourceList[3][0]=100print(sourceList)           # [1, 2, 3, [100, 5, 6]]
print(copyList)             # [1, 2, 3, [100, 5, 6]]
print(deepcopyList)         # [1, 2, 3, [4, 5, 6]]

 

1.14 python垃圾回收机制

1.14.1 引用计数

1)当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1.

2)当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉。

1.14.2 标记-清除

1)它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。

2)对象之间通过引用(指针)连在一起,构成一个有向图

3)从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象,根对象就是全局变量、调用栈、寄存器。

注:像是PyIntObject、PyStringObject这些不可变对象是不可能产生循环引用的,因为它们内部不可能持有其它对象的引用。

 

  1. 在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3,程序无法访问块4和5

  2. 第一步将标记块1,并记住块2和3以供稍后处理。

  3. 第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记。

  4. 扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。

1.14.3 分代回收

  1. 分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。

  2. Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)

  3. 他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。

  4. 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发

  5. 把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推

  6. 老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

1.15 上下文管理

1.15.1 什么是with语句

  1. with是一种上下文管理协议,目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉,简化try….except….finlally的处理流程。

  2. 所以使用with处理的对象必须有enter()和exit()这两个方法

  1)with通过enter方法初始化(enter方法在语句体执行之前进入运行)

  2)然后在exit中做善后以及处理异常(exit()方法在语句体执行完毕退出后运行)

1.15.2 with 语句使用场景

  1. with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源

  2. 比如文件使用后自动关闭、线程中锁的自动获取和释放等。

1.15.3 with处理文件操作的实例


with open('/etc/passwd') as f:
   for line in f:
       print(line)
# 这段代码的作用:打开一个文件,如果一切正常,把文件对象赋值给f,然后用迭代器遍历文件中每一行,当完成时,关闭文件;
# 而无论在这段代码的任何地方,如果发生异常,此时文件仍会被关闭。

1.16 高阶函数

1.16.1 lambda基本使用

  1. lambda只是一个表达式,函数体比def简单很多。

  2. lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。

  3. lambda表达式是起到一个函数速写的作用。允许在代码内嵌入一个函数的定义。

  4. 格式:lambda的一般形式是关键字lambda后面跟一个或多个参数,紧跟一个冒号,之后是一个表达式。


f = lambda x,y,z:x+y+z
print(f(1,2,3))                    # 6

my_lambda = lambda arg : arg + 1
print(my_lambda(10))                # 11

1.16.2 三元运算

  1. 三元运算格式: result=值1 if x<y else 值2 if条件成立result=1,否则result=2

  2. 作用:三元运算,又称三目运算,主要作用是减少代码量,是对简单的条件语句的缩写


name = 'Tom' if 1 == 1 else 'fly'

print(name)
# 运行结果: Tom

f = lambda x:x if x % 2 != 0 else x + 100
print(f(10))  

1.16.3 filter()函数可以对序列做过滤处理


# 利用 filter、lambda表达式 获取l1中元素小于33的所有元素 l1 = [11, 22, 33, 44, 55]

l1= [11,22,33,44,55]
a = filter(lambda x: x<33, l1)
print(list(a))

1.16.4 Map是对序列根据设定条件进行操作后返回他设置的是操作方法

利用map,lambda表达式将所有偶数元素加100


l1= [11,22,33,44,55]
ret = map(lambda x:x if x % 2 != 0 else x + 100,l1)
print(list(ret))
# 运行结果: [11, 122, 33, 144, 55]

1.16.5 reduce 函数

使用reduce进行求和运算

  1. reduce()函数即为化简函数,它的执行过程为:每一次迭代,都将上一次的迭代结果与下一个元素一同传入二元func函数中去执行。

  2. 在reduce()函数中,init是可选的,如果指定,则作为第一次迭代的第一个元素使用,如果没有指定,就取seq中的第一个元素。


from functools import reduce
def f(x, y):
return x + y

print(reduce(f, [1, 3, 5, 7, 9]))  # 25
# 1、先计算头两个元素:f(1, 3),结果为4;
# 2、再把结果和第3个元素计算:f(4, 5),结果为9;
# 3、再把结果和第4个元素计算:f(9, 7),结果为16;
# 4、再把结果和第5个元素计算:f(16, 9),结果为25;
# 5、由于没有更多的元素了,计算结束,返回结果25。

print( reduce(lambda x, y: x + y, [1, 3, 5, 7, 9]) )  # 25

1.16.5 sorted 函数

sorted对字典排序


d = {'k1':1, 'k3': 3, 'k2':2}
# d.items() = [('k1', 1), ('k3', 3), ('k2', 2)]
a = sorted(d.items(), key=lambda x: x[1])
print(a)            # [('k1', 1), ('k2', 2), ('k3', 3)]

 

 

02. MySQL

2.1 Mysql 事务

1. InnoDB 事务原理

  1. 事务(Transaction) 是数据库区别于文件系统的重要特征之一,事务辉把数据库从一种一致性状态转换为另一种一致性状态。

  2. 在数据库提交时,可以确保要么所有修改都已保存,要么所有修改都不保存。

2. 事务的(ACID)特征

  1. 原子性(Atomicity): 整个事务的所有操作要么全部提交成功,要么全部失败回滚(不会出现部分执行的情况)。

  2. 一致性(Consistency): 几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致。

  3. 隔离性(Isolation): 事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须时透明的。

  4. 持久性(Dueability): 一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

3. 事务隔离级别

  1. 未提交读:脏读(READ UNCOMMITTED)

    1. 事务2查询到的数据是事务1中修改但未提交的数据,但因为事务1回滚了数据

    2. 所有事务2查询的数据是不正确的,因此出现了脏读的问题。

  2. 提交读:不可重复读(READ COMMITTED)

    1. 事务2执行update语句但未提交前,事务1的前两个select操作返回结果是相同的。

    2. 但事务2执行commit操作后,事务1的第三个select操作就读取到事务2读数据的改变。

    3. 导致与前两次select操作返回不同的数据,因此出现了不可重复读的问题。

  3. 可重复读:幻读(REPEATABLE READ): 这是MySQL的默认事务隔离级别

    1. 事务每开启一个实例,都会分配一个版本号给它,如果读取的数据行正在被其他事务执行DELETE或UPDATE操作(即该行上有排它锁)

    2. 这时该事务的读取操作不会等待行上的锁释放,而是根据版本号去读取行的快照数据(记录在undolog中)

    3. 这样,事务中的查询操作返回的都是同一版本下的数据,解决了不可重复读问题。

    4. 虽然该隔离级别下解决了不可重复读问题,但理论上会导致另一个问题:幻读(Phantom Read).

    5. 一个事务在执行过程中,另一个事务对已有数据行的更改,MVCC机制可保障该事务读取到的原有数据行的内容相同。

    6. 但并不能阻止另一个事务插入新的数据行,这就会导致该事务中凭空多出数据行,像出现了幻读一样,这便是幻读问题。

  4. 可串行读(SERIALIZABLE)

    1. 这个事务的最高隔离级别,通过强制事务排序,使之不可能互相冲突,就是在每个读的数据行加上共享锁来实现

    2. 在该隔离级别下,可以解决前面出现的脏读、不可重复读和幻读问题,但也会导致大量的超时和锁竞争现象,一般不推荐使用

2.2 MySQL 中的锁

1.锁分类:

  1. 按操作划分:DML锁,DDL锁

  2. 按锁的粒度划分:表级锁、行级锁、页级锁

  3. 按锁的级别划分:共享锁、排它锁

  4. 按加锁方式划分:自动锁、显示锁

  5. 按使用方式划分:乐观锁、悲观锁

2.乐观锁实现方法:

  1. 每次获取商品时,不对该商品加锁。

  2. 在跟新数据的时候需要比较程序中的库存量与数据库中的库存量是否相等,如果相等则进行跟新。

  3. 反之程序重新获取库存量,再次进行比较,直到两个库存量的数值相等才进行数据更新。


#### 乐观锁实现加一操作代码
# 我们可以看到,只有当对数量-1操作时才会加锁,只有当程序中值和数据库中的值相等时才正真执行。

'''
//不加锁
select id,name,stock where id=1;
//业务处理
begin;
update shop set stock=stock-1 where id=1 and stock=stock;
commit;
'''

3.悲观锁

  1. 每次获取商品时,对该商品加排它锁。

  2. 也就是在用户A获取 获取id=1 的商品信息时对该行记录加锁,期间其他用户阻塞等待访问该记录。


#### 悲观锁实现加一操作代码
# 我们可以看到,首先通过begin开启一个事物,在获得shop信息和修改数据的整个过程中都对数据加锁,保证了数据的一致性。

'''
begin;
select id,name,stock as old_stock from shop where id=1 for update;
update shop set stock=stock-1 where id=1 and stock=old_stock;
commit
'''

4. 排它锁:

  1. 排它锁又叫写锁,如果事务T对A加上排它锁,则其它事务都不能对A加任何类型的锁。获准排它锁的事务既能读数据,又能写数据。

  2. 用法:SELECT ... FOR UPDATE

5. 共享锁(share locke)

  1. 共享锁又叫读锁,如果事务T对A加上共享锁,则其它事务只能对A再加共享锁,不能加其他锁。

  2. 获准共享锁的事务只能读数据,不能写数据。

  3. 用法:SELECT ... LOCKIN SHARE MODE

2.3 MySQL 优化

  1. 字段设计优化 适应遵循数据库三范式

  2. 引擎的选择 适应选择 MyIsam & InnoDB

  3. 索引 索引也会消耗内存空间,并不是越多越好。而且索引的种类都有各自的优点

  4. 查询缓存 将select 查询结果缓存起来,key为SQL语句,value 为查询结果

  5. 分区

  6. 水平线分割和垂直分割

  7. 集群

  8. SQL 语句

  9. 服务器的选择 选择配置较高的服务器

为什么要MySQL 优化?

  • 系统的吞吐量瓶颈往往出现在数据库的访问速度上

  • 随着应用程序的运行,数据库中的数据会越来越多,处理时间会相应变慢

  • 为了避免查询的出现

2.4 B-tree/B+tree

1. 以一个3阶的 B-Tree 举例

  1. 每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的时子节点所在磁盘块的地址。

  2. 两个关键词划分成的三个范围域对应三个指针向的子树的数据的范围域。

  3. 和以根节点为例,关键字为17和35,P1指针指向的子树的数据范围小于17,P2 指针指向的子树的范围为17 - 35, P3 指针指向的子树的数据范围为大于35.


'''模拟查找关键字29的过程:'''

# 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
# 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
# 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
# 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
# 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
# 在磁盘块8中的关键字列表中找到关键字29。

 

2. B+tree (以每个节点可存4个键值及指针信息为例)

  1. B+Tree 的 非叶子节点致存储键值信息,假设每个磁盘块能存储4个键值及指针信息

  2. 在 B+Tree 上有两个头指针,一个执行根节点,另一个指向关键子最小的叶子节点,而且所有叶子节点(及数据节点)之间时一种链式环结构。

  3. 因此可以对B+Tree 进行两种查找运算: 一种时对于主键的范围查找和分页查找,另一种时从根节点开始,进行随机查找。

 

2.5 MySQL 主从同步原理

  1. master 服务器将数据的改变记录二进制 binlog 日志, 当 master 上的数据发生改变时,则将其改变写入二进制日志中;

  2. slave 服务器 会在一定时间间隔内对 master 二进制日志进行探测其是否改变,如果发送改变,则开始一个 I/OThread 请求 master 二进制事件

  3. 同时主节点为每个I/O线程启动一个dump线程,用于向发送二进制事件,并保存致至从节点本地的中继日志中,从节点将启动SQL线程再从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后 I/O ThreadSQL Thread 将进入催眠状态,等待下一次被唤醒。

  4.  

 

2.6 MySQL- 慢查询应用

1.答案

  1. 执行一条查询语句花费的时间超过数据库中设定的时间

  2. 只有超过执行的语句才会写入到慢查询日志中

2. 参考答案

​ MySQL 的慢查询,全名是慢查询日志,是MySQL提供的一种日志记录,用来记录在MySQL中响应实践超过阀值的语句。具体环境中,运行事件超过long_query_time 值的 SQL 语句, 则会被记录的慢查询日志中。long_query_time 的默认值为10, 意思是记录运行10秒以上的语句。默认情况下,MySQL数据库并不启动慢查询日志,需要手动来设置这个参数。

​ 当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。

​ 此外,慢查询日志支持将日志记录写入文件和数据库表.

3. MySQL 慢查询的相关参数解释:

  • slow_query_long: 是否开启慢查询日志,1表示开启,0表示关闭。

  • log-slow-queries: 旧版(5.6以下版本) MySQL 数据库慢查询日志存储路径。可以不设置该参数,系统则会默认给你一个缺省的文件host_name-slow.log

  • show-query-log-file: 新版(5.6及以上版本) MySQL 数据库慢查询日志存储。可以不设置该参数,系统则会默认给一个缺省的文件host_name-slow.log

  • long_query_tiem: 慢查询阈值,当查询时间多于设定的阈值时,记录日志。

  • log_queries_not_using_indexes: 未使用索引的查询也被记录到慢查询日志中(可选项)。

  • log_output: 日志存储方式。log_output="FILE" 表示将日志存入文件,默认值是"FILE"。 log_output="TABLE"表示将日志存入数据库。

4. 一些其他慢查询配置选项如下。

  • log-queries-not-using-indexes: 该系统变量指定未使用索引的查询也被记录到慢查询日志中,调优的话,建议开启这个选项。其实使用 full index scan 的 SQL 也会被记录到慢查询日志。

  • log_slow_admin_statements: 这个系统变量表示,是否将慢管理语句例如ANAL YZETABLE 和 ALTER TABLE 等记入慢查询日志 slow_queries 如果你想查询有多少条慢查询记录,可以使用slow_queries 系统变量。

2.7 MySQL-主从一致校验

2.8 MySQL-基于 Docker的主从复制

03. Redis

 

3.1 redis 的五大数据类型实现原理

  • 说明:

    • redis 中所有数据结构都以唯一的key字符串作为名称,然后通过这个唯一的key来获取对应的value

    • 不同的数据类型数据结构差异就在于value的结构不一样

  1. 字符串(string)

  2. 列表(list)

  3. 字典(hash)

  4. 集合

  5. 有序集合

3.2 redis 事务

  1. redis 事务是可以一次执行多个命令,本质是一组命令的集合。

  2. 一个事务中的所有命令都会序列化,按顺序串行化的执行而不会被其他命令插入

  3. 作用:一个队列中,一次性、顺序性、排他性的执行一系列命令

    1. multi(开始一个redis事物)

    2. incr books

      incr books

    3. exec (执行事物)

    4. discard (丢弃事物)

       

3.3 redis 分布式锁

  1. 分布式锁本质是占一个坑,当别的进程也要来占坑时发现已经被占,就会放弃或者稍后重试

  2. 占坑一般使用setnx(set if not exists) 指令,只允许一个客户端占坑

  3. 先来先占,用完了再调用del指令释放坑

  4. 但是这样有一个问题,如果逻辑执行到中间出现异常,可能胡导致del指令没有被调用,这样就会陷入死锁,锁永远无法释放

  5. 为了解决死锁问题,我们拿到锁时可以加上一个expire过期时间,这样即使出现异常,当到达过期时间也会自动释放锁

  6. 这样又有一个问题,setnx 和 expire 是两条指令而不是原子指令,如果两条指令之间进程挂掉依然会出现死锁

  7. 为了治理上面乱象,在redis2.8中加入了set指令的扩展参数,是setnx和expire指令可以一起执行

 

3.4 布隆过滤器

  1. 判断某一个key一定不存在,不能判断一个key一定存在。

  2. 只有有可能存在的key,才会真实的到redis中查找(带有不确定性)

  3. 原理

    1. 使用多个hash函数对同一个key进行hash,结果为整数

    2. 将hash的结果,对存储列表取余,在对应位置上写1

    3. 在查询redis前通过hash函数进行取值,如果一个hash对应位置不为1,就证明这个key一定不存在,不用查询redis

 

4.5 redis 雪崩&穿透&击穿

  1. 缓存穿透

    1. 缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,接着查询数据库也无法查询出结果。

    2. 虽然也不会写入到缓存中,但是这将会导致每个查询都会去请求数据库,造成缓存穿透;

    3. 解决方法就是通过布隆过滤器对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

  2. 缓存雪崩

    1. 缓存雪崩是指,由于缓存层承载大量请求,有效的保护了储存层,但是如果缓存层由于某些原因整体不能提供服务(比如缓存某一时刻可接受的请求是4000次,而如果黑客通过肉鸡攻击你的缓冲层6000次,这样缓冲层便支撑不了压力,导致宕机)

    2. 这样所有的请求都会达到储存层,储存层的调用量会暴增,造成储存层也会挂掉的情况

    3. 保证缓存层服务高可用性: 比如Redis Sentinel 和 Redis Cluster 都实现了高可用

    4. 依赖隔离组件为后端限流并降级: 比如对某个key 只允许一个线程查询数据和写缓存,其他线程等待.

  3. 缓存击穿

    1. 缓存击穿,就是说某个key非常热点,访问非常频繁,处于集中式高并发访问的情况

    2. 当这个key在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开一个洞。

    3. 解决方法就是将数据设置为永远不过期

    4. 或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该key访问数据.

     

3.6 主从同步

  1. 主从刚刚连接的时候,进行全量同步;全量同步结束后,进行增量同步。

  2. 当然,如果有需要,slave 再任何时候都可以发起全量同步。

  3. redis 策略是,无论如何,首先尝试进行增量同步,如不成功,要求从机制全量同步。

3.7 哨兵模式 ---- sentinel

  1. sentinel 作用:

    1. 所有redis中的数据都一样,通过解决请求数据量很大的问题,但是依然不能存储更多数据

    2. 当用Redis 做主从方案时,假如master宕机, Redis 本事无法自动进行主备切换

    3. 而 Redis-sentinel 本身也是一个独立运行的进程,它能监控多个master-slave 集群,发现 master 宕机后才能进行自动切换

    4. Sentinel 负责持续监控主节点的健康,当主节点挂掉时,自动选择一个最优的节点切换成主节点

    5. 从节点来连接集群时会首先连接sentinel,通过sentinel 来查询主节点的地址

    6. 从主节点发生故障时,sentinel 会将最新的主节点地址告诉客户端,可以实现无需启动自动切换redis

3.8 codis 集群

  1. codis 是 redis 集群解决方案之一,codis是GO语言开发的代理中间件

  2. 当客户端向codis发送指令时,codis负责将指令转发给后面的redis 实例来执行,并将返回结果转发给客户端

  3. 单个codis代理支撑的QPS比较有限,通过启动多个codis代理可以显著增加整体QPS

  4. 多codis还能起到容灾功能,挂掉一个codis代理还有很多codis代理可以继续服务

04. 数据结构

4.1 常见的数据结构

    1. 栈的定义: 栈式一种数据集合,可以理解为只能在一端进行插入或者删除操作的列表

    2. 栈的特点:后进后出

  1. 队列定义

    1. 队列是一种数据集合,仅允许在列表的一端进行插入,另一端进行删除

    2. 队列性质:先进先出

    3. 双向队列:队列的两端都允许进行进队和出队操作

  2. 链表

    1. 链表中每个元素都是一个对象,每个对象称为一个节点,包含有数据域key和指向下一节点的指针next,通过各个节点间的相互连接,最终串联成一个链表

  3. 数组

    1. 所谓数组,就是相同数据类型的元素按一定顺序排列的集合

    2. 在Java等其他语言中并不是所有的数据都能存储到数组中,只有相同类型的数据才可以一起存储到数组中。

    3. 因为数组在存储数据时是按照顺序存储的,存储数据的内存也是连续的,所以它的特点就是寻址读取数据比较容易,插入和删除比较困难

  4. 字典对象实现原理

    1. 哈希表(hash tables) key-value 一个键对应一个值

     

4.2 二分法查找


l = list(range(1,101))
def bin_search(data_set,val):
   low = 0
   high = len(data_set) - 1
   while low <= high:
      mid = (low+high)//2
      if data_set[mid] == val:
         return mid
      elif data_set[mid] < val:
         low = mid + 1
      else:
         high = mid - 1
   return
n = bin_search(l,11)
print(n)            # 返回结果是: 10

 

4.3 冒泡法排序

原理: 拿自己与上面一个比较,如果上面一个比自己小就将自己和上面一个调换位置,依次再与上面一个比较,第一轮结束后最上面那个一定是最大的数(在for循环的内层循环减一去掉每次比较出来的最大值实现算法优化)


#! /usr/bin/env pythonf
# -*- coding: utf-8 -*-
def bubble_sort(li):
    for i in range(len(li)-1):
        for j in range(len(li)-i-1):
            if li[j] > li[j+1]:
                li[j],li[j+1]=li[j+1],li[j]
​
li = [1,5,2,6,3,7,4,8,9,0]
bubble_sort(li)
print(li)               # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

 

4.4 快排

快排思路: 去一个元素p(第一个元素),以元素P为中心列表分成两部分,比P小的放左边,比P大的放右边,递归完成排序。


#! /usr/bin/env python
# -*- coding: utf-8 -*-
def quick(list):
    if len(list) < 2:
        return list
​
    tmp = list[0]  # 临时变量 可以取随机值
    left = [x for x in list[1:] if x <= tmp]  # 左列表
    right = [x for x in list[1:] if x > tmp]  # 右列表
    return quick(left) + [tmp] + quick(right)
​
li = [4,3,7,5,8,2]
print quick(li)  # [2, 3, 4, 5, 7, 8]
#### 对[4,3,7,5,8,2]排序
'''
[3, 2] + [4] + [7, 5, 8]                 # tmp = [4]
[2] + [3] + [4] + [7, 5, 8]              # tmp = [3] 此时对[3, 2]这个列表进行排序
[2] + [3] + [4] + [5] + [7] + [8]        # tmp = [7] 此时对[7, 5, 8]这个列表进行排序
'''

 

4.5 递归斐波那契


def fun(i):
    if i == 0:
        return 0
    elif i == 1:
        return 1
    else:
        return fun(i-2) + fun(i-1)
if __name__ == '__main__':
    for i in range(10):
        print(fun(i),end=" ")

 

4.6 青蛙跳台阶


fib = lambda n: n if n < 2 else 2 * fib(n - 1)

5. Linux

5.1 Linux-supervisor

Supervisor 是用Python 开发的一个 client/server服务,是Linux/Unix 系统下的一个进程管理工具,不支持Windows系统。它可以很方便的监听、启动、停止、重启一个或多个进程。用Supervisor管理的进程,当一个进程被意外杀死,supervisor监听到进程死后,就会将它重新拉起,很方便的做到进程自动恢复功能,不再需要自己写shell 脚本来控制。

5.2 linux常用命令

1、系统命令

runlevel # 查看当前的运行级别

systemctl status firewalld # 开启网络服务功能

​ stop # 关闭

​ restart # 重启

​ reload # 重载

reboot # 重启

halt # 关机

poweroff # 关机

2、查看文件常用指令

cat # 在命令提示符下查看文件内容

more # 在命令提示符中分页查看文件内容

less # 命令行中查看文件可以上下翻页反复浏览

head # 命令行中查看文件头几行

tail # 命令行中查看文件尾几行

wc # 统计文件的单词数 行数等信息

3、目录管理常用指令

pwd # 查看你当前所在的目录

cd # 切换目录

ls # 查看显示目录的内容

du # 统计目录和文件空间的占用情况

mkdir # 创建新目录

rmdir # 删除空目录

touch # 创建文件

rm # 删除文件

ln # 创建硬链接

ln -s # 创建软链接

cp # 复制文件或目录

mv # 移动文件或目录

which # 查看linux命令所在的目录

 

4、账号与权限

'''1.组管理'''

groupadd group_name # 创建一个新用户组

groupdel group_name # 删除一个用户组

groupmod -n new_group_name old_group_name # 重命名一个用户组

'''2.用户管理'''

useradd zhangsan # 创建账户张三

passwd zhangsan # 给用户设置密码

userdel -r zhangsan # 删除张三及他的宿主目录

'''3.用户组管理'''

gpasswd -a zhangsan root # 将张三用户加入root组

groups zhangsan # 确认zhangsan用户在root组

gpasswd -d lisi root # 将李zhangsan户从root组中删除

'''4.权限管理'''

chown -R zhangsan /aaa # 将文件夹/aaa的多有者修改为zhangsan

chown root:root /aaa # 将/aaa文件夹的属主和属组都改成root

chmod 777 /aaa # 给文件夹文件/aaa设置权限为777

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



#1、实例化时机会执行__new__、__init__
s1 = Student('tom')

#2、执行 实例() 就会执行__call__ 方法,并将参数传递给__call__函数
s1('call01')

#3、当打印实例时就会执行 __str__ 方法下返回的字符串(默认返回的实例地址)
print(s1)

#4、析构方法:当删除实例时就会调用 __del__ 方法
del s1
# 析构方法作用:在程序结束后会自动执行析构方法删除所有实例
# 但是在程序运行时有很多实例是无用的,但是python内存回收机制却不会自动删除他们,这样就浪费内存
# 我们可以执行 del s1 ,那么在程序运行时,python内存回收机制会检测到这些实例时无用的,才会删除
# 其实我们执行del s1,并没有回收内存,只不过是摘除门牌号,python内存回收机制发现没有门牌号后会自动回收内存

1.

posted @ 2020-11-12 20:26  高登汗  阅读(296)  评论(0编辑  收藏  举报