Python語言特性
1 Python的函式引數傳遞
看兩個例子:
1 2 3 4 5 |
a = 1 def fun(a): a = 2 fun(a) print a # 1 |
1 2 3 4 5 |
a = [] def fun(a): a.append(1) fun(a) print a # [1] |
所有的變數都可以理解是記憶體中一個物件的“引用”,或者,也可以看似c中void*的感覺。
這裡記住的是型別是屬於物件的,而不是變數。而物件有兩種,“可更改”(mutable)與“不可更改”(immutable)物件。在python中,strings, tuples, 和numbers是不可更改的物件,而list,dict等則是可以修改的物件。(這就是這個問題的重點)
當一個引用傳遞給函式的時候,函式自動複製一份引用,這個函式裡的引用和外邊的引用沒有半毛關係了.所以第一個例子裡函式把引用指向了一個不可變物件,當函式返回的時候,外面的引用沒半毛感覺.而第二個例子就不一樣了,函式內的引用指向的是可變物件,對它的操作就和定位了指標地址一樣,在記憶體裡進行修改.
如果還不明白的話,這裡有更好的解釋: http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference
2 Python中的元類(metaclass)
這個非常的不常用,但是像ORM這種複雜的結構還是會需要的,詳情請看:《深刻理解Python中的元類(metaclass)》
3 @staticmethod和@classmethod
Python其實有3個方法,即靜態方法(staticmethod),類方法(classmethod)和例項方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def foo(x): print "executing foo(%s)"%(x) class A(object): def foo(self,x): print "executing foo(%s,%s)"%(self,x) @classmethod def class_foo(cls,x): print "executing class_foo(%s,%s)"%(cls,x) @staticmethod def static_foo(x): print "executing static_foo(%s)"%x a=A() |
這裡先理解下函式引數裡面的self和cls.這個self和cls是對類或者例項的繫結,對於一般的函式來說我們可以這麼呼叫foo(x)
,這個函式就是最常用的,它的工作跟任何東西(類,例項)無關.對於例項方法,我們知道在類裡每次定義方法的時候都需要繫結這個例項,就是foo(self, x)
,為什麼要這麼做呢?因為例項方法的呼叫離不開例項,我們需要把例項自己傳給函式,呼叫的時候是這樣的a.foo(x)
(其實是foo(a, x)
).類方法一樣,只不過它傳遞的是類而不是例項,A.class_foo(x)
.注意這裡的self和cls可以替換別的引數,但是python的約定是這倆,還是不要改的好.
對於靜態方法其實和普通的方法一樣,不需要對誰進行繫結,唯一的區別是呼叫的時候需要使用a.static_foo(x)
或者A.static_foo(x)
來呼叫.
\ | 例項方法 | 類方法 | 靜態方法 |
---|---|---|---|
a = A() | a.foo(x) | a.class_foo(x) | a.static_foo(x) |
A | 不可用 | A.class_foo(x) | A.static_foo(x) |
更多關於這個問題:http://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python
4 類變數和例項變數
1 2 3 4 5 6 7 8 9 |
class Person: name="aaa" p1=Person() p2=Person() p1.name="bbb" print p1.name # bbb print p2.name # aaa print Person.name # aaa |
類變數就是供類使用的變數,例項變數就是供例項使用的.
這裡p1.name="bbb"
是例項呼叫了類變數,這其實和上面第一個問題一樣,就是函式傳參的問題,p1.name
一開始是指向的類變數name="aaa"
,但是在例項的作用域裡把類變數的引用改變了,就變成了一個例項變數,self.name不再引用Person的類變數name了.
可以看看下面的例子:
1 2 3 4 5 6 7 8 9 |
class Person: name=[] p1=Person() p2=Person() p1.name.append(1) print p1.name # [1] print p2.name # [1] print Person.name # [1] |
參考:http://stackoverflow.com/questions/6470428/catch-multiple-exceptions-in-one-line-except-block
5 Python自省
這個也是python彪悍的特性.
自省就是物件導向的語言所寫的程式在執行時,所能知道物件的型別.簡單一句就是執行時能夠獲得物件的型別.比如type(),dir(),getattr(),hasattr(),isinstance().
6 字典推導式
可能你見過列表推導時,卻沒有見過字典推導式,在2.7中才加入的:
1 |
d = {key: value for (key, value) in iterable} |
7 Python中單下劃線和雙下劃線
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> class MyClass(): ... def __init__(self): ... self.__superprivate = "Hello" ... self._semiprivate = ", world!" ... >>> mc = MyClass() >>> print mc.__superprivate Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: myClass instance has no attribute '__superprivate' >>> print mc._semiprivate , world! >>> print mc.__dict__ {'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'} |
__foo__
:一種約定,Python內部的名字,用來區別其他使用者自定義的命名,以防衝突.
_foo
:一種約定,用來指定變數私有.程式設計師用來指定私有變數的一種方式.
__foo
:這個有真正的意義:解析器用_classname__foo
來代替這個名字,以區別和其他類相同的命名.
詳情見:http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python
或者: http://www.zhihu.com/question/19754941
8 字串格式化:%和.format
.format在許多方面看起來更便利.對於%
最煩人的是它無法同時傳遞一個變數和元組.你可能會想下面的程式碼不會有什麼問題:
1 |
"hi there %s" % name |
但是,如果name恰好是(1,2,3),它將會丟擲一個TypeError異常.為了保證它總是正確的,你必須這樣做:
1 |
"hi there %s" % (name,) # 提供一個單元素的陣列而不是一個引數 |
但是有點醜..format就沒有這些問題.你給的第二個問題也是這樣,.format好看多了.
你為什麼不用它?
- 不知道它(在讀這個之前)
- 為了和Python2.5相容(譬如logging庫建議使用
%
(issue #4))
http://stackoverflow.com/questions/5082452/python-string-formatting-vs-format
9 迭代器和生成器
這個是stackoverflow裡python排名第一的問題,值得一看: http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python
這是中文版: http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/1/README.html
10 *args
and **kwargs
用*args
和**kwargs
只是為了方便並沒有強制使用它們.
當你不確定你的函式裡將要傳遞多少引數時你可以用*args
.例如,它可以傳遞任意數量的引數:
1 2 3 4 5 6 7 8 |
>>> def print_everything(*args): for count, thing in enumerate(args): ... print '{0}. {1}'.format(count, thing) ... >>> print_everything('apple', 'banana', 'cabbage') 0. apple 1. banana 2. cabbage |
相似的,**kwargs
允許你使用沒有事先定義的引數名:
1 2 3 4 5 6 7 |
>>> def table_things(**kwargs): ... for name, value in kwargs.items(): ... print '{0} = {1}'.format(name, value) ... >>> table_things(apple = 'fruit', cabbage = 'vegetable') cabbage = vegetable apple = fruit |
你也可以混著用.命名引數首先獲得引數值然後所有的其他引數都傳遞給*args
和**kwargs
.命名引數在列表的最前端.例如:
1 |
def table_things(titlestring, **kwargs) |
*args
和**kwargs
可以同時在函式的定義中,但是*args
必須在**kwargs
前面.
當呼叫函式時你也可以用*
和**
語法.例如:
1 2 3 4 5 6 7 |
>>> def print_three_things(a, b, c): ... print 'a = {0}, b = {1}, c = {2}'.format(a,b,c) ... >>> mylist = ['aardvark', 'baboon', 'cat'] >>> print_three_things(*mylist) a = aardvark, b = baboon, c = cat |
就像你看到的一樣,它可以傳遞列表(或者元組)的每一項並把它們解包.注意必須與它們在函式裡的引數相吻合.當然,你也可以在函式定義或者函式呼叫時用*.
http://stackoverflow.com/questions/3394835/args-and-kwargs
11 面向切面程式設計AOP和裝飾器
這個AOP一聽起來有點懵,同學面阿里的時候就被問懵了…
裝飾器是一個很著名的設計模式,經常被用於有切面需求的場景,較為經典的有插入日誌、效能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函式中與函式功能本身無關的雷同程式碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的物件新增額外的功能。
這個問題比較大,推薦: http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
中文: http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/3/README.html
12 鴨子型別
“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”
我們並不關心物件是什麼型別,到底是不是鴨子,只關心行為。
比如在python中,有很多file-like的東西,比如StringIO,GzipFile,socket。它們有很多相同的方法,我們把它們當作檔案使用。
又比如list.extend()方法中,我們並不關心它的引數是不是list,只要它是可迭代的,所以它的引數可以是list/tuple/dict/字串/生成器等.
鴨子型別在動態語言中經常使用,非常靈活,使得python不想java那樣專門去弄一大堆的設計模式。
13 Python中過載
引自知乎:http://www.zhihu.com/question/20053359
函式過載主要是為了解決兩個問題。
- 可變引數型別。
- 可變引數個數。
另外,一個基本的設計原則是,僅僅當兩個函式除了引數型別和引數個數不同以外,其功能是完全相同的,此時才使用函式過載,如果兩個函式的功能其實不同,那麼不應當使用過載,而應當使用一個名字不同的函式。
好吧,那麼對於情況 1 ,函式功能相同,但是引數型別不同,python 如何處理?答案是根本不需要處理,因為 python 可以接受任何型別的引數,如果函式的功能相同,那麼不同的引數型別在 python 中很可能是相同的程式碼,沒有必要做成兩個不同函式。
那麼對於情況 2 ,函式功能相同,但引數個數不同,python 如何處理?大家知道,答案就是預設引數。對那些缺少的引數設定為預設引數即可解決問題。因為你假設函式功能相同,那麼那些缺少的引數終歸是需要用的。
好了,鑑於情況 1 跟 情況 2 都有了解決方案,python 自然就不需要函式過載了。
14 新式類和舊式類
這個面試官問了,我說了老半天,不知道他問的真正意圖是什麼.
這篇文章很好的介紹了新式類的特性: http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html
新式類很早在2.2就出現了,所以舊式類完全是相容的問題,Python3裡的類全部都是新式類.這裡有一個MRO問題可以瞭解下(新式類是廣度優先,舊式類是深度優先),<Python核心程式設計>裡講的也很多.
15 __new__
和__init__
的區別
這個__new__
確實很少見到,先做了解吧.
__new__
是一個靜態方法,而__init__
是一個例項方法.__new__
方法會返回一個建立的例項,而__init__
什麼都不返回.- 只有在
__new__
返回一個cls的例項時後面的__init__
才能被呼叫. - 當建立一個新例項時呼叫
__new__
,初始化一個例項時用__init__
.
ps: __metaclass__
是建立類時起作用.所以我們可以分別使用__metaclass__
,__new__
和__init__
來分別在類建立,例項建立和例項初始化的時候做一些小手腳.
16 單例模式
這個絕對常考啊.絕對要記住1~2個方法,當時面試官是讓手寫的.
1 使用__new__
方法
1 2 3 4 5 6 7 8 9 |
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 |
2 共享屬性
建立例項時把所有例項的__dict__
指向同一個字典,這樣它們具有相同的屬性和方法.
1 2 3 4 5 6 7 8 9 |
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 |
3 裝飾器版本
1 2 3 4 5 6 7 8 9 10 11 |
def singleton(cls, *args, **kw): instances = {} def getinstance(): if cls not in instances: instances[cls] = cls(*args, **kw) return instances[cls] return getinstance @singleton class MyClass: ... |
4 import方法
作為python的模組是天然的單例模式
1 2 3 4 5 6 7 8 9 10 11 |
# mysingleton.py class My_Singleton(object): def foo(self): pass my_singleton = My_Singleton() # to use from mysingleton import my_singleton my_singleton.foo() |
17 Python中的作用域
Python 中,一個變數的作用域總是由在程式碼中被賦值的地方所決定的。
當 Python 遇到一個變數的話他會按照這樣的順序進行搜尋:
本地作用域(Local)→當前作用域被嵌入的本地作用域(Enclosing locals)→全域性/模組作用域(Global)→內建作用域(Built-in)
18 GIL執行緒全域性鎖
執行緒全域性鎖(Global Interpreter Lock),即Python為了保證執行緒安全而採取的獨立執行緒執行的限制,說白了就是一個核只能在同一時間執行一個執行緒.
解決辦法就是多程式和下面的協程(協程也只是單CPU,但是能減小切換代價提升效能).
19 協程
知乎被問到了,呵呵噠,跪了
簡單點說協程是程式和執行緒的升級版,程式和執行緒都面臨著核心態和使用者態的切換問題而耗費許多切換時間,而協程就是使用者自己控制切換的時機,不再需要陷入系統的核心態.
Python裡最常見的yield就是協程的思想!可以檢視第九個問題.
20 閉包
閉包(closure)是函數語言程式設計的重要的語法結構。閉包也是一種組織程式碼的結構,它同樣提高了程式碼的可重複使用性。
當一個內嵌函式引用其外部作作用域的變數,我們就會得到一個閉包. 總結一下,建立一個閉包必須滿足以下幾點:
- 必須有一個內嵌函式
- 內嵌函式必須引用外部函式中的變數
- 外部函式的返回值必須是內嵌函式
感覺閉包還是有難度的,幾句話是說不明白的,還是查查相關資料.
重點是函式執行後並不會被撤銷,就像16題的instance字典一樣,當函式執行完後,instance並不被銷燬,而是繼續留在記憶體空間裡.這個功能類似類裡的類變數,只不過遷移到了函式上.
閉包就像個空心球一樣,你知道外面和裡面,但你不知道中間是什麼樣.
21 lambda函式
其實就是一個匿名函式,為什麼叫lambda?因為和後面的函數語言程式設計有關.
推薦: 知乎
22 Python函數語言程式設計
這個需要適當的瞭解一下吧,畢竟函數語言程式設計在Python中也做了引用.
推薦: 酷殼
python中函數語言程式設計支援:
filter 函式的功能相當於過濾器。呼叫一個布林函式bool_func
來迭代遍歷每個seq中的元素;返回一個使bool_seq
返回值為true的元素的序列。
1 2 3 4 |
>>>a = [1,2,3,4,5,6,7] >>>b = filter(lambda x: x > 5, a) >>>print b >>>[6,7] |
map函式是對一個序列的每個項依次執行函式,下面是對一個序列每個項都乘以2:
1 2 3 |
>>> a = map(lambda x:x*2,[1,2,3]) >>> list(a) [2, 4, 6] |
reduce函式是對一個序列的每個項迭代呼叫函式,下面是求3的階乘:
1 2 |
>>> reduce(lambda x,y:x*y,range(1,4)) 6 |
23 Python裡的拷貝
引用和copy(),deepcopy()的區別
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import copy a = [1, 2, 3, 4, ['a', 'b']] #原始物件 b = a #賦值,傳物件的引用 c = copy.copy(a) #物件拷貝,淺拷貝 d = copy.deepcopy(a) #物件拷貝,深拷貝 a.append(5) #修改物件a a[4].append('c') #修改物件a中的['a', 'b']陣列物件 print 'a = ', a print 'b = ', b print 'c = ', c print 'd = ', d 輸出結果: a = [1, 2, 3, 4, ['a', 'b', 'c'], 5] b = [1, 2, 3, 4, ['a', 'b', 'c'], 5] c = [1, 2, 3, 4, ['a', 'b', 'c']] d = [1, 2, 3, 4, ['a', 'b']] |
24 Python垃圾回收機制
Python GC主要使用引用計數(reference counting)來跟蹤和回收垃圾。在引用計數的基礎上,通過“標記-清除”(mark and sweep)解決容器物件可能產生的迴圈引用問題,通過“分代回收”(generation collection)以空間換時間的方法提高垃圾回收效率。
1 引用計數
PyObject是每個物件必有的內容,其中ob_refcnt
就是做為引用計數。當一個物件有新的引用時,它的ob_refcnt
就會增加,當引用它的物件被刪除,它的ob_refcnt
就會減少.引用計數為0時,該物件生命就結束了。
優點:
- 簡單
- 實時性
缺點:
- 維護引用計數消耗資源
- 迴圈引用
2 標記-清除機制
基本思路是先按需分配,等到沒有空閒記憶體的時候從暫存器和程式棧上的引用出發,遍歷以物件為節點、以引用為邊構成的圖,把所有可以訪問到的物件打上標記,然後清掃一遍記憶體空間,把所有沒標記的物件釋放。
3 分代技術
分代回收的整體思想是:將系統中的所有記憶體塊根據其存活時間劃分為不同的集合,每個集合就成為一個“代”,垃圾收集頻率隨著“代”的存活時間的增大而減小,存活時間通常利用經過幾次垃圾回收來度量。
Python預設定義了三代物件集合,索引數越大,物件存活時間越長。
舉例:
當某些記憶體塊M經過了3次垃圾收集的清洗之後還存活時,我們就將記憶體塊M劃到一個集合A中去,而新分配的記憶體都劃分到集合B中去。當垃圾收集開始工作時,大多數情況都只對集合B進行垃圾回收,而對集合A進行垃圾回收要隔相當長一段時間後才進行,這就使得垃圾收集機制需要處理的記憶體少了,效率自然就提高了。在這個過程中,集合B中的某些記憶體塊由於存活時間長而會被轉移到集合A中,當然,集合A中實際上也存在一些垃圾,這些垃圾的回收會因為這種分代的機制而被延遲。
25 Python的List
推薦: http://www.jianshu.com/p/J4U6rR
26 Python的is
is是對比地址,==是對比值
27 read,readline和readlines
- read 讀取整個檔案
- readline 讀取下一行,使用生成器方法
- readlines 讀取整個檔案到一個迭代器以供我們遍歷
28 Python2和3的區別
推薦:《Python 2.7.x 和 3.x 版本的重要區別》
作業系統
1 select,poll和epoll
其實所有的I/O都是輪詢的方法,只不過實現的層面不同罷了.
這個問題可能有點深入了,但相信能回答出這個問題是對I/O多路複用有很好的瞭解了.其中tornado使用的就是epoll的.
基本上select有3個缺點:
- 連線數受限
- 查詢配對速度慢
- 資料由核心拷貝到使用者態
poll改善了第一個缺點
epoll改了三個缺點.
關於epoll的: http://www.cnblogs.com/my_life/articles/3968782.html
2 排程演算法
- 先來先服務(FCFS, First Come First Serve)
- 短作業優先(SJF, Shortest Job First)
- 最高優先權排程(Priority Scheduling)
- 時間片輪轉(RR, Round Robin)
- 多級反饋佇列排程(multilevel feedback queue scheduling)
實時排程演算法:
- 最早截至時間優先 EDF
- 最低鬆弛度優先 LLF
3 死鎖
原因:
- 競爭資源
- 程式推進順序不當
必要條件:
- 互斥條件
- 請求和保持條件
- 不剝奪條件
- 環路等待條件
處理死鎖基本方法:
- 預防死鎖(摒棄除1以外的條件)
- 避免死鎖(銀行家演算法)
- 檢測死鎖(資源分配圖)
- 解除死鎖
- 剝奪資源
- 撤銷程式
4 程式編譯與連結
推薦: http://www.ruanyifeng.com/blog/2014/11/compiler.html
Bulid過程可以分解為4個步驟:預處理(Prepressing), 編譯(Compilation)、彙編(Assembly)、連結(Linking)
以c語言為例:
1 預處理
預編譯過程主要處理那些原始檔中的以“#”開始的預編譯指令,主要處理規則有:
- 將所有的“#define”刪除,並展開所用的巨集定義
- 處理所有條件預編譯指令,比如“#if”、“#ifdef”、 “#elif”、“#endif”
- 處理“#include”預編譯指令,將被包含的檔案插入到該編譯指令的位置,注:此過程是遞迴進行的
- 刪除所有註釋
- 新增行號和檔名標識,以便於編譯時編譯器產生除錯用的行號資訊以及用於編譯時產生編譯錯誤或警告時可顯示行號
- 保留所有的#pragma編譯器指令。
2 編譯
編譯過程就是把預處理完的檔案進行一系列的詞法分析、語法分析、語義分析及優化後生成相應的彙編程式碼檔案。這個過程是整個程式構建的核心部分。
3 彙編
彙編器是將彙編程式碼轉化成機器可以執行的指令,每一條彙編語句幾乎都是一條機器指令。經過編譯、連結、彙編輸出的檔案成為目標檔案(Object File)
4 連結
連結的主要內容就是把各個模組之間相互引用的部分處理好,使各個模組可以正確的拼接。
連結的主要過程包塊 地址和空間的分配(Address and Storage Allocation)、符號決議(Symbol Resolution)和重定位(Relocation)等步驟。
5 靜態連結和動態連結
靜態連結方法:靜態連結的時候,載入程式碼就會把程式會用到的動態程式碼或動態程式碼的地址確定下來
靜態庫的連結可以使用靜態連結,動態連結庫也可以使用這種方法連結匯入庫
動態連結方法:使用這種方式的程式並不在一開始就完成動態連結,而是直到真正呼叫動態庫程式碼時,載入程式才計算(被呼叫的那部分)動態程式碼的邏輯地址,然後等到某個時候,程式又需要呼叫另外某塊動態程式碼時,載入程式又去計算這部分程式碼的邏輯地址,所以,這種方式使程式初始化時間較短,但執行期間的效能比不上靜態連結的程式
6 虛擬記憶體技術
虛擬儲存器是值具有請求調入功能和置換功能,能從邏輯上對記憶體容量加以擴充的一種儲存系統.
7 分頁和分段
分頁: 使用者程式的地址空間被劃分成若干固定大小的區域,稱為“頁”,相應地,記憶體空間分成若干個物理塊,頁和塊的大小相等。可將使用者程式的任一頁放在記憶體的任一塊中,實現了離散分配。
分段: 將使用者程式地址空間分成若干個大小不等的段,每段可以定義一組相對完整的邏輯資訊。儲存分配時,以段為單位,段與段在記憶體中可以不相鄰接,也實現了離散分配。
分頁與分段的主要區別
- 頁是資訊的物理單位,分頁是為了實現非連續分配,以便解決記憶體碎片問題,或者說分頁是由於系統管理的需要.段是資訊的邏輯單位,它含有一組意義相對完整的資訊,分段的目的是為了更好地實現共享,滿足使用者的需要.
- 頁的大小固定,由系統確定,將邏輯地址劃分為頁號和頁內地址是由機器硬體實現的.而段的長度卻不固定,決定於使用者所編寫的程式,通常由編譯程式在對源程式進行編譯時根據資訊的性質來劃分.
- 分頁的作業地址空間是一維的.分段的地址空間是二維的.
8 頁面置換演算法
- 最佳置換演算法OPT:不可能實現
- 先進先出FIFO
- 最近最久未使用演算法LRU:最近一段時間裡最久沒有使用過的頁面予以置換.
- clock演算法
9 邊沿觸發和水平觸發
邊緣觸發是指每當狀態變化時發生一個 io 事件,條件觸發是隻要滿足條件就發生一個 io 事件
資料庫
1 事務
資料庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。
2 資料庫索引
推薦: http://tech.meituan.com/mysql-index.html
聚集索引,非聚集索引,B-Tree,B+Tree,最左字首原理
3 Redis原理
4 樂觀鎖和悲觀鎖
悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作
樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。
5 MVCC
6 MyISAM和InnoDB
MyISAM 適合於一些需要大量查詢的應用,但其對於有大量寫操作並不是很好。甚至你只是需要update一個欄位,整個表都會被鎖起來,而別的程式,就算是讀程式都無法操作直到讀操作完成。另外,MyISAM 對於 SELECT COUNT(*) 這類的計算是超快無比的。
InnoDB 的趨勢會是一個非常複雜的儲存引擎,對於一些小的應用,它會比 MyISAM 還慢。他是它支援“行鎖” ,於是在寫操作比較多的時候,會更優秀。並且,他還支援更多的高階應用,比如:事務。
網路
1 三次握手
- 客戶端通過向伺服器端傳送一個SYN來建立一個主動開啟,作為三路握手的一部分。客戶端把這段連線的序號設定為隨機數 A。
- 伺服器端應當為一個合法的SYN回送一個SYN/ACK。ACK 的確認碼應為 A+1,SYN/ACK 包本身又有一個隨機序號 B。
- 最後,客戶端再傳送一個ACK。當服務端受到這個ACK的時候,就完成了三路握手,並進入了連線建立狀態。此時包序號被設定為收到的確認號 A+1,而響應則為 B+1。
2 四次揮手
3 ARP協議
地址解析協議(Address Resolution Protocol): 根據IP地址獲取實體地址的一個TCP/IP協議
4 urllib和urllib2的區別
這個面試官確實問過,當時答的urllib2可以Post而urllib不可以.
- urllib提供urlencode方法用來GET查詢字串的產生,而urllib2沒有。這是為何urllib常和urllib2一起使用的原因。
- urllib2可以接受一個Request類的例項來設定URL請求的headers,urllib僅可以接受URL。這意味著,你不可以偽裝你的User Agent字串等。
5 Post和Get
get: RFC 2616 – Hypertext Transfer Protocol — HTTP/1.1
post: RFC 2616 – Hypertext Transfer Protocol — HTTP/1.1
6 Cookie和Session
Cookie | Session | |
---|---|---|
儲存位置 | 客戶端 | 伺服器端 |
目的 | 跟蹤會話,也可以儲存使用者偏好設定或者儲存使用者名稱密碼等 | 跟蹤會話 |
安全性 | 不安全 | 安全 |
session技術是要使用到cookie的,之所以出現session技術,主要是為了安全。
7 apache和nginx的區別
nginx 相對 apache 的優點:
- 輕量級,同樣起web 服務,比apache 佔用更少的記憶體及資源
- 抗併發,nginx 處理請求是非同步非阻塞的,支援更多的併發連線,而apache 則是阻塞型的,在高併發下nginx 能保持低資源低消耗高效能
- 配置簡潔
- 高度模組化的設計,編寫模組相對簡單
- 社群活躍
apache 相對nginx 的優點:
- rewrite ,比nginx 的rewrite 強大
- 模組超多,基本想到的都可以找到
- 少bug ,nginx 的bug 相對較多
- 超穩定
8 網站使用者密碼儲存
- 明文儲存
- 明文hash後儲存,如md5
- MD5+Salt方式,這個salt可以隨機
- 知乎使用了Bcrypy(好像)加密
9 HTTP和HTTPS
狀態碼 | 定義 |
---|---|
1xx 報告 | 接收到請求,繼續程式 |
2xx 成功 | 步驟成功接收,被理解,並被接受 |
3xx 重定向 | 為了完成請求,必須採取進一步措施 |
4xx 客戶端出錯 | 請求包括錯的順序或不能完成 |
5xx 伺服器出錯 | 伺服器無法完成顯然有效的請求 |
403: Forbidden
404: Not Found
HTTPS握手,對稱加密,非對稱加密,TLS/SSL,RSA
10 XSRF和XSS
- CSRF(Cross-site request forgery)跨站請求偽造
- XSS(Cross Site Scripting)跨站指令碼攻擊
CSRF重點在請求,XSS重點在指令碼
11 冪等 Idempotence
HTTP方法的冪等性是指一次和多次請求某一個資源應該具有同樣的副作用。(注意是副作用)
GET http://www.bank.com/account/123456
,不會改變資源的狀態,不論呼叫一次還是N次都沒有副作用。請注意,這裡強調的是一次和N次具有相同的副作用,而不是每次GET的結果相同。GET http://www.news.com/latest-news
這個HTTP請求可能會每次得到不同的結果,但它本身並沒有產生任何副作用,因而是滿足冪等性的。
DELETE方法用於刪除資源,有副作用,但它應該滿足冪等性。比如:DELETE http://www.forum.com/article/4231
,呼叫一次和N次對系統產生的副作用是相同的,即刪掉id為4231的帖子;因此,呼叫者可以多次呼叫或重新整理頁面而不必擔心引起錯誤。
POST所對應的URI並非建立的資源本身,而是資源的接收者。比如:POST http://www.forum.com/articles
的語義是在http://www.forum.com/articles
下建立一篇帖子,HTTP響應中應包含帖子的建立狀態以及帖子的URI。兩次相同的POST請求會在伺服器端建立兩份資源,它們具有不同的URI;所以,POST方法不具備冪等性。
PUT所對應的URI是要建立或更新的資源本身。比如:PUT http://www.forum/articles/4231
的語義是建立或更新ID為4231的帖子。對同一URI進行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有冪等性。
12 RESTful架構(SOAP,RPC)
推薦: http://www.ruanyifeng.com/blog/2011/09/restful.html
13 SOAP
SOAP(原為Simple Object Access Protocol的首字母縮寫,即簡單物件訪問協議)是交換資料的一種協議規範,使用在計算機網路Web服務(web service)中,交換帶結構資訊。SOAP為了簡化網頁伺服器(Web Server)從XML資料庫中提取資料時,節省去格式化頁面時間,以及不同應用程式之間按照HTTP通訊協議,遵從XML格式執行資料互換,使其抽象於語言實現、平臺和硬體。
14 RPC
RPC(Remote Procedure Call Protocol)——遠端過程呼叫協議,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通訊程式之間攜帶資訊資料。在OSI網路通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網路分散式多程式在內的應用程式更加容易。
總結:服務提供的兩大流派.傳統意義以方法呼叫為導向通稱RPC。為了企業SOA,若干廠商聯合推出webservice,制定了wsdl介面定義,傳輸soap.當網際網路時代,臃腫SOA被簡化為http+xml/json.但是簡化出現各種混亂。以資源為導向,任何操作無非是對資源的增刪改查,於是統一的REST出現了.
進化的順序: RPC -> SOAP -> RESTful
15 CGI和WSGI
CGI是通用閘道器介面,是連線web伺服器和應用程式的介面,使用者通過CGI來獲取動態資料或檔案等。
CGI程式是一個獨立的程式,它可以用幾乎所有語言來寫,包括perl,c,lua,python等等。
WSGI, Web Server Gateway Interface,是Python應用程式或框架和Web伺服器之間的一種介面,WSGI的其中一個目的就是讓使用者可以用統一的語言(Python)編寫前後端。
官方說明:PEP-3333
16 中間人攻擊
在GFW裡屢見不鮮的,呵呵.
中間人攻擊(Man-in-the-middle attack,通常縮寫為MITM)是指攻擊者與通訊的兩端分別建立獨立的聯絡,並交換其所收到的資料,使通訊的兩端認為他們正在通過一個私密的連線與對方直接對話,但事實上整個會話都被攻擊者完全控制。
17 c10k問題
所謂c10k問題,指的是伺服器同時支援成千上萬個客戶端的問題,也就是concurrent 10 000 connection(這也是c10k這個名字的由來)。
推薦: http://www.kegel.com/c10k.html
18 socket
推薦: http://www.cnblogs.com/bingyun84/archive/2009/10/16/1584387.html
Socket=Ip address+ TCP/UDP + port
19 瀏覽器快取
推薦: http://web.jobbole.com/84367/
304 Not Modified
20 HTTP1.0和HTTP1.1
推薦: http://blog.csdn.net/elifefly/article/details/3964766
- 請求頭Host欄位,一個伺服器多個網站
- 長連結
- 檔案斷點續傳
- 身份認證,狀態管理,Cache快取
21 Ajax
AJAX,Asynchronous JavaScript and XML(非同步的 JavaScript 和 XML), 是與在不重新載入整個頁面的情況下,與伺服器交換資料並更新部分網頁的技術。
*NIX
unix程式間通訊方式(IPC)
- 管道(Pipe):管道可用於具有親緣關係程式間的通訊,允許一個程式和另一個與它有共同祖先的程式之間進行通訊。
- 命名管道(named pipe):命名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係程式間的通訊。命名管道在檔案系統中有對應的檔名。命名管道通過命令mkfifo或系統呼叫mkfifo來建立。
- 訊號(Signal):訊號是比較複雜的通訊方式,用於通知接受程式有某種事件發生,除了用於程式間通訊外,程式還可以傳送訊號給程式本身;linux除了支援Unix早期訊號語義函式sigal外,還支援語義符合Posix.1標準的訊號函式sigaction(實際上,該函式是基於BSD的,BSD為了實現可靠訊號機制,又能夠統一對外介面,用sigaction函式重新實現了signal函式)。
- 訊息(Message)佇列:訊息佇列是訊息的連結表,包括Posix訊息佇列system V訊息佇列。有足夠許可權的程式可以向佇列中新增訊息,被賦予讀許可權的程式則可以讀走佇列中的訊息。訊息佇列克服了訊號承載資訊量少,管道只能承載無格式位元組流以及緩衝區大小受限等缺
- 共享記憶體:使得多個程式可以訪問同一塊記憶體空間,是最快的可用IPC形式。是針對其他通訊機制執行效率較低而設計的。往往與其它通訊機制,如訊號量結合使用,來達到程式間的同步及互斥。
- 記憶體對映(mapped memory):記憶體對映允許任何多個程式間通訊,每一個使用該機制的程式通過把一個共享的檔案對映到自己的程式地址空間來實現它。
- 訊號量(semaphore):主要作為程式間以及同一程式不同執行緒之間的同步手段。
- 套介面(Socket):更為一般的程式間通訊機制,可用於不同機器之間的程式間通訊。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支援套接字。
資料結構
1 紅黑樹
紅黑樹與AVL的比較:
AVL是嚴格平衡樹,因此在增加或者刪除節點的時候,根據不同情況,旋轉的次數比紅黑樹要多;
紅黑是用非嚴格的平衡來換取增刪節點時候旋轉次數的降低;
所以簡單說,如果你的應用中,搜尋的次數遠遠大於插入和刪除,那麼選擇AVL,如果搜尋,插入刪除次數幾乎差不多,應該選擇RB。
程式設計題
1 臺階問題/斐波納挈
一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
1 |
fib = lambda n: n if n <= 2 else fib(n - 1) + fib(n - 2) |
第二種記憶方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def memo(func): cache = {} def wrap(*args): if args not in cache: cache[args] = func(*args) return cache[args] return wrap @ memo def fib(i): if i < 2: return 1 return fib(i-1) + fib(i-2) |
第三種方法
1 2 3 4 5 |
def fib(n): a, b = 0, 1 for _ in xrange(n): a, b = b, a + b return b |
2 變態臺階問題
一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
1 |
fib = lambda n: n if n < 2 else 2 * fib(n - 1) |
3 矩形覆蓋
我們可以用2*1
的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2*1
的小矩形無重疊地覆蓋一個2*n
的大矩形,總共有多少種方法?
第
2*n
個矩形的覆蓋方法等於第2*(n-1)
加上第2*(n-2)
的方法。
1 |
f = lambda n: 1 if n < 2 else f(n - 1) + f(n - 2) |
4 楊氏矩陣查詢
在一個m行n列二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。
5 去除列表中的重複元素
用集合
1 |
list(set(l)) |
用字典
1 2 3 |
l1 = ['b','c','d','b','c','a','a'] l2 = {}.fromkeys(l1).keys() print l2 |
用字典並保持順序
1 2 3 4 |
l1 = ['b','c','d','b','c','a','a'] l2 = list(set(l1)) l2.sort(key=l1.index) print l2 |
列表推導式
1 2 3 |
l1 = ['b','c','d','b','c','a','a'] l2 = [] [l2.append(i) for i in l1 if not i in l2] |
面試官提到的,先排序然後刪除.
6 連結串列成對調換
1->2->3->4
轉換成2->1->4->3
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class ListNode: def __init__(self, x): self.val = x self.next = None class Solution: # @param a ListNode # @return a ListNode def swapPairs(self, head): if head != None and head.next != None: next = head.next head.next = self.swapPairs(next.next) next.next = head return next return head |
7 建立字典的方法
1 直接建立
1 |
dict = {'name':'earth', 'port':'80'} |
2 工廠方法
1 2 3 |
items=[('name','earth'),('port','80')] dict2=dict(items) dict1=dict((['name','earth'],['port','80'])) |
3 fromkeys()方法
1 2 3 4 |
dict1={}.fromkeys(('x','y'),-1) dict={'x':-1,'y':-1} dict2={}.fromkeys(('x','y')) dict2={'x':None, 'y':None} |
8 合併兩個有序列表
知乎遠端面試要求程式設計
尾遞迴
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def _recursion_merge_sort2(l1, l2, tmp): if len(l1) == 0 or len(l2) == 0: tmp.extend(l1) tmp.extend(l2) return tmp else: if l1[0] < l2[0]: tmp.append(l1[0]) del l1[0] else: tmp.append(l2[0]) del l2[0] return _recursion_merge_sort2(l1, l2, tmp) def recursion_merge_sort2(l1, l2): return _recursion_merge_sort2(l1, l2, []) |
迴圈演算法
1 2 3 4 5 6 7 8 9 10 11 12 |
def loop_merge_sort(l1, l2): tmp = [] while len(l1) > 0 and len(l2) > 0: if l1[0] < l2[0]: tmp.append(l1[0]) del l1[0] else: tmp.append(l2[0]) del l2[0] tmp.extend(l1) tmp.extend(l2) return tmp |
9 交叉連結串列求交點
去哪兒的面試,沒做出來.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class ListNode: def __init__(self, x): self.val = x self.next = None def node(l1, l2): length1, lenth2 = 0, 0 # 求兩個連結串列長度 while l1.next: l1 = l1.next length1 += 1 while l2.next: l2 = l2.next length2 += 1 # 長的連結串列先走 if length1 > lenth2: for _ in range(length1 - length2): l1 = l1.next else: for _ in range(length2 - length1): l2 = l2.next while l1 and l2: if l1.next == l2.next: return l1.next else: l1 = l1.next l2 = l2.next |
10 二分查詢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def binarySearch(l, t): low, high = 0, len(l) - 1 while low < high: print low, high mid = (low + high) / 2 if l[mid] > t: high = mid elif l[mid] < t: low = mid + 1 else: return mid return low if l[low] == t else False if __name__ == '__main__': l = [1, 4, 12, 45, 66, 99, 120, 444] print binarySearch(l, 12) print binarySearch(l, 1) print binarySearch(l, 13) print binarySearch(l, 444) |
11 快排
1 2 3 4 5 6 7 8 9 10 11 12 |
def qsort(seq): if seq==[]: return [] else: pivot=seq[0] lesser=qsort([x for x in seq[1:] if x<pivot]) greater=qsort([x for x in seq[1:] if x>=pivot]) return lesser+[pivot]+greater if __name__=='__main__': seq=[5,6,78,9,0,-1,2,3,-65,12] print(qsort(seq)) |
12 找零問題
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def coinChange(values, money, coinsUsed): #values T[1:n]陣列 #valuesCounts 錢幣對應的種類數 #money 找出來的總錢數 #coinsUsed 對應於目前錢幣總數i所使用的硬幣數目 for cents in range(1, money+1): minCoins = cents #從第一個開始到money的所有情況初始 for value in values: if value <= cents: temp = coinsUsed[cents - value] + 1 if temp < minCoins: minCoins = temp coinsUsed[cents] = minCoins print('面值為:{0} 的最小硬幣數目為:{1} '.format(cents, coinsUsed[cents]) ) if __name__ == '__main__': values = [ 25, 21, 10, 5, 1] money = 63 coinsUsed = {i:0 for i in range(money+1)} coinChange(values, money, coinsUsed) |
13 廣度遍歷和深度遍歷二叉樹
給定一個陣列,構建二叉樹,並且按層次列印這個二叉樹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
## 14 二叉樹節點 class Node(object): def __init__(self, data, left=None, right=None): self.data = data self.left = left self.right = right tree = Node(1, Node(3, Node(7, Node(0)), Node(6)), Node(2, Node(5), Node(4))) ## 15 層次遍歷 def lookup(root): stack = [root] while stack: current = stack.pop(0) print current.data if current.left: stack.append(current.left) if current.right: stack.append(current.right) ## 16 深度遍歷 def deep(root): if not root: return print root.data deep(root.left) deep(root.right) if __name__ == '__main__': lookup(tree) deep(tree) |
17 前中後序遍歷
深度遍歷改變順序就OK了
18 求最大樹深
1 2 3 4 |
def maxDepth(root): if not root: return 0 return max(maxDepth(root.left), maxDepth(root.right)) + 1 |
19 求兩棵樹是否相同
1 2 3 4 5 6 7 |
def isSameTree(p, q): if p == None and q == None: return True elif p and q : return p.val == q.val and isSameTree(p.left,q.left) and isSameTree(p.right,q.right) else : return False |
20 前序中序求後序
推薦: http://blog.csdn.net/hinyunsin/article/details/6315502
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def rebuild(pre, center): if not pre: return cur = Node(pre[0]) index = center.index(pre[0]) cur.left = rebuild(pre[1:index + 1], center[:index]) cur.right = rebuild(pre[index + 1:], center[index + 1:]) return cur def deep(root): if not root: return deep(root.left) deep(root.right) print root.data |
21 單連結串列逆置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Node(object): def __init__(self, data=None, next=None): self.data = data self.next = next link = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7, Node(8, Node(9))))))))) def rev(link): pre = link cur = link.next pre.next = None while cur: tmp = cur.next cur.next = pre pre = cur cur = tmp return pre root = rev(link) while root: print root.data root = root.next |