開發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.5.2 程序定義拓展回答內容
-
程式並不能單獨執行,只有將程式裝載到內容中,系統為它分配資源才能執行,這種執行的程式就稱之為程序
-
程式和程序的區別就在於:程式是指令的集合,他是程序執行的靜態描述文字;程序是程式的一次執行活動,屬於動態概念
-
在多道程式設計中,我們允許多個程式同時載入到記憶體中,在作業系統的排程下,可以實現併發的執行
-
程序的出現讓每個使用者感覺到自己獨享CPU,因此,程序就是為了在CPU上實現多道程式設計而提出的
-
程序之間有自己獨立的記憶體,各種程序之間不能相互訪問
-
建立一個新執行緒很簡單,建立新的程序需要對父程序進行復制
1.5.3 程序間互相訪問資料的四種方法
注:不同程序間記憶體是不共享的,所以相互之間不能訪問對方資料
-
利用 Queues 實現父程序到子程序(或子程序間) 的資料傳遞
-
使用管道 pipe 實現兩個程序間資料傳遞
-
Managers 實現很多程序間資料共享
-
藉助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+100 def 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.6 執行緒
1.6.1 執行緒定義
-
執行緒是作業系統排程的最小單位
-
它被包含在程序之中,是程序中的實際運作單位
-
程序本事是無法自己執行的,要操作cpu,必須建立一個執行緒,執行緒是一系列指令的集合
1.6.1.1 執行緒定義拓展回答內容
-
執行緒是作業系統能夠進行運算排程的最小的。它被包含在程序之中,是程序中的實際運作單位
-
一條執行緒指的是程序中一個單位順序的控制流,一個程序中可以多個執行緒,每條執行緒並行執行不同的任務
-
無論你啟動多少個執行緒,你有多少個cpu,Python在執行的時候就會淡定的同一時刻只允許一條執行緒執行
-
程序本身是無法自覺執行的,要操作cpu,必須建立一個執行緒,執行緒是一系列指令的集合
-
所有在同一個程序裡的執行緒是共享一塊記憶體空間的,不同程序間記憶體空間不同
-
同一個程序中的各執行緒可以相互訪問資源,執行緒可以操作同進程中的其他執行緒,但程序僅能操作與程序
-
兩個程序想通訊,必須要通過一個代理
-
對主執行緒的修改可能會影響其他子執行緒,對程序修改不會影響其他程序因為程序間記憶體相互獨立,但是同一程序下的執行緒共享記憶體、
1.6.2 程序和執行緒的區別
-
程序包含執行緒
-
程序共享記憶體空間
-
程序記憶體是獨立的(不可互相訪問)
-
程序可以生成於執行緒,子程序之間相互不能互相訪問(相當於在父級程序克隆兩個程序)
-
在一個程序裡面執行緒之間可以交流。兩個程序想要通訊,必須通過一箇中間代理來實現
-
建立新執行緒很簡單,建立新程序需要對其父程序進行克隆。
-
一個執行緒可以控制或操作同一個程序裡面的其它執行緒。但程序只能操作子程序。
-
父程序可以修改不影響子程序,但不能修改子程序
-
執行緒可以幫助應用程式同時做多件事
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操作時(如:計算)才是序列的,其他都是並行的,所以比序列快很多
-
為了解決不同執行緒同時訪問同一資源時,資料保護問題,而產生了GIL
-
GIL在直譯器的層面限制了程式在同一時間只有一個執行緒被CPU實際執行,而不管你的程式裡實際開了多少條執行緒
-
為了解決這個問題,CPython自己定義了一個全域性直譯器鎖,同一時間僅僅有一個執行緒可以拿到這個資料
-
python之所以會產生這種不好的狀況是因為python啟用一個執行緒是呼叫作業系統原生執行緒,就是C介面
-
但是這僅僅是CPython這個版本的問題,在PyPy,中就沒有這種缺陷
1.6.7 執行緒鎖
-
當一個執行緒對某個資源進行CPU計算的操作時加一個執行緒鎖,只有當前執行緒計算完成主動釋放鎖,其他執行緒才能對其操作
-
這樣就可以防止還未計算完成,釋放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 (訊號量)
-
互斥鎖 同時只允許一個執行緒更改資料,而Semaphore是同時允許一定數量的執行緒更改資料
-
Semaphore 的作用就好比銀行只有一個視窗可以等待,在Semaphore的作用下有多個視窗可以完成任務和供人等待的。
-
作用就是同一時刻允許執行的執行緒數量
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 什麼是協程(進入上一次呼叫的狀態)
-
協程,又稱微執行緒,纖程,協程是一種使用者態的輕量級執行緒。
-
執行緒的切換會儲存到CPU的棧裡,協程擁有自己的暫存器上下文和棧,
-
協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧
-
協程能保留上一次呼叫時的狀態(即所有區域性狀態的一個特定組合),每次過程重入時,就相當於進入上一次呼叫的狀態
-
協程最主要的作用是在單執行緒的條件下實現併發的效果,但實際上還是序列的(像yield一樣)
1.7.2. 協程缺點(無法利用多核資源)
協程的本質是個單執行緒,它不能同時將 單個CPU 的多個核用上,協程需要和程序配合才能執行在多CPU上,執行緒阻塞(Blocking)操作(如IO時)會阻塞掉整個程式
1.7.3 協程為何能處理大併發1:Greenlet遇到I/O手動切換
-
協程之所以快是因為遇到I/O操作就切換(最後只有CPU運算)
-
greenlet可以實現手動的對各個協程之間切換 。
-
其實Gevent模組僅僅是對greenlet的再封裝,將I/O間的手動切換變成自動切換
1.7.4 協程為何能處理大併發2:Gevent遇到I/O自動切換
-
Gevent 是一個第三方庫,可以輕鬆通過gevent實現併發同步或非同步程式設計
-
在gevent中用到的主要模式是Greenlet, 它是以C擴充套件模組形式接入Python的輕量級協程
-
Greenlet全部執行在主程式作業系統程序的內部,但它們被協作式地排程。
-
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(能監控數量有限,不能告訴使用者程式具體哪個連線有資料)
-
select目前幾乎在所有的平臺上支援,其良好跨平臺支援也是它的一個優點
-
select的一個缺點在於單個程序能夠監視的檔案描述符的數量存在最大限制,在Linux上一般為1024
-
select監控socket連線時不能準確告訴使用者是哪個,比如:現在用socket監控10000連結,如果其中有一個連結有資料了,select就會告訴使用者程式,你有socket來資料了,那樣就只能自己迴圈10000次判斷哪個活躍
1.8.2 poll(和select一樣,僅僅去除了最大監控數量)
-
poll和select在本質上沒有多大差別,但是poll沒有最大檔案描述符數量的限制
-
可以理解為poll是一個過渡階段,大家也都不用他
1.8.3 epoll (不僅沒有最大監控數量限制,還能告訴使用者程式哪個連線有活躍)
注:epoll被認為是linux下效能最好的多路io就緒通知方法
-
epoll直到Linux2.6(centos6以後)才出現了由核心直接支援
-
Epoll沒有最大檔案描述符數量限制
-
epoll最重要的優點是他可以直接告訴使用者程式哪一個,比如現在用epoll去監控10000個socket連結,交給核心去監測,現在有一個連線有資料了,在有有一個連線有資料了,epoll會直接高數使用者程式哪個連線有資料了
1.8.4 epoll能實現高併發原理
-
epoll() 中核心則維護一個連結串列,epoll_wait 直接檢查連結串列是不是空就知道是否有檔案描述符準備好了。
-
在核心實現中 epoll 是根據每個 sockfd 上面的與裝置驅動程式建立起來的回撥函式實現的。
-
某個 sockfd 上的事件發生時,與它對應的回撥函式就會被呼叫,把這個 sockfd 加入連結串列。
-
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 生成器的作用
-
通過列表生成式,我們可以直接建立一個列表,但是,受到記憶體限制,列表容量肯定是有限的。
-
而且,建立一個包含100萬個元素的列表,不僅佔用很大的儲存空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
-
所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的過程中不斷推算出後續的元素呢?
-
這樣就不必建立完整的list,從而節省大量的空間。在Python中,這種一邊迴圈一邊計算的機制,稱為生成器:generator。
1.10.3 生成器工作原理
-
生成器是這樣一個函式,它記住上一次返回時在函式體中的位置。
-
對生成器函式的第二次(或第 n 次)呼叫跳轉至該函式中間,而上次呼叫的所有區域性變數都保持不變。
-
生成器不僅“記住”了它資料狀態;生成器還“記住”了它在流控制構造中的位置。
-
生成器是一個函式,而且函式的引數都會保留。
-
迭代到下一次的呼叫時,所使用的引數都是第一次所保留下的,即是說,在整個所有函式呼叫的引數都是第一次所呼叫時保留的,而不是新建立的
1.10.4 yield 生成器執行機制
在Python中,yield就是這樣的一個生成器。
-
當你問生成器要一個數時,生成器會執行,直至出現 yield 語句,生成器把yield 的引數給你,之後生成器就不會往下繼續執行。
-
當你問他要下一個數時,他會從上次的狀態開始執行,直至出現yield語句,把引數給你,之後停下。如此反覆
-
在python中,當你定義一個函式,使用了yield關鍵字時,這個函式就是一個生成器
-
它的執行會和其他普通的函式有很多不同,函式返回的是一個物件,而不是你平常所用return語句那樣,能得到結果值。如果想取得值,那得呼叫next()函式
-
每當呼叫一次迭代器的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 迭代器定義
-
迭代器是訪問集合內元素的方式,迭代器物件從集合的第一個元素開始訪問,直到所有的元素都被訪問一遍後結束
-
迭代器僅是一容器物件,它有兩個基本方法
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.12.1.2 Inheritance 繼承(程式碼重用)
-
一個類可以派生出子類,在這個父類裡定義的屬性、方法自動被子類繼承
-
比如CS中的警察和恐怖分子,可以將兩個角色的相同點寫到一個父類中,然後同時去繼承它
-
使用經典類: Person.init(self,name,age) 並重寫寫父類Person的構造方法,實現,先覆蓋,再繼承,再重構
1.12.1.3 Polymorphism 多型(介面重用)
1.多型是面向物件的重要特性,簡單點說:“一個介面,多種實現”
-
指一個基類中派生出了不同的子類,且每個子類在繼承同樣的方法名的同時又對父類的方法做了不同的實現
-
這就是同一種事物表現出的多種形態
-
比如黃種人繼承了人talk這個功能,但是他說的是中文,而美國人的talk是英文,但是他們是同樣的talk
作用:簡單的講就是允許父類呼叫子類的方法
1.12.2 靜態方法、類方法、屬性方法
1.12.2.1 靜態方法
-
作用:靜態方法可以更好的組織程式碼,防止程式碼變大後變得比較混亂。
-
特性: 靜態方法只是名義上歸類管理,實際上在靜態方法裡訪問不了類或則例項中的任何屬性
-
靜態方法使用場景:
1)我們要寫一個只在類中執行而不在例項中執行的方法.
2)經常有一些跟類有關係的功能但在執行時又不需要例項和類參與的情況下需要用到靜態方法.
3)比如更改環境變數或者修改其他類的屬性等能用到靜態方法.
4)這種情況可以直接用函式解決, 但這樣同樣會擴散類內部的程式碼,造成維護困難.
-
呼叫方式: 既可以被類直接呼叫,也可以通過例項呼叫
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 類方法
-
作用:無需例項化直接被類呼叫。
-
特性: 類方法只能訪問類變數,不能訪問例項變數。
-
類方法使用場景: 當我們還未建立例項,但是需要呼叫類中的方法。
-
呼叫方式: 既可以被類直接呼叫,也可以通過例項呼叫。
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.2getattr(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的變數及其儲存
-
python的一切變數都是物件,變數的儲存,採用了引用語義的方式,儲存的只是一個變數的值所在的記憶體地址,而不是這個變數的只本身
-
不管多麼複雜的資料結構,淺拷貝都只會copy一層。
理解:兩個人公用一張桌子,只要桌子不變,桌子上的菜發生了變化兩個人是共同感受的。
1.13.2 淺copy與deepcopy
-
淺copy: 不管多麼複雜的資料結構,淺拷貝都只會copy一層
-
deepcopy : 深拷貝會完全複製原變數相關的所有資料,在記憶體中生成一套完全一樣的內容,我們對這兩個變數中任意一個修改都不會影響其他變數
import copy sourceList = [1,2,3,[4,5,6]] copyList = copy.copy(sourceList) deepcopyList = copy.deepcopy(sourceList) sourceList[3][0]=100 print(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,並且可以間接訪問塊2和3,程式無法訪問塊4和5
-
第一步將標記塊1,並記住塊2和3以供稍後處理。
-
第二步將標記塊2,第三步將標記塊3,但不記得塊2,因為它已被標記。
-
掃描階段將忽略塊1,2和3,因為它們已被標記,但會回收塊4和5。
1.14.3 分代回收
-
分代回收是建立在標記清除技術基礎之上的,是一種以空間換時間的操作方式。
-
Python將記憶體分為了3“代”,分別為年輕代(第0代)、中年代(第1代)、老年代(第2代)
-
他們對應的是3個連結串列,它們的垃圾收集頻率與物件的存活時間的增大而減小。
-
新建立的物件都會分配在年輕代,年輕代連結串列的總數達到上限時,Python垃圾收集機制就會被觸發
-
把那些可以被回收的物件回收掉,而那些不會回收的物件就會被移到中年代去,依此類推
-
老年代中的物件是存活時間最久的物件,甚至是存活於整個系統的生命週期內。
1.15 上下文管理
1.15.1 什麼是with語句
-
with是一種上下文管理協議,目的在於從流程圖中把 try,except 和finally 關鍵字和資源分配釋放相關程式碼統統去掉,簡化try….except….finlally的處理流程。
-
所以使用with處理的物件必須有enter()和exit()這兩個方法
1)with通過enter方法初始化(enter方法在語句體執行之前進入執行)
2)然後在exit中做善後以及處理異常(exit()方法在語句體執行完畢退出後執行)
1.15.2 with 語句使用場景
-
with 語句適用於對資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的“清理”操作,釋放資源
-
比如檔案使用後自動關閉、執行緒中鎖的自動獲取和釋放等。
1.15.3 with處理檔案操作的例項
with open('/etc/passwd') as f:
for line in f:
print(line)
# 這段程式碼的作用:開啟一個檔案,如果一切正常,把檔案物件賦值給f,然後用迭代器遍歷檔案中每一行,當完成時,關閉檔案;
# 而無論在這段程式碼的任何地方,如果發生異常,此時檔案仍會被關閉。
1.16 高階函式
1.16.1 lambda基本使用
-
lambda只是一個表示式,函式體比def簡單很多。
-
lambda的主體是一個表示式,而不是一個程式碼塊。僅僅能在lambda表示式中封裝有限的邏輯進去。
-
lambda表示式是起到一個函式速寫的作用。允許在程式碼內嵌入一個函式的定義。
-
格式: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 三元運算
-
三元運算格式: result=值1 if x<y else 值2 if條件成立result=1,否則result=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進行求和運算
-
reduce()函式即為化簡函式,它的執行過程為:每一次迭代,都將上一次的迭代結果與下一個元素一同傳入二元func函式中去執行。
-
在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 事務原理
-
事務(Transaction) 是資料庫區別於檔案系統的重要特徵之一,事務輝把資料庫從一種一致性狀態轉換為另一種一致性狀態。
-
在資料庫提交時,可以確保要麼所有修改都已儲存,要麼所有修改都不儲存。
2. 事務的(ACID)特徵
-
原子性(Atomicity): 整個事務的所有操作要麼全部提交成功,要麼全部失敗回滾(不會出現部分執行的情況)。
-
一致性(Consistency): 幾個並行執行的事務,其執行結果必須與按某一順序序列執行的結果相一致。
-
隔離性(Isolation): 事務的執行不受其他事務的干擾,事務執行的中間結果對其他事務必須時透明的。
-
永續性(Dueability): 一個事務一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。
3. 事務隔離級別
-
未提交讀:髒讀(READ UNCOMMITTED)
-
事務2查詢到的資料是事務1中修改但未提交的資料,但因為事務1回滾了資料
-
所有事務2查詢的資料是不正確的,因此出現了髒讀的問題。
-
-
提交讀:不可重複讀(READ COMMITTED)
-
事務2執行update語句但未提交前,事務1的前兩個select操作返回結果是相同的。
-
但事務2執行commit操作後,事務1的第三個select操作就讀取到事務2讀資料的改變。
-
導致與前兩次select操作返回不同的資料,因此出現了不可重複讀的問題。
-
-
可重複讀:幻讀(REPEATABLE READ): 這是MySQL的預設事務隔離級別
-
事務每開啟一個例項,都會分配一個版本號給它,如果讀取的資料行正在被其他事務執行DELETE或UPDATE操作(即該行上有排它鎖)
-
這時該事務的讀取操作不會等待行上的鎖釋放,而是根據版本號去讀取行的快照資料(記錄在undolog中)
-
這樣,事務中的查詢操作返回的都是同一版本下的資料,解決了不可重複讀問題。
-
雖然該隔離級別下解決了不可重複讀問題,但理論上會導致另一個問題:幻讀(Phantom Read).
-
一個事務在執行過程中,另一個事務對已有資料行的更改,MVCC機制可保障該事務讀取到的原有資料行的內容相同。
-
但並不能阻止另一個事務插入新的資料行,這就會導致該事務中憑空多出資料行,像出現了幻讀一樣,這便是幻讀問題。
-
-
可序列讀(SERIALIZABLE)
-
這個事務的最高隔離級別,通過強制事務排序,使之不可能互相沖突,就是在每個讀的資料行加上共享鎖來實現
-
在該隔離級別下,可以解決前面出現的髒讀、不可重複讀和幻讀問題,但也會導致大量的超時和鎖競爭現象,一般不推薦使用
-
2.2 MySQL 中的鎖
1.鎖分類:
-
按操作劃分:DML鎖,DDL鎖
-
按鎖的粒度劃分:表級鎖、行級鎖、頁級鎖
-
按鎖的級別劃分:共享鎖、排它鎖
-
按加鎖方式劃分:自動鎖、顯示鎖
-
按使用方式劃分:樂觀鎖、悲觀鎖
2.樂觀鎖實現方法:
-
每次獲取商品時,不對該商品加鎖。
-
在跟新資料的時候需要比較程式中的庫存量與資料庫中的庫存量是否相等,如果相等則進行跟新。
-
反之程式重新獲取庫存量,再次進行比較,直到兩個庫存量的數值相等才進行資料更新。
#### 樂觀鎖實現加一操作程式碼
# 我們可以看到,只有當對數量-1操作時才會加鎖,只有當程式中值和資料庫中的值相等時才正真執行。
'''
//不加鎖
select id,name,stock where id=1;
//業務處理
begin;
update shop set stock=stock-1 where id=1 and stock=stock;
commit;
'''
3.悲觀鎖
-
每次獲取商品時,對該商品加排它鎖。
-
也就是在使用者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. 排它鎖:
-
排它鎖又叫寫鎖,如果事務T對A加上排它鎖,則其它事務都不能對A加任何型別的鎖。獲准排它鎖的事務既能讀資料,又能寫資料。
-
用法:SELECT ... FOR UPDATE
5. 共享鎖(share locke)
-
共享鎖又叫讀鎖,如果事務T對A加上共享鎖,則其它事務只能對A再加共享鎖,不能加其他鎖。
-
獲准共享鎖的事務只能讀資料,不能寫資料。
-
用法:SELECT ... LOCKIN SHARE MODE
2.3 MySQL 優化
-
欄位設計優化 適應遵循資料庫三正規化
-
引擎的選擇 適應選擇 MyIsam & InnoDB
-
索引 索引也會消耗記憶體空間,並不是越多越好。而且索引的種類都有各自的優點
-
查詢快取 將select 查詢結果快取起來,key為SQL語句,value 為查詢結果
-
分割槽
-
水平線分割和垂直分割
-
叢集
-
SQL 語句
-
伺服器的選擇 選擇配置較高的伺服器
為什麼要MySQL 優化?
-
系統的吞吐量瓶頸往往出現在資料庫的訪問速度上
-
隨著應用程式的執行,資料庫中的資料會越來越多,處理時間會相應變慢
-
為了避免查詢的出現
2.4 B-tree/B+tree
1. 以一個3階的 B-Tree 舉例
-
每個節點佔用一個盤塊的磁碟空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指標,指標儲存的時子節點所在磁碟塊的地址。
-
兩個關鍵詞劃分成的三個範圍域對應三個指標向的子樹的資料的範圍域。
-
和以根節點為例,關鍵字為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個鍵值及指標資訊為例)
-
B+Tree 的 非葉子節點致儲存鍵值資訊,假設每個磁碟塊能儲存4個鍵值及指標資訊
-
在 B+Tree 上有兩個頭指標,一個執行根節點,另一個指向關鍵子最小的葉子節點,而且所有葉子節點(及資料節點)之間時一種鏈式環結構。
-
因此可以對B+Tree 進行兩種查詢運算: 一種時對於主鍵的範圍查詢和分頁查詢,另一種時從根節點開始,進行隨機查詢。
2.5 MySQL 主從同步原理
-
master 伺服器將資料的改變記錄二進位制 binlog 日誌, 當 master 上的資料發生改變時,則將其改變寫入二進位制日誌中;
-
slave 伺服器 會在一定時間間隔內對 master 二進位制日誌進行探測其是否改變,如果傳送改變,則開始一個 I/OThread 請求 master 二進位制事件
-
同時主節點為每個I/O執行緒啟動一個dump執行緒,用於向傳送二進位制事件,並儲存致至從節點本地的中繼日誌中,從節點將啟動SQL執行緒再從中繼日誌中讀取二進位制日誌,在本地重放,使得其資料和主節點的保持一致,最後 I/O Thread 和 SQL Thread 將進入催眠狀態,等待下一次被喚醒。
2.6 MySQL- 慢查詢應用
1.答案
-
執行一條查詢語句花費的時間超過資料庫中設定的時間
-
只有超過執行的語句才會寫入到慢查詢日誌中
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的結構不一樣
-
-
字串(string)
-
列表(list)
-
字典(hash)
-
集合
-
有序集合
3.2 redis 事務
-
redis 事務是可以一次執行多個命令,本質是一組命令的集合。
-
一個事務中的所有命令都會序列化,按順序序列化的執行而不會被其他命令插入
-
作用:一個佇列中,一次性、順序性、排他性的執行一系列命令
-
multi(開始一個redis事物)
-
incr books
incr books
-
exec (執行事物)
-
discard (丟棄事物)
-
3.3 redis 分散式鎖
-
分散式鎖本質是佔一個坑,當別的程序也要來佔坑時發現已經被佔,就會放棄或者稍後重試
-
佔坑一般使用setnx(set if not exists) 指令,只允許一個客戶端佔坑
-
先來先佔,用完了再呼叫del指令釋放坑
-
但是這樣有一個問題,如果邏輯執行到中間出現異常,可能胡導致del指令沒有被呼叫,這樣就會陷入死鎖,鎖永遠無法釋放
-
為了解決死鎖問題,我們拿到鎖時可以加上一個expire過期時間,這樣即使出現異常,當到達過期時間也會自動釋放鎖
-
這樣又有一個問題,setnx 和 expire 是兩條指令而不是原子指令,如果兩條指令之間程序掛掉依然會出現死鎖
-
為了治理上面亂象,在redis2.8中加入了set指令的擴充套件引數,是setnx和expire指令可以一起執行
3.4 布隆過濾器
-
判斷某一個key一定不存在,不能判斷一個key一定存在。
-
只有有可能存在的key,才會真實的到redis中查詢(帶有不確定性)
-
原理
-
使用多個hash函式對同一個key進行hash,結果為整數
-
將hash的結果,對儲存列表取餘,在對應位置上寫1
-
在查詢redis前通過hash函式進行取值,如果一個hash對應位置不為1,就證明這個key一定不存在,不用查詢redis
-
4.5 redis 雪崩&穿透&擊穿
-
快取穿透
-
快取穿透是指查詢一個一定不存在的資料,由於快取不命中,接著查詢資料庫也無法查詢出結果。
-
雖然也不會寫入到快取中,但是這將會導致每個查詢都會去請求資料庫,造成快取穿透;
-
解決方法就是通過布隆過濾器對所有可能查詢的引數以hash形式儲存,在控制層先進行校驗,不符合則丟棄,從而避免了對底層儲存系統的查詢壓力;
-
-
快取雪崩
-
快取雪崩是指,由於快取層承載大量請求,有效的保護了儲存層,但是如果快取層由於某些原因整體不能提供服務(比如快取某一時刻可接受的請求是4000次,而如果黑客通過肉雞攻擊你的緩衝層6000次,這樣緩衝層便支撐不了壓力,導致宕機)
-
這樣所有的請求都會達到儲存層,儲存層的呼叫量會暴增,造成儲存層也會掛掉的情況。
-
保證快取層服務高可用性: 比如Redis Sentinel 和 Redis Cluster 都實現了高可用
-
依賴隔離元件為後端限流並降級: 比如對某個key 只允許一個執行緒查詢資料和寫快取,其他執行緒等待.
-
-
快取擊穿
-
快取擊穿,就是說某個key非常熱點,訪問非常頻繁,處於集中式高併發訪問的情況
-
當這個key在失效的瞬間,大量的請求就擊穿了快取,直接請求資料庫,就像是在一道屏障上鑿開一個洞。
-
解決方法就是將資料設定為永遠不過期
-
或者基於 redis or zookeeper 實現互斥鎖,等待第一個請求構建完快取之後,再釋放鎖,進而其它請求才能通過該key訪問資料.
-
3.6 主從同步
-
主從剛剛連線的時候,進行全量同步;全量同步結束後,進行增量同步。
-
當然,如果有需要,slave 再任何時候都可以發起全量同步。
-
redis 策略是,無論如何,首先嚐試進行增量同步,如不成功,要求從機制全量同步。
3.7 哨兵模式 ---- sentinel
-
sentinel 作用:
-
所有redis中的資料都一樣,通過解決請求資料量很大的問題,但是依然不能儲存更多資料
-
當用Redis 做主從方案時,假如master宕機, Redis 本事無法自動進行主備切換
-
而 Redis-sentinel 本身也是一個獨立執行的程序,它能監控多個master-slave 叢集,發現 master 宕機後才能進行自動切換
-
Sentinel 負責持續監控主節點的健康,當主節點掛掉時,自動選擇一個最優的節點切換成主節點
-
從節點來連線叢集時會首先連線sentinel,通過sentinel 來查詢主節點的地址
-
從主節點發生故障時,sentinel 會將最新的主節點地址告訴客戶端,可以實現無需啟動自動切換redis
-
3.8 codis 叢集
-
codis 是 redis 叢集解決方案之一,codis是GO語言開發的代理中介軟體
-
當客戶端向codis傳送指令時,codis負責將指令轉發給後面的redis 例項來執行,並將返回結果轉發給客戶端
-
單個codis代理支撐的QPS比較有限,通過啟動多個codis代理可以顯著增加整體QPS
-
多codis還能起到容災功能,掛掉一個codis代理還有很多codis代理可以繼續服務
04. 資料結構
4.1 常見的資料結構
-
棧
-
棧的定義: 棧式一種資料集合,可以理解為只能在一端進行插入或者刪除操作的列表
-
棧的特點:後進後出
-
-
佇列定義
-
佇列是一種資料集合,僅允許在列表的一端進行插入,另一端進行刪除
-
佇列性質:先進先出
-
雙向佇列:佇列的兩端都允許進行進隊和出隊操作
-
-
連結串列
-
連結串列中每個元素都是一個物件,每個物件稱為一個節點,包含有資料域key和指向下一節點的指標next,通過各個節點間的相互連線,最終串聯成一個連結串列
-
-
陣列
-
所謂陣列,就是相同資料型別的元素按一定順序排列的集合
-
在Java等其他語言中並不是所有的資料都能儲存到陣列中,只有相同型別的資料才可以一起儲存到陣列中。
-
因為陣列在儲存資料時是按照順序儲存的,儲存資料的記憶體也是連續的,所以它的特點就是定址讀取資料比較容易,插入和刪除比較困難
-
-
字典物件實現原理
-
雜湊表(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記憶體回收機制發現沒有門牌號後會自動回收記憶體