後端開發面試題 =================== #後端開發面試知識點大綱: ##語言類(C++): ###關鍵字做用解釋: volatile做用 Volatile關鍵詞的第一個特性:易變性。所謂的易變性,在彙編層面反映出來,就是兩條語句,下一條語句不會直接使用上一條語句對應的volatile變量的寄存器內容,而是從新從內存中讀取。 Volatile關鍵詞的第二個特性:「不可優化」特性。volatile告訴編譯器,不要對我這個變量進行各類激進的優化,甚至將變量直接消除,保證程序員寫在代碼中的指令,必定會被執行。 Volatile關鍵詞的第三個特性:」順序性」,可以保證Volatile變量間的順序性,編譯器不會進行亂序優化。 C/C++ Volatile變量,與非Volatile變量之間的操做,是可能被編譯器交換順序的。C/C++ Volatile變量間的操做,是不會被編譯器交換順序的。哪怕將全部的變量所有都聲明爲volatile,哪怕杜絕了編譯器的亂序優化,可是針對生成的彙編代碼,CPU有可能仍舊會亂序執行指令,致使程序依賴的邏輯出錯,volatile對此無能爲力 針對這個多線程的應用,真正正確的作法,是構建一個happens-before語義。 [C/C++ Volatile關鍵詞深度剖析](http://hedengcheng.com/?p=725) static 控制變量的存儲方式和可見性。 (1)修飾局部變量 通常狀況下,對於局部變量是存放在棧區的,而且局部變量的生命週期在該語句塊執行結束時便結束了。可是若是用static進行修飾的話,該變量便存放在靜態數據區,其生命週期一直持續到整個程序執行結束。可是在這裏要注意的是,雖然用static對局部變量進行修飾事後,其生命週期以及存儲空間發生了變化,可是其做用域並無改變,其仍然是一個局部變量,做用域僅限於該語句塊。 (2)修飾全局變量 對於一個全局變量,它既能夠在本源文件中被訪問到,也能夠在同一個工程的其它源文件中被訪問(只需用extern進行聲明便可)。用static對全局變量進行修飾改變了其做用域的範圍,由原來的整個工程可見變爲本源文件可見。 (3)修飾函數 用static修飾函數的話,狀況與修飾全局變量大同小異,就是改變了函數的做用域。 (4)C++中的static 若是在C++中對類中的某個函數用static進行修飾,則表示該函數屬於一個類而不是屬於此類的任何特定對象;若是對類中的某個變量進行static修飾,表示該變量爲類以及其全部的對象全部。它們在存儲空間中都只存在一個副本。能夠經過類和對象去調用。 const的含義及實現機制 const名叫常量限定符,用來限定特定變量,以通知編譯器該變量是不可修改的。習慣性的使用const,能夠避免在函數中對某些不該修改的變量形成可能的改動。 (1)const修飾基本數據類型 1.const修飾通常常量及數組 基本數據類型,修飾符const能夠用在類型說明符前,也能夠用在類型說明符後,其結果是同樣的。在使用這些常量的時候,只要不改變這些常量的值便好。 2.const修飾指針變量*及引用變量& 若是const位於星號*的左側,則const就是用來修飾指針所指向的變量,即指針指向爲常量; 若是const位於星號的右側,const就是修飾指針自己,即指針自己是常量。 (2)const應用到函數中, 1.做爲參數的const修飾符 調用函數的時候,用相應的變量初始化const常量,則在函數體中,按照const所修飾的部分進行常量化,保護了原對象的屬性。 [注意]:參數const一般用於參數爲指針或引用的狀況; 2.做爲函數返回值的const修飾符 聲明瞭返回值後,const按照"修飾原則"進行修飾,起到相應的保護做用。 (3)const在類中的用法 不能在類聲明中初始化const數據成員。正確的使用const實現方法爲:const數據成員的初始化只能在類構造函數的初始化表中進行 類中的成員函數:A fun4()const; 其意義上是不能修改所在類的的任何變量。 (4)const修飾類對象,定義常量對象 常量對象只能調用常量函數,別的成員函數都不能調用。 http://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html extern 在C語言中,修飾符extern用在變量或者函數的聲明前,用來講明「此變量/函數是在別處定義的,要在此處引用」。 注意extern聲明的位置對其做用域也有關係,若是是在main函數中進行聲明的,則只能在main函數中調用,在其它函數中不能調用。其實要調用其它文件中的函數和變量,只需把該文件用#include包含進來便可,爲啥要用extern?由於用extern會加速程序的編譯過程,這樣能節省時間。 在C++中extern還有另一種做用,用於指示C或者C++函數的調用規範。好比在C++中調用C庫函數,就須要在C++程序中用extern 「C」聲明要引用的函數。這是給連接器用的,告訴連接器在連接的時候用C函數規範來連接。主要緣由是C++和C程序編譯完成後在目標代碼中命名規則不一樣,用此來解決名字匹配的問題。 宏定義和展開、內聯函數區別, 內聯函數是代碼被插入到調用者代碼處的函數。如同 #define 宏,內聯函數經過避免被調用的開銷來提升執行效率,尤爲是它可以經過調用(「過程化集成」)被編譯器優化。 宏定義不檢查函數參數,返回值什麼的,只是展開,相對來講,內聯函數會檢查參數類型,因此更安全。 內聯函數和宏很相似,而區別在於,宏是由預處理器對宏進行替代,而內聯函數是經過編譯器控制來實現的。並且內聯函數是真正的函數,只是在須要用到的時候,內聯函數像宏同樣的展開,因此取消了函數的參數壓棧,減小了調用的開銷。 宏是預編譯器的輸入,而後宏展開以後的結果會送去編譯器作語法分析。宏與函數等處於不一樣的級別,操做不一樣的實體。宏操做的是 token, 能夠進行 token的替換和鏈接等操做,在語法分析以前起做用。而函數是語言中的概念,會在語法樹中建立對應的實體,內聯只是函數的一個屬性。 對於問題:有了函數要它們何用?答案是:一:函數並不能徹底替代宏,有些宏能夠在當前做用域生成一些變量,函數作不到。二:內聯函數只是函數的一種,內聯是給編譯器的提示,告訴它最好把這個函數在被調用處展開,省掉一個函數調用的開銷(壓棧,跳轉,返回) 內聯函數也有必定的侷限性。就是函數中的執行代碼不能太多了,若是,內聯函數的函數體過大,通常的編譯器會放棄內聯方式,而採用普通的方式調用函數。這樣,內聯函數就和普通函數執行效率同樣 內聯函數必須是和函數體申明在一塊兒,纔有效。 [宏定義和內聯函數區別](http://www.cnblogs.com/chengxuyuancc/archive/2013/04/04/2999844.html) ###庫函數實現: malloc,strcpy,strcmp的實現,經常使用庫函數實現,哪些庫函數屬於高危函數 ###STL原理及實現: STL各種型容器實現,STL共有六大組件 STL提供六大組件,彼此能夠組合套用: 一、容器(Containers):各類數據結構,如:序列式容器vector、list、deque、關聯式容器set、map、multiset、multimap。用來存放數據。從實現的角度來看,STL容器是一種class template。 二、算法(algorithms):各類經常使用算法,如:sort、search、copy、erase。從實現的角度來看,STL算法是一種 function template。注意一個問題:任何的一個STL算法,都須要得到由一對迭代器所標示的區間,用來表示操做範圍。這一對迭代器所標示的區間都是前閉後開區間,例如[first, last) 三、迭代器(iterators):容器與算法之間的膠合劑,是所謂的「泛型指針」。共有五種類型,以及其餘衍生變化。從實現的角度來看,迭代器是一種將 operator*、operator->、operator++、operator- - 等指針相關操做進行重載的class template。全部STL容器都有本身專屬的迭代器,只有容器自己才知道如何遍歷本身的元素。原生指針(native pointer)也是一種迭代器。 四、仿函數(functors):行爲相似函數,可做爲算法的某種策略(policy)。從實現的角度來看,仿函數是一種重載了operator()的class或class template。通常的函數指針也可視爲狹義的仿函數。 五、配接器(adapters):一種用來修飾容器、仿函數、迭代器接口的東西。例如:STL提供的queue 和 stack,雖然看似容器,但其實只能算是一種容器配接器,由於它們的底部徹底藉助deque,全部操做都由底層的deque供應。改變 functors接口者,稱爲function adapter;改變 container 接口者,稱爲container adapter;改變iterator接口者,稱爲iterator adapter。 六、配置器(allocators):負責空間配置與管理。從實現的角度來看,配置器是一個實現了動態空間配置、空間管理、空間釋放的class template。 這六大組件的交互關係:container(容器) 經過 allocator(配置器) 取得數據儲存空間,algorithm(算法)經過 iterator(迭代器)存取 container(容器) 內容,functor(仿函數) 能夠協助 algorithm(算法) 完成不一樣的策略變化,adapter(配接器) 能夠修飾或套接 functor(仿函數) 序列式容器: vector-數組,元素不夠時再從新分配內存,拷貝原來數組的元素到新分配的數組中。 list-單鏈表。 deque-分配中央控制器map(並不是map容器),map記錄着一系列的固定長度的數組的地址.記住這個map僅僅保存的是數組的地址,真正的數據在數組中存放着.deque先從map中央的位置(由於雙向隊列,先後均可以插入元素)找到一個數組地址,向該數組中放入數據,數組不夠時繼續在map中找空閒的數組來存數據。當map也不夠時從新分配內存看成新的map,把原來map中的內容copy的新map中。因此使用deque的複雜度要大於vector,儘可能使用vector。 stack-基於deque。 queue-基於deque。 heap-徹底二叉樹,使用最大堆排序,以數組(vector)的形式存放。 priority_queue-基於heap。 slist-雙向鏈表。 關聯式容器: set,map,multiset,multimap-基於紅黑樹(RB-tree),一種加上了額外平衡條件的二叉搜索樹。 hash table-散列表。將待存數據的key通過映射函數變成一個數組(通常是vector)的索引,例如:數據的key%數組的大小=數組的索引(通常文本經過算法也能夠轉換爲數字),而後將數據看成此索引的數組元素。有些數據的key通過算法的轉換多是同一個數組的索引值(碰撞問題,能夠用線性探測,二次探測來解決),STL是用開鏈的方法來解決的,每個數組的元素維護一個list,他把相同索引值的數據存入一個list,這樣當list比較短時執行刪除,插入,搜索等算法比較快。 hash_map,hash_set,hash_multiset,hash_multimap-基於hashtable。 [STL六大組件] (http://blog.csdn.net/chenguolinblog/article/details/30336805) 什麼是「標準非STL容器」? list和vector有什麼區別? vector擁有一段連續的內存空間,所以支持隨機存取,若是須要高效的隨即存取,而不在意插入和刪除的效率,使用vector。 list擁有一段不連續的內存空間,所以不支持隨機存取,若是須要大量的插入和刪除,而不關心隨即存取,則應使用list。 ###虛函數: 虛函數的做用和實現原理,什麼是虛函數,有什麼做用? C++的多態分爲靜態多態(編譯時多態)和動態多態(運行時多態)兩大類。靜態多態經過重載、模板來實現;動態多態就是經過本文的主角虛函數來體現的。 虛函數實現原理:包括虛函數表、虛函數指針等 虛函數的做用說白了就是:當調用一個虛函數時,被執行的代碼必須和調用函數的對象的動態類型相一致。編譯器須要作的就是如何高效的實現提供這種特性。不一樣編譯器實現細節也不相同。大多數編譯器經過vtbl(virtual table)和vptr(virtual table pointer)來實現的。 當一個類聲明瞭虛函數或者繼承了虛函數,這個類就會有本身的vtbl。vtbl實際上就是一個函數指針數組,有的編譯器用的是鏈表,不過方法都是差很少。vtbl數組中的每個元素對應一個函數指針指向該類的一個虛函數,同時該類的每個對象都會包含一個vptr,vptr指向該vtbl的地址。 結論: 每一個聲明瞭虛函數或者繼承了虛函數的類,都會有一個本身的vtbl 同時該類的每一個對象都會包含一個vptr去指向該vtbl 虛函數按照其聲明順序放於vtbl表中, vtbl數組中的每個元素對應一個函數指針指向該類的虛函數 若是子類覆蓋了父類的虛函數,將被放到了虛表中原來父類虛函數的位置 在多繼承的狀況下,每一個父類都有本身的虛表。子類的成員函數被放到了第一個父類的表中 衍生問題:爲何 C++裏訪問虛函數比訪問普通函數慢? 單繼承時性能差很少,多繼承的時候會慢 調用性能方面 從前面虛函數的調用過程可知。當調用虛函數時過程以下(引自More Effective C++): 經過對象的 vptr 找到類的 vtbl。這是一個簡單的操做,由於編譯器知道在對象內 哪裏能找到 vptr(畢竟是由編譯器放置的它們)。所以這個代價只是一個偏移調整(以獲得 vptr)和一個指針的間接尋址(以獲得 vtbl)。 找到對應 vtbl 內的指向被調用函數的指針。這也是很簡單的, 由於編譯器爲每一個虛函數在 vtbl 內分配了一個惟一的索引。這步的代價只是在 vtbl 數組內 的一個偏移。 調用第二步找到的的指針所指向的函數。 在單繼承的狀況下,調用虛函數所需的代價基本上和非虛函數效率同樣,在大多數計算機上它多執行了不多的一些指令,因此有不少人一律而論說虛函數性能不行是不太科學的。在多繼承的狀況下,因爲會根據多個父類生成多個vptr,在對象裏爲尋找 vptr 而進行的偏移量計算會變得複雜一些,但這些並非虛函數的性能瓶頸。 虛函數運行時所需的代價主要是虛函數不能是內聯函。這也是很是好理解的,是由於內聯函數是指在編譯期間用被調用的函數體自己來代替函數調用的指令,可是虛函數的「虛」是指「直到運行時才能知道要調用的是哪個函數。」但虛函數的運行時多態特性就是要在運行時才知道具體調用哪一個虛函數,因此無法在編譯時進行內聯函數展開。固然若是經過對象直接調用虛函數它是能夠被內聯,可是大多數虛函數是經過對象的指針或引用被調用的,這種調用不能被內聯。 由於這種調用是標準的調用方式,因此虛函數實際上不能被內聯。 佔用空間方面 在上面的虛函數實現原理部分,能夠看到爲了實現運行時多態機制,編譯器會給每個包含虛函數或繼承了虛函數的類自動創建一個虛函數表,因此虛函數的一個代價就是會增長類的體積。在虛函數接口較少的類中這個代價並不明顯,虛函數表vtbl的體積至關於幾個函數指針的體積,若是你有大量的類或者在每一個類中有大量的虛函數,你會發現 vtbl 會佔用大量的地址空間。但這並非最主要的代價,主要的代價是發生在類的繼承過程當中,在上面的分析中,能夠看到,當子類繼承父類的虛函數時,子類會有本身的vtbl,若是子類只覆蓋父類的一兩個虛函數接口,子類vtbl的其他部份內容會與父類重複。這在若是存在大量的子類繼承,且重寫父類的虛函數接口只佔總數的一小部分的狀況下,會形成大量地址空間浪費。在一些GUI庫上這種大量子類繼承自同一父類且只覆蓋其中一兩個虛函數的狀況是常常有的,這樣就致使UI庫的佔用內存明顯變大。 因爲虛函數指針vptr的存在,虛函數也會增長該類的每一個對象的體積。在單繼承或沒有繼承的狀況下,類的每一個對象會多一個vptr指針的體積,也就是4個字節;在多繼承的狀況下,類的每一個對象會多N個(N=包含虛函數的父類個數)vptr的體積,也就是4N個字節。當一個類的對象體積較大時,這個代價不是很明顯,但當一個類的對象很輕量的時候,如成員變量只有4個字節,那麼再加上4(或4N)個字節的vptr,對象的體積至關於翻了1(或N)倍,這個代價是很是大的。 [C++虛函數淺析](http://glgjing.github.io/blog/2015/01/03/c-plus-plus-xu-han-shu-qian-xi/) 純虛函數,爲何須要純虛函數? 純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義本身的實現方法。在基類中實現純虛函數的方法是在函數原型後加「=0」 virtual void funtion1()=0 緣由: 一、爲了方便使用多態特性,咱們經常須要在基類中定義虛擬函數。 二、在不少狀況下,基類自己生成對象是不合情理的。例如,動物做爲一個基類能夠派生出老虎、孔雀等子類,但動物自己生成對象明顯不合常理。 爲了解決上述問題,引入了純虛函數的概念,將函數定義爲純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱爲抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。聲明瞭純虛函數的類是一個抽象類。因此,用戶不能建立類的實例,只能建立它的派生類的實例。 定義純虛函數的目的在於,使派生類僅僅只是繼承函數的接口。 純虛函數的意義,讓全部的類對象(主要是派生類對象)均可以執行純虛函數的動做,但類沒法爲純虛函數提供一個合理的缺省實現。因此類純虛函數的聲明就是在告訴子類的設計者,「你必須提供一個純虛函數的實現,但我不知道你會怎樣實現它」。 [虛函數和純虛函數的區別](http://blog.csdn.net/hackbuteer1/article/details/7558868) 爲何須要虛析構函數,何時不須要?父類的析構函數爲何要定義爲虛函數 通常狀況下類的析構函數裏面都是釋放內存資源,而析構函數不被調用的話就會形成內存泄漏。這樣作是爲了當用一個基類的指針刪除一個派生類的對象時,派生類的析構函數會被調用。 固然,並非要把全部類的析構函數都寫成虛函數。由於當類裏面有虛函數的時候,編譯器會給類添加一個虛函數表,裏面來存放虛函數指針,這樣就會增長類的存儲空間。因此,只有當一個類被用來做爲基類的時候,才把析構函數寫成虛函數。 內聯函數、構造函數、靜態成員函數能夠是虛函數嗎? inline, static, constructor三種函數都不能帶有virtual關鍵字。 inline是編譯時展開,必須有實體; static屬於class本身的,也必須有實體; virtual函數基於vtable(內存空間),constructor函數若是是virtual的,調用時也須要根據vtable尋找,可是constructor是virtual的狀況下是找不到的,由於constructor本身自己都不存在了,建立不到class的實例,沒有實例,class的成員(除了public static/protected static for friend class/functions,其他不管是否virtual)都不能被訪問了。 虛函數實際上不能被內聯:虛函數運行時所需的代價主要是虛函數不能是內聯函。這也是很是好理解的,是由於內聯函數是指在編譯期間用被調用的函數體自己來代替函數調用的指令,可是虛函數的「虛」是指「直到運行時才能知道要調用的是哪個函數。」但虛函數的運行時多態特性就是要在運行時才知道具體調用哪一個虛函數,因此無法在編譯時進行內聯函數展開。固然若是經過對象直接調用虛函數它是能夠被內聯,可是大多數虛函數是經過對象的指針或引用被調用的,這種調用不能被內聯。 由於這種調用是標準的調用方式,因此虛函數實際上不能被內聯。 構造函數不能是虛函數。並且,在構造函數中調用虛函數,實際執行的是父類的對應函數,由於本身尚未構造好, 多態是被disable的。 靜態的對象是屬於整個類的,不對某一個對象而言,同時其函數的指針存放也不一樣於通常的成員函數,其沒法成爲一個對象的虛函數的指針以實現由此帶來的動態機制。 構造函數中能夠調用虛函數嗎? 最後,總結一下關於虛函數的一些常見問題: 1) 虛函數是動態綁定的,也就是說,使用虛函數的指針和引用可以正確找到實際類的對應函數,而不是執行定義類的函數。這是虛函數的基本功能,就再也不解釋了。 2) 構造函數不能是虛函數。並且,在構造函數中調用虛函數,實際執行的是父類的對應函數,由於本身尚未構造好, 多態是被disable的。 3) 析構函數能夠是虛函數,並且,在一個複雜類結構中,這每每是必須的。 4) 將一個函數定義爲純虛函數,其實是將這個類定義爲抽象類,不能實例化對象。 5) 純虛函數一般沒有定義體,但也徹底能夠擁有。 6) 析構函數能夠是純虛的,但純虛析構函數必須有定義體,由於析構函數的調用是在子類中隱含的。 7) 非純的虛函數必須有定義體,否則是一個錯誤。 8) 派生類的override虛函數定義必須和父類徹底一致。除了一個特例,若是父類中返回值是一個指針或引用,子類override時能夠返回這個指針(或引用)的派生。例如,在上面的例子中,在Base中定義了 virtual Base* clone(); 在Derived中能夠定義爲 virtual Derived* clone()。能夠看到,這種放鬆對於Clone模式是很是有用的。 [虛析構函數(√)、純虛析構函數(√)、虛構造函數(X)](http://www.cnblogs.com/chio/archive/2007/09/10/888260.html) 爲何須要虛繼承?虛繼承實現原理解析, 虛擬繼承是多重繼承中特有的概念。虛擬基類是爲解決多重繼承而出現的。 如:類D繼承自類B一、B2,而類B一、B2都繼 承自類A,所以在類D中兩次出現類A中的變量和函數。爲了節省內存空間,能夠將B一、B2對A的繼承定義爲虛擬繼承,而A就成了虛擬基類,虛擬繼承在通常的應用中不多用到,因此也每每被忽視,這也主要是由於在C++中,多重繼承是不推薦的,也並不經常使用,而一旦離開了多重繼承,虛擬繼承就徹底失去了存在的必要由於這樣只會下降效率和佔用更多的空間。 虛繼承的特色是,在任何派生類中的virtual基類總用同一個(共享)對象表示, [C++虛擬繼承](http://blog.csdn.net/hyg0811/article/details/11951855) ###設計模式: C++單例模式寫法: 靜態化並非單例 (Singleton) 模式: 第一, 靜態成員變量初始化順序不依賴構造函數, 得看編譯器心情的, 無法保證初始化順序 (極端狀況: 有 a b 兩個成員對象, b 須要把 a 做爲初始化參數傳入, 你的類就 必須 得要有構造函數, 並確保初始化順序). 第二, 最嚴重的問題, 失去了面對對象的重要特性 -- "多態", 靜態成員方法不多是 virtual 的. Log 類的子類無法享受 "多態" 帶來的便利. class Log { public: static void Write(char const *logline); static bool SaveTo(char const *filename); private: static std::list<std::string> m_data; }; In log.cpp we need to add std::list<std::string> Log::m_data; 餓漢模式: 餓漢模式 是指單例實例在程序運行時被當即執行初始化: class Log { public: static Log* Instance() { return &m_pInstance; } virtual void Write(char const *logline); virtual bool SaveTo(char const *filename); private: Log(); // ctor is hidden Log(Log const&); // copy ctor is hidden static Log m_pInstance; static std::list<std::string> m_data; }; // in log.cpp we have to add Log Log::m_pInstance; 這種模式的問題也很明顯, 類如今是多態的, 但靜態成員變量初始化順序仍是沒保證. 懶漢模式 (堆棧-粗糙版) 單例實例只在第一次被使用時進行初始化: class Log { public: static Log* Instance() { if (!m_pInstance) m_pInstance = new Log; return m_pInstance; } virtual void Write(char const *logline); virtual bool SaveTo(char const *filename); private: Log(); // ctor is hidden Log(Log const&); // copy ctor is hidden static Log* m_pInstance; static std::list<std::string> m_data; }; // in log.cpp we have to add Log* Log::m_pInstance = NULL; Instance() 只在第一次被調用時爲 m_pInstance 分配內存並初始化. 嗯, 看上去全部的問題都解決了, 初始化順序有保證, 多態也沒問題. 程序退出時, 析構函數沒被執行. 這在某些設計不可靠的系統上會致使資源泄漏, 好比文件句柄, socket 鏈接, 內存等等 對於這個問題, 比較土的解決方法是, 給每一個 Singleton 類添加一個 destructor() 方法: 懶漢模式 (局部靜態變量-最佳版) 它也被稱爲 Meyers Singleton [Meyers]: class Log { public: static Log& Instance() { static Log theLog; return theLog; } virtual void Write(char const *logline); virtual bool SaveTo(char const *filename); private: Log(); // ctor is hidden Log(Log const&); // copy ctor is hidden Log& operator=(Log const&); // assign op is hidden static std::list<std::string> m_data; }; 在 Instance() 函數內定義局部靜態變量的好處是, theLog `` 的構造函數只會在第一次調用 ``Instance() 時被初始化, 達到了和 "堆棧版" 相同的動態初始化效果, 保證了成員變量和 Singleton 自己的初始化順序. 它還有一個潛在的安全措施, Instance() 返回的是對局部靜態變量的引用, 若是返回的是指針, Instance() 的調用者極可能會誤認爲他要檢查指針的有效性, 並負責銷燬. 構造函數和拷貝構造函數也私有化了, 這樣類的使用者不能自行實例化. 另外, 多個不一樣的 Singleton 實例的析構順序與構造順序相反. [C++ Singleton (單例) 模式最優實現](http://blog.yangyubo.com/2009/06/04/best-cpp-singleton-pattern/) 用C++設計一個不能被繼承的類。 構造函數或析構函數爲私有函數,因此該類是沒法被繼承的, 如何定義一個只能在堆上定義對象的類?棧上呢 只能在堆內存上實例化的類:將析構函數定義爲private,在棧上不能自動調用析構函數,只能手動調用。也能夠將構造函數定義爲private,但這樣須要手動寫一個函數實現對象的構造。 只能在棧內存上實例化的類:將函數operator new和operator delete定義爲private,這樣使用new操做符建立對象時候,沒法調用operator new,delete銷燬對象也沒法調用operator delete。 [設計一個只能在堆上或棧上實例化的類](http://www.cnblogs.com/luxiaoxun/archive/2012/08/03/2621827.html) 知足上述3個條件 [C++中的單例模式](http://www.cnblogs.com/xiehongfeng100/p/4781013.html) 多重類構造和析構的順序 先調用基類的構造函數,在調用派生類的構造函數 先構造的後析構,後構造的先析構 ###內存分配: 內存分配方式有三種: (1)從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。 (2)在棧上建立。在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。 (3)從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員本身負責在什麼時候用free或delete釋放內存。動態內存的生存期由咱們決定,使用很是靈活,但問題也最多。 c++運行時各種型內存分配(堆,棧,靜態區,數據段,BSS,ELF),BSS段, sizeof一個類求大小(字節對齊原則)、 C++四種強制類型轉換, int char float,long long long類型長度 ###指針: 防止指針的越界使用, 必須讓指針指向一個有效的內存地址, 1 防止數組越界 2 防止向一塊內存中拷貝過多的內容 3 防止使用空指針 4 防止改變const修改的指針 5 防止改變指向靜態存儲區的內容 6 防止兩次釋放一個指針 7 防止使用野指針. 什麼是指針退化及防止、 若是用一個數組做爲函數入參 好比 void fun(char a[100]) { cout<<SIZEOF(A)< } 指針的移動問題, 指針P ++具體移動的字節數等於指針指向的變量類型大小. Const,volatile修飾指針的含義, 堆和棧上的指針, 指針所指向的這塊內存是在哪裏分配的,在堆上稱爲堆上的指針,在棧上爲棧上的指針. 在堆上的指針,能夠保存在全局數據結構中,供不一樣函數使用訪問同一塊內存. 在棧上的指針,在函數退出後,該內存即不可訪問. 指針的釋放及內存泄露緣由, 指針做爲函數的參數,函數指針, 指針和引用及地址的區別,數組名, 指針與地址的區別? 區別: 1指針意味着已經有一個指針變量存在,他的值是一個地址,指針變量自己也存放在一個長度爲四個字節的地址當中,而地址概念自己並不表明有任何變量存在. 2 指針的值,若是沒有限制,一般是能夠變化的,也能夠指向另一個地址. 地址表示內存空間的一個位置點,他是用來賦給指針的,地址自己是沒有大小概念,指針指向變量的大小,取決於地址後面存放的變量類型. 指針與數組名的關係? 其值都是一個地址,但前者是能夠移動的,後者是不可變的. 指針和引用的區別(通常都會問到) 相同點:1. 都是地址的概念; 指針指向一塊內存,它的內容是所指內存的地址;引用是某塊內存的別名。 區別:1. 指針是一個實體,而引用僅是個別名; 2. 引用使用時無需解引用(*),指針須要解引用; 3. 引用只能在定義時被初始化一次,以後不可變;指針可變; 4. 引用沒有 const,指針有 const; 5. 引用不能爲空,指針能夠爲空; 6. 「sizeof 引用」獲得的是所指向的變量(對象)的大小,而「sizeof 指針」獲得的是指針自己(所指向的變量或對象的地址)的大小; 7. 指針和引用的自增(++)運算意義不同; 8.從內存分配上看:程序爲指針變量分配內存區域,而引用不須要分配內存區域。 迭代器與普通指針有什麼區別 智能指針的原理, 智能指針:實際指行爲相似於指針的類對象 ,它的一種通用實現方法是採用引用計數的方法。 1.智能指針將一個計數器與類指向的對象相關聯,引用計數跟蹤共有多少個類對象共享同一指針。 2.每次建立類的新對象時,初始化指針並將引用計數置爲1; 3.當對象做爲另外一對象的副本而建立時,拷貝構造函數拷貝指針並增長與之相應的引用計數; 4.對一個對象進行賦值時,賦值操做符減小左操做數所指對象的引用計數(若是引用計數爲減至0,則刪除對象),並增長右操做數所指對象的引用計數;這是由於左側的指針指向了右側指針所指向的對象,所以右指針所指向的對象的引用計數+1; 5.調用析構函數時,構造函數減小引用計數(若是引用計數減至0,則刪除基礎對象)。 6.實現智能指針有兩種經典策略:一是引入輔助類,二是使用句柄類。這裏主要講一下引入輔助類的方法 其餘:override和overload的區別, override(重寫) 一、方法名、參數、返回值相同。 二、子類方法不能縮小父類方法的訪問權限。 三、子類方法不能拋出比父類方法更多的異常(但子類方法能夠不拋出異常)。 四、存在於父類和子類之間。 五、方法被定義爲final不能被重寫。 overload(重載) 一、參數類型、個數、順序至少有一個不相同。 二、不能重載只有返回值不一樣的方法名。 三、存在於父類和子類、同類中。 Overload是重載的意思,Override是覆蓋的意思,也就是重寫。 重載Overload表示同一個類中能夠有多個名稱相同的方法,但這些方法的參數列表各不相同(即參數個數或類型不一樣)。 重寫Override表示子類中的方法能夠與父類中的某個方法的名稱和參數徹底相同,經過子類建立的實例對象調用這個方法時,將調用子類中的定義方法,這至關於把父類中定義的那個徹底相同的方法給覆蓋了,這也是面向對象編程的多態性的一種表現。 子類覆蓋父類的方法時,只能比父類拋出更少的異常,或者是拋出父類拋出的異常的子異常,由於子類能夠解決父類的一些問題,不能比父類有更多的問題。子類方法的訪問權限只能比父類的更大,不能更小。若是父類的方法是private類型,那麼,子類則不存在覆蓋的限制,至關於子類中增長了一個全新的方法。 寫string類的構造,析構,拷貝函數 String 類的原型以下 class String { public: String(const char *str=NULL); //構造函數 String(const String &other); //拷貝構造函數 ~String(void); //析構函數 String& operator=(const String &other); //等號操做符重載 ShowString(); private: char *m_data; //指針 }; String::~String() { delete [] m_data; //析構函數,釋放地址空間 } String::String(const char *str) { if (str==NULL)//當初始化串不存在的時候,爲m_data申請一個空間存放'\0'; { m_data=new char[1]; *m_data='\0'; } else//當初始化串存在的時候,爲m_data申請一樣大小的空間存放該串; { int length=strlen(str); m_data=new char[length+1]; strcpy(m_data,str); } } String::String(const String &other)//拷貝構造函數,功能與構造函數相似。 { int length=strlen(other.m_data); m_data=new [length+1]; strcpy(m_data,other.m_data); } String& String::operator =(const String &other) { if (this==&other)//當地址相同時,直接返回; return *this; delete [] m_data;//當地址不相同時,刪除原來申請的空間,從新開始構造; int length=sizeof(other.m_data); m_data=new [length+1]; strcpy(m_data,other.m_data); return *this; } String::ShowString()//因爲m_data是私有成員,對象只能經過public成員函數來訪問; { cout<<this->m_data<<endl; } main() { String AD; char * p="ABCDE"; String B(p); AD.ShowString(); AD=B; AD.ShowString(); } 1 指針的四要素 1指針變量,表示一個內存地址,一般爲邏輯地址,與實際的物理地址還有一個映射關係. 2指針變量的長度,在WIN32下爲四個字節, 3指針指向的變量 該內存地址空間下存放的變量,具體內容多是各類類型的變量. 4 指針指向的變量的長度,以該內存地址空間開始的內存空間大小. ##數據結構算法: 鏈表、樹、哈希表、有效避免hash結果值的碰撞 排序算法性能比較 ##操做系統: linux的內存管理機制,內存尋址方式,什麼叫虛擬內存,內存調頁算法,任務調度算法、 Linux虛擬內存的實現須要6種機制的支持:地址映射機制、內存分配回收機制、緩存和刷新機制、請求頁機制、交換機制和內存共享機制 內存管理程序經過映射機制把用戶程序的邏輯地址映射到物理地址。當用戶程序運行時,若是發現程序中要用的虛地址沒有對應的物理內存,就發出了請求頁要求。若是有空閒的內存可供分配,就請求分配內存(因而用到了內存的分配和回收),並把正在使用的物理頁記錄在緩存中(使用了緩存機制)。若是沒有足夠的內存可供分配,那麼就調用交換機制;騰出一部份內存。另外,在地址映射中要經過TLB(翻譯後援存儲器)來尋找物理頁;交換機制中也要用到交換緩存,而且把物理頁內容交換到交換文件中,也要修改頁表來映射文件地址。 進程和線程、進程間及線程通訊方式、共享內存的使用實現原理 死鎖必要條件及避免算法、 一、資源不能共享,只能由一個進程使用。 二、請求與保持(Hold andwait):已經獲得資源的進程能夠再次申請新的資源。 三、不可剝奪(Nopre-emption):已經分配的資源不能從相應的進程中被強制地剝奪。 四、循環等待:系統中若干進程組成環路,該環路中每一個進程都在等待相鄰進程正佔用的資源 處理死鎖的策略:1.忽略該問題。例如鴕鳥算法,該算法能夠應用在極少發生死鎖的的狀況下。爲何叫鴕鳥算法呢,由於傳說中鴕鳥看到危險就把頭埋在地底下,可能鴕鳥以爲看不到危險也就沒危險了吧。跟掩耳盜鈴有點像。2.檢測死鎖而且恢復。3.仔細地對資源進行動態分配,以免死鎖。4.經過破除死鎖四個必要條件之一,來防止死鎖產生。) 動態連接和靜態連接的區別、 動態連接是隻創建一個引用的接口,而真正的代碼和數據存放在另外的可執行模塊中,在運行時再裝入;而靜態連接是把全部的代碼和數據都複製到本模塊中,運行時就再也不須要庫了。 c程序辨別系統是16位or32位,大端or小端字節序、 16or32 法一:int k=~0; if((unsigned int)k >63356) cout<<"at least 32bits"<<endl; else cout<<"16 bits"<<endl; 法二://32爲系統 int i=65536; cout<<i<<endl; int j=65535; cout<<j<<endl; 大or小 1) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。 2) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。 舉一個例子,好比數字0x12 34 56 78在內存中的表示形式爲: 1)大端模式: 低地址 -----------------> 高地址 0x12 | 0x34 | 0x56 | 0x78 2)小端模式: 低地址 ------------------> 高地址 0x78 | 0x56 | 0x34 | 0x12 32bit寬的數0x12345678在Little-endian模式以及Big-endian模式)CPU內存中的存放方式(假設從地址0x4000開始存放)爲: 內存地址 小端模式存放內容 大端模式存放內容 0x4000 0x78 0x12 0x4001 0x56 0x34 0x4002 0x34 0x56 0x4003 0x12 0x78 4)大端小端沒有誰優誰劣,各自優點即是對方劣勢: 小端模式 :強制轉換數據不須要調整字節內容,一、二、4字節的存儲方式同樣。 大端模式 :符號位的斷定固定爲第一個字節,容易判斷正負。 BOOL IsBigEndian() { int a = 0x1234; char b = *(char *)&a; //經過將int強制類型轉換成char單字節,經過判斷起始存儲位置。即等於 取b等於a的低地址部分 if( b == 0x12) { return TRUE; } return FALSE; } 聯合體union的存放順序是全部成員都從低地址開始存放,利用該特性能夠輕鬆地得到了CPU對內存採用Little-endian仍是Big-endian模式讀寫: BOOL IsBigEndian() { union NUM { int a; char b; }num; num.a = 0x1234; if( num.b == 0x12 ) { return TRUE; } return FALSE; } 通常操做系統都是小端,而通信協議是大端的。 常見CPU的字節序 Big Endian : PowerPC、IBM、Sun Little Endian : x8六、DEC ARM既能夠工做在大端模式,也能夠工做在小端模式。 常見的信號、系統如何將一個信號通知到進程、 信號機制是進程之間相互傳遞消息的一種方法,信號全稱爲軟中斷信號,也有人稱做軟中斷。 進程之間能夠互相經過系統調用kill發送軟中斷信號。 SIGHUP 1 A 終端掛起或者控制進程終止 SIGINT 2 A 鍵盤中斷(如break鍵被按下) SIGQUIT 3 C 鍵盤的退出鍵被按下 SIGILL 4 C 非法指令 SIGABRT 6 C 由abort(3)發出的退出指令 SIGFPE 8 C 浮點異常 SIGKILL 9 AEF Kill信號 SIGSEGV 11 C 無效的內存引用 SIGPIPE 13 A 管道破裂: 寫一個沒有讀端口的管道 信號機制是異步的;當一個進程接收到一個信號時,它會馬上處理這個信號,而不會等待當前函數甚至當前一行代碼結束運行。信號有幾十種,分別表明着不一樣的意義。信號之間依靠它們的值來區分,可是一般在程序中使用信號的名字來表示一個信號。在Linux系統中,這些信號和以它們的名稱命名的常量均定義在/usr/include/bits/signum.h文件中。(一般程序中不須要直接包含這個頭文件,而應該包含<signal.h>。) 信號事件的發生有兩個來源:硬件來源(好比咱們按下了鍵盤或者其它硬件故障);軟件來源,最經常使用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包括一些非法運算等操做。 發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。 進程能夠經過三種方式來響應一個信號:(1)忽略信號,即對信號不作任何處理,其中,有兩個信號不能忽略:SIGKILL及SIGSTOP;(2)捕捉信號。定義信號處理函數,當信號發生時,執行相應的處理函數;(3)執行缺省操做, linux系統的各種同步機制、linux系統的各種異步機制、 如何實現守護進程 守護進程最重要的特性是後臺運行。 1. 在後臺運行。 爲避免掛起控制終端將Daemon放入後臺執行。方法是在進程中調用fork使父進程終止,讓Daemon在子進程中後臺執行。 if(pid=fork()) exit(0); //是父進程,結束父進程,子進程繼續 2. 脫離控制終端,登陸會話和進程組 有必要先介紹一下Linux中的進程與控制終端,登陸會話和進程組之間的關係:進程屬於一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登陸會話能夠包含多個進程組。這些進程組共享一個控制終端。這個控制終端一般是建立進程的登陸終端。控制終端,登陸會話和進程組一般是從父進程繼承下來的。咱們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點的基礎上,調用setsid()使進程成爲會話組長: setsid(); 說明:當進程是會話組長時setsid()調用失敗。但第一點已經保證進程不是會話組長。setsid()調用成功後,進程成爲新的會話組長和新的進程組長,並與原來的登陸會話和進程組脫離。因爲會話過程對控制終端的獨佔性,進程同時與控制終端脫離。 3. 禁止進程從新打開控制終端 如今,進程已經成爲無終端的會話組長。但它能夠從新申請打開一個控制終端。能夠經過使進程再也不成爲會話組長來禁止進程從新打開控制終端: if(pid=fork()) exit(0); //結束第一子進程,第二子進程繼續(第二子進程再也不是會話組長) 4. 關閉打開的文件描述符 進程從建立它的父進程那裏繼承了打開的文件描述符。如不關閉,將會浪費系統資源,形成進程所在的文件系統沒法卸下以及引發沒法預料的錯誤。按以下方法關閉它們: for(i=0;i 關閉打開的文件描述符close(i);> 5. 改變當前工做目錄 進程活動時,其工做目錄所在的文件系統不能卸下。通常須要將工做目錄改變到根目錄。對於須要轉儲核心,寫運行日誌的進程將工做目錄改變到特定目錄如 /tmpchdir("/") 6. 重設文件建立掩模 進程從建立它的父進程那裏繼承了文件建立掩模。它可能修改守護進程所建立的文件的存取位。爲防止這一點,將文件建立掩模清除:umask(0); 7. 處理SIGCHLD信號 處理SIGCHLD信號並非必須的。但對於某些進程,特別是服務器進程每每在請求到來時生成子進程處理請求。若是父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。若是父進程等待子進程結束,將增長父進程的負擔,影響服務器進程的併發性能。在Linux下能夠簡單地將 SIGCHLD信號的操做設爲SIG_IGN。 signal(SIGCHLD,SIG_IGN); 這樣,內核在子進程結束時不會產生殭屍進程。這一點與BSD4不一樣,BSD4下必須顯式等待子進程結束才能釋放殭屍進程。 標準庫函數和系統調用的區別, 一、系統調用 系統調用提供的函數如open, close, read, write, ioctl等,需包含頭文件unistd.h。以write爲例:其函數原型爲 size_t write(int fd, const void *buf, size_t nbytes),其操做對象爲文件描述符或文件句柄fd(file descriptor),要想寫一個文件,必須先以可寫權限用open系統調用打開一個文件,得到所打開文件的fd,例如fd=open(/"/dev/video/", O_RDWR)。fd是一個整型值,每新打開一個文件,所得到的fd爲當前最大fd加1。Linux系統默認分配了3個文件描述符值:0-standard input,1-standard output,2-standard error。 系統調用一般用於底層文件訪問(low-level file access),例如在驅動程序中對設備文件的直接訪問。 系統調用是操做系統相關的,所以通常沒有跨操做系統的可移植性。 系統調用發生在內核空間,所以若是在用戶空間的通常應用程序中使用系統調用來進行文件操做,會有用戶空間到內核空間切換的開銷。事實上,即便在用戶空間使用庫函數來對文件進行操做,由於文件老是存在於存儲介質上,所以無論是讀寫操做,都是對硬件(存儲器)的操做,都必然會引發系統調用。也就是說,庫函數對文件的操做其實是經過系統調用來實現的。例如C庫函數fwrite()就是經過write()系統調用來實現的。 這樣的話,使用庫函數也有系統調用的開銷,爲何不直接使用系統調用呢?這是由於,讀寫文件一般是大量的數據(這種大量是相對於底層驅動的系統調用所實現的數據操做單位而言),這時,使用庫函數就能夠大大減小系統調用的次數。這一結果又緣於緩衝區技術。在用戶空間和內核空間,對文件操做都使用了緩衝區,例如用fwrite寫文件,都是先將內容寫到用戶空間緩衝區,當用戶空間緩衝區滿或者寫操做結束時,纔將用戶緩衝區的內容寫到內核緩衝區,一樣的道理,當內核緩衝區滿或寫結束時纔將內核緩衝區內容寫到文件對應的硬件媒介。 二、庫函數調用 標準C庫函數提供的文件操做函數如fopen, fread, fwrite, fclose,fflush, fseek等,需包含頭文件stdio.h。以fwrite爲例,其函數原型爲size_t fwrite(const void *buffer,size_t size, size_t item_num, FILE *pf),其操做對象爲文件指針FILE *pf,要想寫一個文件,必須先以可寫權限用fopen函數打開一個文件,得到所打開文件的FILE結構指針pf,例如pf=fopen(/"~/proj/filename/",/"w/")。實際上,因爲庫函數對文件的操做最終是經過系統調用實現的,所以,每打開一個文件所得到的FILE結構指針都有一個內核空間的文件描述符fd與之對應。一樣有相應的預約義的FILE指針:stdin-standard input,stdout-standard output,stderr-standard error。 庫函數調用一般用於應用程序中對通常文件的訪問。 庫函數調用是系統無關的,所以可移植性好。 因爲庫函數調用是基於C庫的,所以也就不可能用於內核空間的驅動程序中對設備的操做 fd和PCB, 32位系統一個進程最多有多少堆內存, 五種I/O 模式, 五種I/O 模式: 【1】 阻塞I/O (Linux下的I/O操做默認是阻塞I/O,即open和socket建立的I/O都是阻塞I/O) 【2】 非阻塞 I/O (能夠經過fcntl或者open時使用O_NONBLOCK參數,將fd設置爲非阻塞的I/O) 【3】 I/O 多路複用 (I/O多路複用,一般須要非阻塞I/O配合使用) 【4】 信號驅動 I/O (SIGIO) 【5】 異步 I/O Apache 模型(Process Per Connection,簡稱PPC),TPC(ThreadPer Connection)模型,以及 select 模型和 poll 模型,epoll模型 通常來講,程序進行輸入操做有兩步: 1.等待有數據能夠讀 2.將數據從系統內核中拷貝到程序的數據區。 對於sock編程來講: 第一步: 通常來講是等待數據從網絡上傳到本地。當數據包到達的時候,數據將會從網絡層拷貝到內核的緩存中; 第二步: 是從內核中把數據拷貝到程序的數據區中。 阻塞I/O模式 //進程處於阻塞模式時,讓出CPU,進入休眠狀態 阻塞 I/O 模式是最廣泛使用的 I/O 模式。是Linux系統下缺省的IO模式。 大部分程序使用的都是阻塞模式的 I/O 。 一個套接字創建後所處於的模式就是阻塞 I/O 模式。(由於Linux系統默認的IO模式是阻塞模式) 對於一個UDP 套接字來講,數據就緒的標誌比較簡單: (1)已經收到了一整個數據報 (2)沒有收到。 而 TCP 這個概念就比較複雜,須要附加一些其餘的變量。 一個進程調用 recvfrom ,而後系統調用並不返回知道有數據報到達本地系統,而後系統將數據拷貝到進程的緩存中。(若是系統調用收到一箇中斷信號,則它的調用會被中斷) 咱們稱這個進程在調用recvfrom一直到從recvfrom返回這段時間是阻塞的。當recvfrom正常返回時,咱們的進程繼續它的操做 非阻塞模式I/O //非阻塞模式的使用並不廣泛,由於非阻塞模式會浪費大量的CPU資源。 當咱們將一個套接字設置爲非阻塞模式,咱們至關於告訴了系統內核: 「當我請求的I/O 操做不可以立刻完成,你想讓個人進程進行休眠等待的時候,不要這麼作,請立刻返回一個錯誤給我。」 咱們開始對 recvfrom 的三次調用,由於系統尚未接收到網絡數據,因此內核立刻返回一個EWOULDBLOCK的錯誤。 第四次咱們調用 recvfrom 函數,一個數據報已經到達了,內核將它拷貝到咱們的應用程序的緩衝區中,而後 recvfrom 正常返回,咱們就能夠對接收到的數據進行處理了。 當一個應用程序使用了非阻塞模式的套接字,它須要使用一個循環來不聽的測試是否一個文件描述符有數據可讀(稱作 polling(輪詢))。應用程序不停的 polling 內核來檢查是否 I/O操做已經就緒。這將是一個極浪費 CPU資源的操做。這種模式使用中不是很廣泛。 例如: 對管道的操做,最好使用非阻塞方式! I/O多路複用 //針對批量IP操做時,使用I/O多路複用,很是有好。 在使用 I/O 多路技術的時候,咱們調用select()函數和 poll()函數或epoll函數(2.6內核開始支持),在調用它們的時候阻塞,而不是咱們來調用 recvfrom(或recv)的時候阻塞。 當咱們調用 select函數阻塞的時候,select 函數等待數據報套接字進入讀就緒狀態。當select函數返回的時候,也就是套接字能夠讀取數據的時候。這時候咱們就能夠調用 recvfrom函數來將數據拷貝到咱們的程序緩衝區中。 對於單個I/O操做,和阻塞模式相比較,select()和poll()或epoll並無什麼高級的地方。 並且,在阻塞模式下只須要調用一個函數: 讀取或發送函數。 在使用了多路複用技術後,咱們須要調用兩個函數了: 先調用 select()函數或poll()函數,而後才能進行真正的讀寫。 多路複用的高級之處在於:: 它能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就能夠返回。 IO 多路技術通常在下面這些狀況中被使用: 一、當一個客戶端須要同時處理多個文件描述符的輸入輸出操做的時候(通常來講是標準的輸入輸出和網絡套接字),I/O 多路複用技術將會有機會獲得使用。 二、當程序須要同時進行多個套接字的操做的時候。 三、若是一個 TCP 服務器程序同時處理正在偵聽網絡鏈接的套接字和已經鏈接好的套接字。 四、若是一個服務器程序同時使用 TCP 和 UDP 協議。 五、若是一個服務器同時使用多種服務而且每種服務可能使用不一樣的協議(好比 inetd就是這樣的)。 異步IO模式有:: 一、信號驅動I/O模式 二、異步I/O模式 信號驅動I/O模式 //本身沒有用過。 咱們能夠使用信號,讓內核在文件描述符就緒的時候使用 SIGIO 信號來通知咱們。咱們將這種模式稱爲信號驅動 I/O 模式。 爲了在一個套接字上使用信號驅動 I/O 操做,下面這三步是所必須的。 (1)一個和 SIGIO信號的處理函數必須設定。 (2)套接字的擁有者必須被設定。通常來講是使用 fcntl 函數的 F_SETOWN 參數來 進行設定擁有者。 (3)套接字必須被容許使用異步 I/O。通常是經過調用 fcntl 函數的 F_SETFL 命令,O_ASYNC爲參數來實現。 雖然設定套接字爲異步 I/O 很是簡單,可是使用起來困難的部分是怎樣在程序中判定產生 SIGIO信號發送給套接字屬主的時候,程序處在什麼狀態。 1.UDP 套接字的 SIGIO 信號 (比較簡單) 在 UDP 協議上使用異步 I/O 很是簡單.這個信號將會在這個時候產生: 一、套接字收到了一個數據報的數據包。 二、套接字發生了異步錯誤。 當咱們在使用 UDP 套接字異步 I/O 的時候,咱們使用 recvfrom()函數來讀取數據報數據或是異步 I/O 錯誤信息。 2.TCP 套接字的 SIGIO 信號 (不會使用) 不幸的是,異步 I/O 幾乎對 TCP 套接字而言沒有什麼做用。由於對於一個 TCP 套接字來講,SIGIO 信號發生的概率過高了,因此 SIGIO 信號並不能告訴咱們究竟發生了什麼事情。 在 TCP 鏈接中, SIGIO 信號將會在這個時候產生: l 在一個監聽某個端口的套接字上成功的創建了一個新鏈接。 l 一個斷線的請求被成功的初始化。 l 一個斷線的請求成功的結束。 l 套接字的某一個通道(發送通道或是接收通道)被關閉。 l 套接字接收到新數據。 l 套接字將數據發送出去。 l 發生了一個異步 I/O 的錯誤。 一個對信號驅動 I/O 比較實用的方面是NTP(網絡時間協議 Network TimeProtocol)服務器,它使用 UDP。這個服務器的主循環用來接收從客戶端發送過來的數據報數據包,而後再發送請求。對於這個服務器來講,記錄下收到每個數據包的具體時間是很重要的。 由於那將是返回給客戶端的值,客戶端要使用這個數據來計算數據報在網絡上來回所花費的時間。圖 6-8 表示了怎樣創建這樣的一個 UDP 服務器。 異步I/O模式 //好比寫操做,只需用寫,不必定寫入磁盤(這就是異步I/O)的好處。異步IO的好處效率高。 當咱們運行在異步 I/O 模式下時,咱們若是想進行 I/O 操做,只須要告訴內核咱們要進行 I/O 操做,而後內核會立刻返回。具體的 I/O 和數據的拷貝所有由內核來完成,咱們的程序能夠繼續向下執行。當內核完成全部的 I/O 操做和數據拷貝後,內核將通知咱們的程序。 異步 I/O 和 信號驅動I/O的區別是: 一、信號驅動 I/O 模式下,內核在操做能夠被操做的時候通知給咱們的應用程序發送SIGIO 消息。 二、異步 I/O 模式下,內核在全部的操做都已經被內核操做結束以後纔會通知咱們的應用程序。 select,poll,epoll . Epoll 是何方神聖? Epoll 但是當前在 Linux 下開發大規模併發網絡程序的熱門人選, Epoll 在 Linux2.6 內核中正式引入,和 select 類似,其實都 I/O 多路複用技術而已,並無什麼神祕的。 其實在Linux 下設計併發網絡程序,向來不缺乏方法,好比典型的 Apache 模型( Process Per Connection ,簡稱PPC ), TPC ( ThreadPer Connection )模型,以及 select 模型和 poll 模型,那爲什麼還要再引入 Epoll 這個東東呢?那仍是有得說說的 … 2. 經常使用模型的缺點 若是不擺出來其餘模型的缺點,怎麼能對比出 Epoll 的優勢呢。 2.1 PPC/TPC 模型 這兩種模型思想相似,就是讓每個到來的鏈接一邊本身作事去,別再來煩我。只是 PPC 是爲它開了一個進程,而 TPC 開了一個線程。但是別煩我是有代價的,它要時間和空間啊,鏈接多了以後,那麼多的進程 / 線程切換,這開銷就上來了;所以這類模型能接受的最大鏈接數都不會高,通常在幾百個左右。 2.2 select 模型 1. 最大併發數限制,由於一個進程所打開的 FD (文件描述符)是有限制的,www.linuxidc.com 由FD_SETSIZE 設置,默認值是 1024/2048 ,所以 Select 模型的最大併發數就被相應限制了。本身改改這個 FD_SETSIZE ?想法雖好,但是先看看下面吧 … 2. 效率問題, select 每次調用都會線性掃描所有的 FD 集合,這樣效率就會呈現線性降低,把 FD_SETSIZE 改大的後果就是,你們都慢慢來,什麼?都超時了??!! 3. 內核 / 用戶空間內存拷貝問題,如何讓內核把 FD 消息通知給用戶空間呢?在這個問題上 select 採起了內存拷貝方法。 2.3 poll 模型 基本上效率和select 是相同的,select 缺點的 2 和 3 它都沒有改掉。 3. Epoll 的提高 把其餘模型逐個批判了一下,再來看看 Epoll 的改進之處吧,其實把 select 的缺點反過來那就是 Epoll 的優勢了。 3.1. Epoll 沒有最大併發鏈接的限制,上限是最大能夠打開文件的數目,這個數字通常遠大於 2048, 通常來講這個數目和系統內存關係很大,具體數目能夠 cat /proc/sys/fs/file-max 察看。 3.2. 效率提高, Epoll 最大的優勢就在於它只管你「活躍」的鏈接,而跟鏈接總數無關,所以在實際的網絡環境中, Epoll 的效率就會遠遠高於 select 和 poll 。 3.3. 內存拷貝, Epoll 在這點上使用了「共享內存 」,這個內存拷貝也省略了。 4. Epoll 爲何高效 Epoll 的高效和其數據結構的設計是密不可分的,這個下面就會提到。 首先回憶一下select 模型,當有I/O 事件到來時,select 通知應用程序有事件到了快去處理,而應用程序必須輪詢全部的 FD 集合,測試每一個 FD 是否有事件發生,並處理事件;代碼像下面這樣: int res = select(maxfd+1, &readfds,NULL, NULL, 120); if (res > 0) { for (int i = 0; i <MAX_CONNECTION; i++) { if (FD_ISSET(allConnection[i], &readfds)) { handleEvent(allConnection[i]); } } } // if(res == 0) handle timeout, res < 0handle error Epoll 不只會告訴應用程序有I/0事件到來,還會告訴應用程序相關的信息,這些信息是應用程序填充的,所以根據這些信息應用程序就能直接定位到事件,而沒必要遍歷整個FD 集合。 int res = epoll_wait(epfd, events, 20,120); for (int i = 0; i < res;i++) { handleEvent(events[n]); } 5. Epoll 關鍵數據結構 前面提到Epoll 速度快和其數據結構密不可分,其關鍵數據結構就是: struct epoll_event { __uint32_tevents; // Epoll events epoll_data_tdata; // User data variable }; typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; 可見epoll_data 是一個 union 結構體 , 藉助於它應用程序能夠保存不少類型的信息 :fd 、指針等等。有了它,應用程序就能夠直接定位目標了。 socket服務端的實現,select和epoll的區別(必問) select的本質是採用32個整數的32位,即32*32= 1024來標識,fd值爲1-1024。當fd的值超過1024限制時,就必須修改FD_SETSIZE的大小。這個時候就能夠標識32*max值範圍的fd。 對於單進程多線程,每一個線程處理多個fd的狀況,select是不適合的。 1.全部的線程均是從1-32*max進行掃描,每一個線程處理的均是一段fd值,這樣作有點浪費 2.1024上限問題,一個處理多個用戶的進程,fd值遠遠大於1024 因此這個時候應該採用poll, poll傳遞的是數組頭指針和該數組的長度,只要數組的長度不是很長,性能仍是很不錯的,由於poll一次在內核中申請4K(一個頁的大小來存放fd),儘可能控制在4K之內 epoll仍是poll的一種優化,返回後不須要對全部的fd進行遍歷,在內核中維持了fd的列表。select和poll是將這個內核列表維持在用戶態,而後傳遞到內核中。可是隻有在2.6的內核才支持。 epoll更適合於處理大量的fd ,且活躍fd不是不少的狀況,畢竟fd較多仍是一個串行的操做 epoll哪些觸發模式,有啥區別?(必須很是詳盡的解釋水平觸發和邊緣觸發的區別,以及邊緣觸發在編程中要作哪些更多的確認) epoll能夠同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變爲就緒狀態,它只說一遍,若是咱們沒有采起行動,那麼它將不會再次告知,這種方式稱爲邊緣觸發),理論上邊緣觸發的性能要更高一些,可是代碼實現至關複雜。 epoll一樣只告知那些就緒的文件描述符,並且當咱們調用epoll_wait()得到就緒文件描述符時,返回的不是實際的描述符,而是一個表明就緒描述符數量的值,你只須要去epoll指定的一個數組中依次取得相應數量的文件描述符便可,這裏也使用了內存映射(mmap)技術,這樣便完全省掉了這些文件描述符在系統調用時複製的開銷。 另外一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用必定的方法後,內核纔對全部監視的文件描述符進行掃描,而epoll事先經過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用相似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便獲得通知。 驚羣現象, 舉一個很簡單的例子,當你往一羣鴿子中間扔一塊食物,雖然最終只有一個鴿子搶到食物,但全部鴿子都會被驚動來爭奪,沒有搶到食物的鴿子只好回去繼續睡覺,等待下一塊食物到來。這樣,每扔一塊食物,都會驚動全部的鴿子,即爲驚羣。對於操做系統來講,多個進程/線程在等待同一資源是,也會產生相似的效果,其結果就是每當資源可用,全部的進程/線程都來競爭資源,形成的後果: 1)系統對用戶進程/線程頻繁的作無效的調度、上下文切換,系統系能大打折扣。 2)爲了確保只有一個線程獲得資源,用戶必須對資源操做進行加鎖保護,進一步加大了系統開銷。 什麼是驚羣 最多見的例子就是對於socket描述符的accept操做,當多個用戶進程/線程監聽在同一個端口上時,因爲實際只可能accept一次,所以就會產生驚羣現象,固然前面已經說過了,這個問題是一個古老的問題,新的操做系統內核已經解決了這一問題。 linux內核解決驚羣問題的方法 對於一些已知的驚羣問題,內核開發者增長了一個「互斥等待」選項。一個互斥等待的行爲與睡眠基本相似,主要的不一樣點在於: 1)當一個等待隊列入口有 WQ_FLAG_EXCLUSEVE 標誌置位, 它被添加到等待隊列的尾部. 沒有這個標誌的入口項, 相反, 添加到開始. 2)當 wake_up 被在一個等待隊列上調用時, 它在喚醒第一個有 WQ_FLAG_EXCLUSIVE 標誌的進程後中止。 也就是說,對於互斥等待的行爲,好比如對一個listen後的socket描述符,多線程阻塞accept時,系統內核只會喚醒全部正在等待此時間的隊列的第一個,隊列中的其餘人則繼續等待下一次事件的發生,這樣就避免的多個線程同時監聽同一個socket描述符時的驚羣問題。 塊設備和字符設備有什麼區別, (1) 字符設備:提供連續的數據流,應用程序能夠順序讀取,一般不支持隨機存取。相反,此類設備支持按字節/字符來讀寫數據。舉例來講,調制解調器是典型的字符設備。 (2) 塊設備:應用程序能夠隨機訪問設備數據,程序可自行肯定讀取數據的位置。硬盤是典型的塊設備,應用程序能夠尋址磁盤上的任何位置,並由此讀取數據。此外,數據的讀寫只能以塊(一般是512B)的倍數進行。與字符設備不一樣,塊設備並不支持基於字符的尋址。 兩種設備自己並沒用嚴格的區分,主要是字符設備和塊設備驅動程序提供的訪問接口(file I/O API)是不同的。本文主要就數據接口、訪問接口和設備註冊方法對兩種設備進行比較。 用戶態和內核態的區別 雖然用戶態下和內核態下工做的程序有不少差異,但最重要的差異就在於特權級的不一樣,即權力的不一樣。運行在用戶態下的程序不能直接訪問操做系統內核數據結構和程序, 當咱們在系統中執行一個程序時,大部分時間是運行在用戶態下的,在其須要操做系統幫助完成某些它沒有權力和能力完成的工做時就會切換到內核態, linux文件系統:inode,inode存儲了哪些東西,目錄名,文件名存在哪裏 inode包含文件的元信息,具體來講有如下內容: * 文件的字節數 * 文件擁有者的User ID * 文件的Group ID * 文件的讀、寫、執行權限 * 文件的時間戳,共有三個:ctime指inode上一次變更的時間,mtime指文件內容上一次變更的時間,atime指文件上一次打開的時間。 * 連接數,即有多少文件名指向這個inode * 文件數據block的位置 inode也會消耗硬盤空間,因此硬盤格式化的時候,操做系統自動將硬盤分紅兩個區域。一個是數據區,存放文件數據;另外一個是inode區(inode table),存放inode所包含的信息。 每一個inode節點的大小,通常是128字節或256字節。inode節點的總數,在格式化時就給定,通常是每1KB或每2KB就設置一個inode。假定在一塊1GB的硬盤中,每一個inode節點的大小爲128字節,每1KB就設置一個inode,那麼inode table的大小就會達到128MB,佔整塊硬盤的12.8%。 每一個inode都有一個號碼,操做系統用inode號碼來識別不一樣的文件。 這裏值得重複一遍,Unix/Linux系統內部不使用文件名,而使用inode號碼來識別文件。對於系統來講,文件名只是inode號碼便於識別的別稱或者綽號。 表面上,用戶經過文件名,打開文件。實際上,系統內部這個過程分紅三步:首先,系統找到這個文件名對應的inode號碼;其次,經過inode號碼,獲取inode信息;最後,根據inode信息,找到文件數據所在的block,讀出數據。 通常狀況下,文件名和inode號碼是"一一對應"關係,每一個inode號碼對應一個文件名。可是,Unix/Linux系統容許,多個文件名指向同一個inode號碼。 這意味着,能夠用不一樣的文件名訪問一樣的內容;對文件內容進行修改,會影響到全部文件名;可是,刪除一個文件名,不影響另外一個文件名的訪問。這種狀況就被稱爲"硬連接"(hard link)。 ln命令能夠建立硬連接:ln 源文件 目標文件 文件A和文件B的inode號碼雖然不同,可是文件A的內容是文件B的路徑。讀取文件A時,系統會自動將訪問者導向文件B。所以,不管打開哪個文件,最終讀取的都是文件B。這時,文件A就稱爲文件B的"軟連接"(soft link)或者"符號連接(symbolic link)。 這意味着,文件A依賴於文件B而存在,若是刪除了文件B,打開文件A就會報錯:"No such file or directory"。這是軟連接與硬連接最大的不一樣:文件A指向文件B的文件名,而不是文件B的inode號碼,文件B的inode"連接數"不會所以發生變化。 ln -s命令能夠建立軟連接。:ln -s 源文文件或目錄 目標文件或目錄 http://www.ruanyifeng.com/blog/2011/12/inode.html /proc存在哪裏(存在內存上) /proc 文件系統是一個虛擬文件系統,經過它能夠使用一種新的方法在 Linux® 內核空間和用戶空間之間進行通訊。在 /proc 文件系統中,咱們能夠將對虛擬文件的讀寫做爲與內核中實體進行通訊的一種手段,可是與普通文件不一樣的是,這些虛擬文件的內容都是動態建立的 http://www.ibm.com/developerworks/cn/linux/l-proc.html ##網絡: TCP和UDP區別、 key:TCP是一種面向鏈接的、可靠的、字節流服務 1.面向連接:TCP面向連接,面向鏈接意味着兩個使用TCP的應用(一般是一個客戶和一個服務器)在彼此交換數據以前必須經過三次握手先創建一個TCP鏈接。在一個TCP中僅有兩方彼此通訊,多播和廣播不能用於TCP。UDP是不可靠的傳輸,傳輸前不須要創建連接,能夠應用多播和廣播實現一對多的通訊。 2.可靠性:TCP提供端到端的流量控制,對收到的數據進行確認,採用超時重發,對失序的數據進行從新排序等機制保證數據通訊的可靠性。而UDP是一種不可靠的服務,接收方可能不能收到發送方的數據報。 3.TCP是一種流模式的協議,UDP是一種數據報模式的協議。進程的每一個輸出操做都正好產生一個UDP數據報,並組裝成一份待發送的IP數據報。TCP應用程序產生的全體數據與真正發送的單個IP數據報可能沒有什麼聯繫。TCP會有粘包和半包的現象。 4.效率上:速度上,通常TCP速度慢,傳輸過程當中須要對數據進行確認,超時重發,還要對數據進行排序。UDP沒有這些機制因此速度快。數據比例,TCP頭至少20個字節,UDP頭8個字節,相對效率高。組裝效率上:TCP頭至少20個字節,UDP頭8個字節,系統組裝上TCP相對慢。 5.用途上:用於TCP可靠性,http,ftp使用。而因爲UDP速度快,視頻,在線遊戲多用UDP,保證明時性 對於第三點的理解。TCP可能發送100個「包」,而接收到50個「包」,不是丟「包」了,而是每次接受的「包」都比發送的多,其實TCP並無包的概念。例如,每次發10個字節,可能讀得時候一次讀了20個字節。TCP是一種流模式的協議,在接收到的緩存中按照發送的包得順序自動按照順序拼接好,由於數據基原本自同一個主機,並且是按照順序發送過來的,TCP的緩存中存放的就是,連續的數據。感受好像是多封裝了一步比UDP。而UDP由於可能兩個不一樣的主機,給同一個主機發送,(一個端口可能收到多個應用程序的數據),或者按照TCP那樣合併數據,必然會形成數據錯誤。我以爲關鍵的緣由仍是,TCP是面向鏈接,而UDP是無鏈接的,這就致使,TCP接收的數據爲一個主機發來且有序無誤的,而UDP多是多個主機發來的無序,可能錯誤的。 TCP和UDP頭部字節定義, TCP和UDP三次握手和四次揮手狀態及消息類型, time_wait,close_wait狀態產生緣由,keepalive, TIME_WAIT:表示收到了對方的FIN報文,併發送出了ACK報文。 TIME_WAIT狀態下的TCP鏈接會等待2*MSL(Max Segment Lifetime,最大分段生存期,指一個TCP報文在Internet上的最長生存時間。每一個具體的TCP協議實現都必須選擇一個肯定的MSL值,RFC 1122建議是2分鐘,但BSD傳統實現採用了30秒,Linux能夠cat /proc/sys/net/ipv4/tcp_fin_timeout看到本機的這個值),而後便可回到CLOSED 可用狀態了。若是FIN_WAIT_1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時,能夠直接進入到TIME_WAIT狀態,而無須通過FIN_WAIT_2狀態。 若是使用了nginx代理,那麼系統TIME_WAIT的數量會變得比較多,這是因爲nginx代理使用了短連接的方式和後端交互的緣由,使得nginx和後端的ESTABLISHED變得不多而TIME_WAIT不少。這不但發生在安裝nginx的代理服務器上,並且也會使後端的app服務器上有大量的TIME_WAIT。查閱TIME_WAIT資料,發現這個狀態不少也沒什麼大問題,但可能由於它佔用了系統過多的端口,致使後續的請求沒法獲取端口而形成障礙。 雖然TIME_WAIT會形成一些問題,可是要徹底槍斃掉它也是不正當的,雖然看起來這麼作沒什麼錯。具體可看這篇文檔: http://hi.baidu.com/tim_bi/blog/item/35b005d784ca91d5a044df1d.html 因此目前看來最好的辦法是讓每一個TIME_WAIT早點過時。 在linux上能夠這麼配置: #讓TIME_WAIT狀態能夠重用,這樣即便TIME_WAIT佔滿了全部端口,也不會拒絕新的請求形成障礙 echo "1" > /proc/sys/net/ipv4/tcp_tw_reuse #讓TIME_WAIT儘快回收,我也不知是多久,觀察大概是一秒鐘 echo "1" > /proc/sys/net/ipv4/tcp_tw_recycle 不少文檔都會建議兩個參數都配置上,可是我發現只用修改tcp_tw_recycle就能夠解決問題的了,TIME_WAIT重用TCP協議自己就是不建議打開的。 不能重用端口可能會形成系統的某些服務沒法啓動,好比要重啓一個系統監控的軟件,它用了40000端口,而這個端口在軟件重啓過程當中恰好被使用了,就可能會重啓失敗的。linux默認考慮到了這個問題,有這麼個設定: #查看系統本地可用端口極限值 cat /proc/sys/net/ipv4/ip_local_port_range 用這條命令會返回兩個數字,默認是:32768 61000,說明這臺機器本地能向外鏈接61000-32768=28232個鏈接,注意是本地向外鏈接,不是這臺機器的全部鏈接,不會影響這臺機器的80端口的對外鏈接數。但這個數字會影響到代理服務器(nginx)對app服務器的最大鏈接數,由於nginx對app是用的異步傳輸,因此這個環節的鏈接速度很快,因此堆積的鏈接就不多。假如nginx對app服務器之間的帶寬出了問題或是app服務器有問題,那麼可能使鏈接堆積起來,這時能夠經過設定nginx的代理超時時間,來使鏈接儘快釋放掉,通常來講極少能用到28232個鏈接。 由於有軟件使用了40000端口監聽,經常出錯的話,能夠經過設定ip_local_port_range的最小值來解決: echo "40001 61000" > /proc/sys/net/ipv4/ip_local_port_range 可是這麼作很顯然把系統可用端口數減小了,這時能夠把ip_local_port_range的最大值往上調,可是好習慣是使用不超過32768的端口來偵聽服務,另外也沒必要要去修改ip_local_port_range數值成1024 65535之類的,意義不大。 由於使用了nginx代理,在windows下也會形成大量TIME_WAIT,固然windows也能夠調整: 在註冊表(regedit)的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters上添加一個DWORD類型的值TcpTimedWaitDelay,值就是秒數,便可。 windows默認是重用TIME_WAIT,我如今還不知道怎麼改爲不重用的,本地端口也沒查到是什麼值,但這些都關係不大,均可以按系統默認運做。 ------------------------------------------------------------------------------------------------------------------------ TIME_WAIT狀態 根據TCP協議,主動發起關閉的一方,會進入TIME_WAIT狀態,持續2*MSL(Max Segment Lifetime),缺省爲240秒,在這個post中簡潔的介紹了爲何須要這個狀態。 值得一說的是,對於基於TCP的HTTP協議,關閉TCP鏈接的是Server端,這樣,Server端會進入TIME_WAIT狀態,可想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態,假如server一秒鐘接收1000個請求,那麼就會積壓240*1000=240,000個TIME_WAIT的記錄,維護這些狀態給Server帶來負擔。固然現代操做系統都會用快速的查找算法來管理這些TIME_WAIT,因此對於新的TCP鏈接請求,判斷是否hit中一個TIME_WAIT不會太費時間,可是有這麼多狀態要維護老是很差。 HTTP協議1.1版規定default行爲是Keep-Alive,也就是會重用TCP鏈接傳輸多個request/response,一個主要緣由就是發現了這個問題。還有一個方法減緩TIME_WAIT壓力就是把系統的2*MSL時間減小,由於240秒的時間實在是忒長了點,對於Windows,修改註冊表,在HKEY_LOCAL_MACHINE\ SYSTEM\CurrentControlSet\Services\ Tcpip\Parameters上添加一個DWORD類型的值TcpTimedWaitDelay,通常認爲不要少於60,否則可能會有麻煩。 對於大型的服務,一臺server搞不定,須要一個LB(Load Balancer)把流量分配到若干後端服務器上,若是這個LB是以NAT方式工做的話,可能會帶來問題。假如全部從LB到後端Server的IP包的source address都是同樣的(LB的對內地址),那麼LB到後端Server的TCP鏈接會受限制,由於頻繁的TCP鏈接創建和關閉,會在server上留下TIME_WAIT狀態,並且這些狀態對應的remote address都是LB的,LB的source port撐死也就60000多個(2^16=65536,1~1023是保留端口,還有一些其餘端口缺省也不會用),每一個LB上的端口一旦進入Server的TIME_WAIT黑名單,就有240秒不能再用來創建和Server的鏈接,這樣LB和Server最多也就能支持300個左右的鏈接。若是沒有LB,不會有這個問題,由於這樣server看到的remote address是internet上廣闊無垠的集合,對每一個address,60000多個port實在是夠用了。 一開始我以爲用上LB會很大程度上限制TCP的鏈接數,可是實驗代表沒這回事,LB後面的一臺Windows Server 2003每秒處理請求數照樣達到了600個,難道TIME_WAIT狀態沒起做用?用Net Monitor和netstat觀察後發現,Server和LB的XXXX端口之間的鏈接進入TIME_WAIT狀態後,再來一個LB的XXXX端口的SYN包,Server照樣接收處理了,而是想像的那樣被drop掉了。翻書,從書堆裏面找出覆滿塵土的大學時代買的《UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI》,中間提到一句,對於BSD-derived實現,只要SYN的sequence number比上一次關閉時的最大sequence number還要大,那麼TIME_WAIT狀態同樣接受這個SYN,難不成Windows也算BSD-derived?有了這點線索和關鍵字(BSD),找到這個post,在NT4.0的時候,仍是和BSD-derived不同的,不過Windows Server 2003已是NT5.2了,也許有點差異了。 作個試驗,用Socket API編一個Client端,每次都Bind到本地一個端口好比2345,重複的創建TCP鏈接往一個Server發送Keep-Alive=false的HTTP請求,Windows的實現讓sequence number不斷的增加,因此雖然Server對於Client的2345端口鏈接保持TIME_WAIT狀態,可是老是可以接受新的請求,不會拒絕。那若是SYN的Sequence Number變小會怎麼樣呢?一樣用Socket API,不過此次用Raw IP,發送一個小sequence number的SYN包過去,Net Monitor裏面看到,這個SYN被Server接收後如泥牛如海,一點反應沒有,被drop掉了。 按照書上的說法,BSD-derived和Windows Server 2003的作法有安全隱患,不過至少這樣至少不會出現TIME_WAIT阻止TCP請求的問題,固然,客戶端要配合,保證不一樣TCP鏈接的sequence number要上漲不要降低。 ---------------------------------------------------------------------------------------------------------------------------- Socket中的TIME_WAIT狀態 在高併發短鏈接的server端,當server處理完client的請求後馬上closesocket此時會出現time_wait狀態而後若是client再併發2000個鏈接,此時部分鏈接就鏈接不上了,用linger強制關閉能夠解決此問題,可是linger會致使數據丟失,linger值爲0時是強制關閉,不管併發多少多能正常鏈接上,若是非0會發生部分鏈接不上的狀況!(可調用setsockopt設置套接字的linger延時標誌,同時將延時時間設置爲0。) TCP/IP的RFC文檔。TIME_WAIT是TCP鏈接斷開時一定會出現的狀態。 是沒法避免掉的,這是TCP協議實現的一部分。 在WINDOWS下,能夠修改註冊表讓這個時間變短一些 time_wait的時間爲2msl,默認爲4min. 你能夠經過改變這個變量: TcpTimedWaitDelay 把它縮短到30s TCP要保證在全部可能的狀況下使得全部的數據都可以被投遞。當你關閉一個socket時,主動關閉一端的socket將進入TIME_WAIT狀態,而被動關閉一方則轉入CLOSED狀態,這的確可以保證全部的數據都被傳輸。當一個socket關閉的時候,是經過兩端互發信息的四次握手過程完成的,當一端調用close()時,就說明本端沒有數據再要發送了。這好似看來在握手完成之後,socket就都應該處於關閉CLOSED狀態了。但這有兩個問題,首先,咱們沒有任何機制保證最後的一個ACK可以正常傳輸,第二,網絡上仍然有可能有殘餘的數據包(wandering duplicates),咱們也必須可以正常處理。 經過正確的狀態機,咱們知道雙方的關閉過程以下 圖 假設最後一個ACK丟失了,服務器會重發它發送的最後一個FIN,因此客戶端必須維持一個狀態信息,以便可以重發ACK;若是不維持這種狀態,客戶端在接收到FIN後將會響應一個RST,服務器端接收到RST後會認爲這是一個錯誤。若是TCP協議可以正常完成必要的操做而終止雙方的數據流傳輸,就必須徹底正確的傳輸四次握手的四個節,不能有任何的丟失。這就是爲何socket在關閉後,仍然處於 TIME_WAIT狀態,由於他要等待以便重發ACK。 若是目前鏈接的通訊雙方都已經調用了close(),假定雙方都到達CLOSED狀態,而沒有TIME_WAIT狀態時,就會出現以下的狀況。如今有一個新的鏈接被創建起來,使用的IP地址與端口與先前的徹底相同,後創建的鏈接又稱做是原先鏈接的一個化身。還假定原先的鏈接中有數據報殘存於網絡之中,這樣新的鏈接收到的數據報中有多是先前鏈接的數據報。爲了防止這一點,TCP不容許從處於TIME_WAIT狀態的socket創建一個鏈接。處於TIME_WAIT狀態的socket在等待兩倍的MSL時間之後(之因此是兩倍的MSL,是因爲MSL是一個數據報在網絡中單向發出到認定丟失的時間,一個數據報有可能在發送圖中或是其響應過程當中成爲殘餘數據報,確認一個數據報及其響應的丟棄的須要兩倍的MSL),將會轉變爲CLOSED狀態。這就意味着,一個成功創建的鏈接,必然使得先前網絡中殘餘的數據報都丟失了。 因爲TIME_WAIT狀態所帶來的相關問題,咱們能夠經過設置SO_LINGER標誌來避免socket進入TIME_WAIT狀態,這能夠經過發送RST而取代正常的TCP四次握手的終止方式。但這並非一個很好的主意,TIME_WAIT對於咱們來講每每是有利的。 客戶端與服務器端創建TCP/IP鏈接後關閉SOCKET後,服務器端鏈接的端口 狀態爲TIME_WAIT 是否是全部執行主動關閉的socket都會進入TIME_WAIT狀態呢? 有沒有什麼狀況使主動關閉的socket直接進入CLOSED狀態呢? 主動關閉的一方在發送最後一個 ack 後 就會進入 TIME_WAIT 狀態 停留2MSL(max segment lifetime)時間 這個是TCP/IP必不可少的,也就是「解決」不了的。 也就是TCP/IP設計者原本是這麼設計的 主要有兩個緣由 1。防止上一次鏈接中的包,迷路後從新出現,影響新鏈接 (通過2MSL,上一次鏈接中全部的重複包都會消失) 2。可靠的關閉TCP鏈接 在主動關閉方發送的最後一個 ack(fin) ,有可能丟失,這時被動方會從新發 fin, 若是這時主動方處於 CLOSED 狀態 ,就會響應 rst 而不是 ack。因此 主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED 。 TIME_WAIT 並不會佔用很大資源的,除非受到攻擊。 還有,若是一方 send 或 recv 超時,就會直接進入 CLOSED 狀態 socket-faq中的這一段講的也很好,摘錄以下: 2.7. Please explain the TIME_WAIT state. 什麼是滑動窗口,超時重傳, 列舉你所知道的tcp選項, connect會阻塞檢測及防止,socket什麼狀況下可讀? connect會阻塞,怎麼解決?(必考必問) 最一般的方法最有效的是加定時器;也能夠採用非阻塞模式。 設置非阻塞,返回以後用select檢測狀態) 若是select返回可讀,結果只讀到0字節,什麼狀況? 某個套接字集合中沒有準備好,可能會select內存用FD_CLR清該位爲0; socket什麼狀況下可讀? 每次讀操做返回前都要檢查是否還有剩餘數據沒讀完,若是是的話保持數據有效標誌,不這樣設計的話會出現明顯的不一致,那就是數據在讀緩衝但沒有讀有效標誌。 keepalive是什麼東東?如何使用? 設置Keepalive參數,檢測已中斷的客戶鏈接 在TCP中有一個Keep-alive的機制能夠檢測死鏈接,原理很簡單,TCP會在空閒了必定時間後發送數據給對方: 1.若是主機可達,對方就會響應ACK應答,就認爲是存活的。 2.若是可達,但應用程序退出,對方就發RST應答,發送TCP撤消鏈接。 3.若是可達,但應用程序崩潰,對方就發FIN消息。 4.若是對方主機不響應ack, rst,繼續發送直到超時,就撤消鏈接。這個時間就是默認 的二個小時。 UDP中使用connect的好處: 1:會提高效率.前面已經描述了.2:高併發服務中會增長系統穩定性.緣由:假設client A 經過非connect的UDP與serverB,C通訊.B,C提供相同服務.爲了負載均衡,咱們讓A與B,C交替通訊.A 與 B通訊IPa:PORTa<----> IPb:PORTbA 與 C通訊IPa:PORTa'<---->IPc:PORTc 假設PORTa 與 PORTa'相同了(在大併發狀況下會發生這種狀況),那麼就有可能出現A等待B的報文,卻收到了C的報文.致使收報錯誤.解決方法內就是採用connect的UDP通訊方式.在A中建立兩個udp,而後分別connect到B,C. 長鏈接和短鏈接, DNS和HTTP協議,HTTP請求方式, cookie,session,localstroage, 一致性哈希負載均衡, 描述在瀏覽器中敲入一個網址並按下回車後所發生的事情, PING命令 ping命令所利用的原理是這樣的:網絡上的機器都有惟一肯定的IP地址,咱們給目標IP地址發送一個數據包,對方就要返回一個一樣大小的數據包,根據返回的數據包咱們能夠肯定目標主機的存在,能夠初步判斷目標主機的操做系統等。 ##數據庫: 談談你對數據庫中索引的理解,索引和主鍵區別 - 彙集索引一個表只能有一個,而非彙集索引一個表能夠存在多個。 - 彙集索引存儲記錄是物理上連續存在,而非彙集索引是邏輯上的連續,物理存儲並不連續。 彙集索引:該索引中鍵值的邏輯順序決定了表中相應行的物理順序。 彙集索引肯定表中數據的物理順序。彙集索引相似於電話簿,後者按姓氏排列數據。因爲彙集索引規定數據在表中的物理存儲順序,所以一個表只能包含一個彙集索引。但該索引能夠包含多個列(組合索引),就像電話簿按姓氏和名字進行組織同樣。 彙集索引使用注意事項 定義彙集索引鍵時使用的列越少越好。 • 包含大量非重複值的列。 .• 使用下列運算符返回一個範圍值的查詢:BETWEEN、>、>=、< 和 <=。 • 被連續訪問的列。 • 回大型結果集的查詢。 • 常常被使用聯接或 GROUP BY 子句的查詢訪問的列;通常來講,這些是外鍵列。對 ORDER BY 或 GROUP BY 子句中指定的列進行索引,能夠使 SQL Server 沒必要對數據進行排序,由於這些行已經排序。這樣能夠提升查詢性能。 • OLTP 類型的應用程序,這些程序要求進行很是快速的單行查找(通常經過主鍵)。應在主鍵上建立彙集索引。 彙集索引不適用於: • 頻繁更改的列 。這將致使整行移動(由於 SQL Server 必須按物理順序保留行中的數據值)。這一點要特別注意,由於在大數據量事務處理系統中數據是易失的。 • 寬鍵 。來自彙集索引的鍵值由全部非彙集索引做爲查找鍵使用,所以存儲在每一個非彙集索引的葉條目內。 非彙集索引:數據存儲在一個地方,索引存儲在另外一個地方,索引帶有指針指向數據的存儲位置。 非彙集索引中的項目按索引鍵值的順序存儲,而表中的信息按另外一種順序存儲(這能夠由彙集索引規定)。對於非彙集索引,能夠爲在表非彙集索引中查找數據時經常使用的每一個列建立一個非彙集索引。有些書籍包含多個索引。例如,一本介紹園藝的書可能會包含一個植物通俗名稱索引,和一個植物學名索引,由於這是讀者查找信息的兩種最經常使用的方法。 一個通俗的舉例,說明二者的區別 其實,咱們的漢語字典的正文自己就是一個彙集索引。好比,咱們要查「安」字,就會很天然地翻開字典的前幾頁,由於「安」的拼音是「an」,而按照拼音排序漢字的字典是以英文字母「a」開頭並以「z」結尾的,那麼「安」字就天然地排在字典的前部。若是您翻完了全部以「a」開頭的部分仍然找不到這個字,那麼就說明您的字典中沒有這個字;一樣的,若是查「張」字,那您也會將您的字典翻到最後部分,由於「張」的拼音是「zhang」。也就是說,字典的正文部分自己就是一個目錄,您不須要再去查其餘目錄來找到您須要找的內容。咱們把這種正文內容自己就是一種按照必定規則排列的目錄稱爲「彙集索引」。 若是您認識某個字,您能夠快速地從自動中查到這個字。但您也可能會遇到您不認識的字,不知道它的發音,這時候,您就不能按照剛纔的方法找到您要查的字,而須要去根據「偏旁部首」查到您要找的字,而後根據這個字後的頁碼直接翻到某頁來找到您要找的字。但您結合「部首目錄」和「檢字表」而查到的字的排序並非真正的正文的排序方法,好比您查「張」字,咱們能夠看到在查部首以後的檢字表中「張」的頁碼是672頁,檢字表中「張」的上面是「馳」字,但頁碼倒是63頁,「張」的下面是「弩」字,頁面是390頁。很顯然,這些字並非真正的分別位於「張」字的上下方,如今您看到的連續的「馳、張、弩」三字實際上就是他們在非彙集索引中的排序,是字典正文中的字在非彙集索引中的映射。咱們能夠經過這種方式來找到您所須要的字,但它須要兩個過程,先找到目錄中的結果,而後再翻到您所須要的頁碼。咱們把這種目錄純粹是目錄,正文純粹是正文的排序方式稱爲「非彙集索引」。 第一:彙集索引的約束是惟一性,是否要求字段也是惟一的呢? 分析:若是認爲是的朋友,多是受系統默認設置的影響,通常咱們指定一個表的主鍵,若是這個表以前沒有彙集索引,同時創建主鍵時候沒有強制指定使用非彙集索引,SQL會默認在此字段上建立一個彙集索引,而主鍵都是惟一的,因此理所固然的認爲建立彙集索引的字段也須要惟一。 結論:彙集索引能夠建立在任何一列你想建立的字段上,這是從理論上講,實際狀況並不能隨便指定,不然在性能上會是惡夢。 第二:爲何彙集索引能夠建立在任何一列上,若是此表沒有主鍵約束,即有可能存在重複行數據呢? 粗一看,這還真是和彙集索引的約束相背,但實際狀況真能夠建立彙集索引。 分析其緣由是:若是未使用 UNIQUE 屬性建立彙集索引,數據庫引擎將向表自動添加一個四字節 uniqueifier 列。必要時,數據庫引擎 將向行自動添加一個 uniqueifier 值,使每一個鍵惟一。此列和列值供內部使用,用戶不能查看或訪問。 第三:是否是彙集索引就必定要比非彙集索引性能優呢? 若是想查詢學分在60-90之間的學生的學分以及姓名,在學分上建立彙集索引是不是最優的呢? 答:否。既然只輸出兩列,咱們能夠在學分以及學生姓名上建立聯合非彙集索引,此時的索引就造成了覆蓋索引,即索引所存儲的內容就是最終輸出的數據,這種索引在比以學分爲彙集索引作查詢性能更好。 第四:在數據庫中經過什麼描述彙集索引與非彙集索引的? 索引是經過二叉樹的形式進行描述的,咱們能夠這樣區分彙集與非彙集索引的區別:彙集索引的葉節點就是最終的數據節點,而非彙集索引的葉節仍然是索引節點,但它有一個指向最終數據的指針。 第五:在主鍵是建立彙集索引的表在數據插入上爲何比主鍵上建立非彙集索引錶速度要慢? 有了上面第四點的認識,咱們分析這個問題就有把握了,在有主鍵的表中插入數據行,因爲有主鍵惟一性的約束,因此須要保證插入的數據沒有重複。咱們來比較下主鍵爲彙集索引和非彙集索引的查找狀況:彙集索引因爲索引葉節點就是數據頁,因此若是想檢查主鍵的惟一性,須要遍歷全部數據節點才行,但非彙集索引不一樣,因爲非彙集索引上已經包含了主鍵值,因此查找主鍵惟一性,只須要遍歷全部的索引頁就行,這比遍歷全部數據行減小了很多IO消耗。這就是爲何主鍵上建立非彙集索引比主鍵上建立彙集索引在插入數據時要快的真正緣由。 如今普通關係數據庫用得數據結構是什麼類型的數據結構, B+樹 [MySQL索引背後的數據結構及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) 索引的優勢和缺點, 創建索引的優勢 1.大大加快數據的檢索速度; 2.建立惟一性索引,保證數據庫表中每一行數據的惟一性; 3.加速表和表之間的鏈接; 4.在使用分組和排序子句進行數據檢索時,能夠顯著減小查詢中分組和排序的時間。 索引的缺點 1.索引須要佔物理空間。 2.當對錶中的數據進行增長、刪除和修改的時候,索引也要動態的維護,下降了數據的維護速度。 惟一索引 惟一索引是不容許其中任何兩行具備相同索引值的索引。 當現有數據中存在重複的鍵值時,大多數數據庫不容許將新建立的惟一索引與表一塊兒保存。數據庫還可能防止添加將在表中建立重複鍵值的新數據。例如,若是在 employee 表中職員的姓 (lname) 上建立了惟一索引,則任何兩個員工都不能同姓。 主鍵索引 數據庫表常常有一列或列組合,其值惟一標識表中的每一行。該列稱爲表的主鍵。 在數據庫關係圖中爲表定義主鍵將自動建立主鍵索引,主鍵索引是惟一索引的特定類型。該索引要求主鍵中的每一個值都惟一。當在查詢中使用主鍵索引時,它還容許對數據的快速訪問。 彙集索引 在彙集索引中,表中行的物理順序與鍵值的邏輯(索引)順序相同。一個表只能包含一個彙集索引。 若是某索引不是彙集索引,則表中行的物理順序與鍵值的邏輯順序不匹配。與非彙集索引相比,彙集索引一般提供更快的數據訪問速度。 關係型數據庫和非關係數據庫的特色, 簡單來講,關係模型指的就是二維表格模型,而一個關係型數據庫就是由二維表及其之間的聯繫所組成的一個數據組織。 非關係型數據庫提出另外一種理念,例如,以鍵值對存儲,且結構不固定,每個元組能夠有不同的字段,每一個元組能夠根據須要增長一些本身的鍵值對,這 樣就不會侷限於固定的結構,能夠減小一些時間和空間的開銷。使用這種方式,用戶能夠根據須要去添加本身須要的字段,這樣,爲了獲取用戶的不一樣信息,不須要 像關係型數據庫中,要對多表進行關聯查詢。僅須要根據id取出相應的value就能夠完成查詢。但非關係型數據庫因爲不多的約束,他也不可以提供像SQL 所提供的where這種對於字段屬性值狀況的查詢。而且難以體現設計的完整性。他只適合存儲一些較爲簡單的數據,對於須要進行較複雜查詢的數據,SQL數 據庫顯的更爲合適。 關係型數據庫的最大特色就是事務的一致性:傳統的關係型數據庫讀寫操做都是事務的,具備ACID的特色,這個特性使得關係型數據庫能夠用於幾乎全部對一致性有要求的系統中,如典型的銀行系統。 可是,在網頁應用中,尤爲是SNS應用中,一致性卻不是顯得那麼重要,用戶A看到的內容和用戶B看到同一用戶C內容更新不一致是能夠容忍的,或者 說,兩我的看到同一好友的數據更新的時間差那麼幾秒是能夠容忍的,所以,關係型數據庫的最大特色在這裏已經無用武之地,起碼不是那麼重要了。 相反地,關係型數據庫爲了維護一致性所付出的巨大代價就是其讀寫性能比較差,而像微博、facebook這類SNS的應用,對併發讀寫能力要求極 高,關係型數據庫已經沒法應付(在讀方面,傳統上爲了克服關係型數據庫缺陷,提升性能,都是增長一級memcache來靜態化網頁,而在SNS中,變化太 快,memchache已經無能爲力了),所以,必須用新的一種數據結構存儲來代替關係數據庫。 關係數據庫的另外一個特色就是其具備固定的表結構,所以,其擴展性極差,而在SNS中,系統的升級,功能的增長,每每意味着數據結構巨大變更,這一點關係型數據庫也難以應付,須要新的結構化數據存儲。 因而,非關係型數據庫應運而生,因爲不可能用一種數據結構化存儲應付全部的新的需求,所以,非關係型數據庫嚴格上不是一種數據庫,應該是一種數據結構化存儲方法的集合。 必須強調的是,數據的持久存儲,尤爲是海量數據的持久存儲,仍是須要一種關係數據庫這員老將。 非關係型數據庫分類: 主要分爲如下幾類: 面向高性能併發讀寫的key-value數據庫: key-value數據庫的主要特色即便具備極高的併發讀寫性能,Redis,Tokyo Cabinet,Flare就是這類的表明 面向海量數據訪問的面向文檔數據庫: 這類數據庫的特色是,能夠在海量的數據中快速的查詢數據,典型表明爲MongoDB以及CouchDB 面向可擴展性的分佈式數據庫: 這類數據庫想解決的問題就是傳統數據庫存在可擴展性上的缺陷,這類數據庫能夠適應數據量的增長以及數據結構的變化 [關係型數據庫和非關係型數據庫](http://my.oschina.net/u/1773689/blog/364548) 樂觀鎖與悲觀鎖的區別, 悲觀鎖:假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做。[1] 悲觀鎖假定其餘用戶企圖訪問或者改變你正在訪問、更改的對象的機率是很高的,所以在悲觀鎖的環境中,在你開始改變此對象以前就將該對象鎖住,而且直到你提交了所做的更改以後才釋放鎖。悲觀的缺陷是不管是頁鎖仍是行鎖,加鎖的時間可能會很長,這樣可能會長時間的限制其餘用戶的訪問,也就是說悲觀鎖的併發訪問性很差。 樂觀鎖:假設不會發生併發衝突,只在提交操做時檢查是否違反數據完整性。[1] 樂觀鎖不能解決髒讀的問題。 樂觀鎖則認爲其餘用戶企圖改變你正在更改的對象的機率是很小的,所以樂觀鎖直到你準備提交所做的更改時纔將對象鎖住,當你讀取以及改變該對象時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖能夠用較大的鎖粒度得到較好的併發訪問性能。可是若是第二個用戶剛好在第一個用戶提交更改以前讀取了該對象,那麼當他完成了本身的更改進行提交時,數據庫就會發現該對象已經變化了,這樣,第二個用戶不得不從新讀取該對象並做出更改。這說明在樂觀鎖環境中,會增長併發用戶讀取對象的次數。 從數據庫廠商的角度看,使用樂觀的頁鎖是比較好的,尤爲在影響不少行的批量操做中能夠放比較少的鎖,從而下降對資源的需求提升數據庫的性能。再考慮彙集索引。在數據庫中記錄是按照彙集索引的物理順序存放的。若是使用頁鎖,當兩個用戶同時訪問更改位於同一數據頁上的相鄰兩行時,其中一個用戶必須等待另外一個用戶釋放鎖,這會明顯地下降系統的性能。interbase和大多數關係數據庫同樣,採用的是樂觀鎖,並且讀鎖是共享的,寫鎖是排他的。能夠在一個讀鎖上再放置讀鎖,但不能再放置寫鎖;你不能在寫鎖上再放置任何鎖。鎖是目前解決多用戶併發訪問的有效手段。 [樂觀鎖與悲觀鎖的區別](http://www.cnblogs.com/Bob-FD/p/3352216.html) 數據庫範式 1NF的定義爲:符合1NF的關係中的每一個屬性都不可再分,1NF是全部關係型數據庫的最基本要求, 2NF在1NF的基礎之上,消除了非主屬性對於碼的部分函數依賴。 3NF在2NF的基礎之上,消除了非主屬性對於碼的傳遞函數依賴。也就是說, 若是存在非主屬性對於碼的傳遞函數依賴,則不符合3NF的要求。 BCNF範式在 3NF 的基礎上消除主屬性對於碼的部分與傳遞函數依賴。 [解釋一下關係數據庫的第一第二第三範式?](http://www.zhihu.com/question/24696366) 數據庫日誌類型做用 MySQL日誌文件分類 1.錯誤日誌(Error Log) 2.二進制日誌(Binary Log & Binary Log Index) 3.通用查詢日誌(query log) 4.慢查詢日誌(slow query log) 5.Innodb的在線 redo 日誌(innodb redo log) 6.更新日誌(update log) innodb和myisam的區別 innodb,彙集索引,支持外鍵和事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant)),不支持全文索引,不支持計數,統計的時候會遍歷, myisam,非彙集索引,不支持外鍵事務,支持全文索引,支持計數,查詢效果較好 你對innodb哪裏最熟悉 innodb的索引有哪幾種類型 4種,hash,b-tree,spatial,full-text 1. B-Tree索引 最多見的索引類型,基於B-Tree數據結構。B-Tree的基本思想是,全部值(被索引的列)都是排過序的,每一個葉節點到跟節點距離相等。因此B-Tree適合用來查找某一範圍內的數據,並且能夠直接支持數據排序(ORDER BY)。可是當索引多列時,列的順序特別重要,須要格外注意。InnoDB和MyISAM都支持B-Tree索引。InnoDB用的是一個變種B+Tree,而MyISAM爲了節省空間對索引進行了壓縮,從而犧牲了性能。 2. Hash索引 基於hash表。因此這種索引只支持精確查找,不支持範圍查找,不支持排序。這意味着範圍查找或ORDER BY都要依賴server層的額外工做。目前只有Memory引擎支持顯式的hash索引(可是它的hash是nonunique的,衝突太多時也會影響查找性能)。Memory引擎默認的索引類型便是Hash索引,雖然它也支持B-Tree索引。 Hash 索引僅僅能知足"=","IN"和"<=>"查詢,不能使用範圍查詢。 例子: CREATE TABLE testhash ( fname VARCHAR(50) NOT NULL, lname VARCHAR(50) NOT NULL, KEY USING HASH(fname) ) ENGINE =MEMORY; 3. Spatial (R-Tree)(空間)索引 只有MyISAM引擎支持,而且支持的很差。能夠忽略。 4. Full-text索引 主要用來查找文本中的關鍵字,而不是直接與索引中的值相比較。Full-text索引跟其它索引大不相同,它更像是一個搜索引擎,而不是簡單的WHERE語句的參數匹配。你能夠對某列分別進行full-text索引和B-Tree索引,二者互不衝突。Full-text索引配合MATCH AGAINST操做使用,而不是通常的WHERE語句加LIKE。 http://segmentfault.com/q/1010000003832312 B TREE 和B+TREE的區別 innodb有全文索引嗎 沒有,myisam有 union和join JOIN用於按照ON條件聯接兩個表,主要有四種: INNER JOIN:內部聯接兩個表中的記錄,僅當至少有一個同屬於兩表的行符合聯接條件時,內聯接才返回行。我理解的是隻要記錄不符合ON條件,就不會顯示在結果集內。 LEFT JOIN / LEFT OUTER JOIN:外部聯接兩個表中的記錄,幷包含左表中的所有記錄。若是左表的某記錄在右表中沒有匹配記錄,則在相關聯的結果集中右表的全部選擇列表列均爲空值。理解爲即便不符合ON條件,左表中的記錄也所有顯示出來,且結果集中該類記錄的右表字段爲空值。 RIGHT JOIN / RIGHT OUTER JOIN:外部聯接兩個表中的記錄,幷包含右表中的所有記錄。簡單說就是和LEFT JOIN反過來。 FULL JOIN / FULL OUTER JOIN: 完整外部聯接返回左表和右表中的全部行。就是LEFT JOIN和RIGHT JOIN和合並,左右兩表的數據都所有顯示。 JOIN的基本語法: Select table1.* FROM table1 JOIN table2 ON table1.id=table2.id UNION運算符 將兩個或更多查詢的結果集組合爲單個結果集,該結果集包含聯合查詢中的全部查詢的所有行。UNION的結果集列名與UNION運算符中第一個Select語句的結果集的列名相同。另外一個Select語句的結果集列名將被忽略。 其中兩種不一樣的用法是UNION和UNION ALL,區別在於UNION從結果集中刪除重複的行。若是使用UNION ALL 將包含全部行而且將不刪除重複的行。 相同點:在某些特定的狀況下,能夠用join實現union all的功能,這種狀況是有條件的,當出現這種狀況的時候選擇union all仍是group by就能夠看狀況或者看二者的消耗而決定。 http://chengheng1984.blog.163.com/blog/static/17947412201012215738844/ http://www.51testing.com/html/14/446214-249265.html ##海量數據處理: bitmap Map-Reduce原理, BloomFilter原理、 它其實是一個很長的二進制向量和一系列隨機映射函數(Hash函數)。布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。Bloom Filter普遍的應用於各類須要查詢的場合中,如Orocle的數據庫,Google的BitTable也用了此技術。 Bloom Filter特色: 不存在漏報(False Negative),即某個元素在某個集合中,確定能報出來。 可能存在誤報(False Positive),即某個元素不在某個集合中,可能也被爆出來。 肯定某個元素是否在某個集合中的代價和總的元素數目無關。 Trie樹原理、 單詞搜索樹 B+樹原理, LSM樹原理, 大數據處理 ##工具: 編譯工具GCC,調試工具GDB,性能優化工具Perf,內存泄露檢查工具Valgrind,makefile編寫 其餘工具: netstat,ps,top,df,fdisk,lsof,ifconfig,uname,kill,tcpdump,ipcs,grep ##其餘:安全,加密方式(DES,SHA) 附:[軟件開發者面試百問](http://blog.csdn.net/programmer_editor/article/details/4004408) #相關書籍 語言類: C:C程序設計語言(K&R)->C和指針->C專家編程->C陷阱與缺陷->你必須知道的495個C語言問題 C++: C++ primer -> effective C++->深度探索C++對象模型 ->stl源碼分析->C++必知必會 java:java編程思想->java併發編程->深刻理解Java虛擬機:JVM高級特性與最佳實踐 算法和數據結構: 算法導論->數據結構與算法分析(維斯)->編程之美->劍指offer 操做系統: 深刻理解計算機操做系統->編譯原理(龍書) 鳥哥的linux私房菜->linux內核設計與實現->深刻理解linux內核 linux shell腳本攻略(短小精悍) 網絡編程: TCP/IP協議詳解v1-> unix高級環境編程-> unix網絡編程(卷1&卷2)-> unix編程藝術(進階) 視野: 大型網站技術架構:核心原理與案例分析, 深刻理解nginx:模塊開發與架構解析, 大規模分佈式存儲系統 : 原理解析與架構實戰 其餘: 程序員自我修養, 重構, 編寫可讀代碼的藝術, headfirst設計模式 #相關網絡資源 Coolshell:http://coolshell.cn/ Matrix67大牛的博客:http://www.matrix67.com/blog/。 July的CSDN博客:http://blog.csdn.net/v_JULY_v。 何海濤博客:http://zhedahht.blog.163.com/。 筆試面試的經典:Cracking the coding interview--問題與解答: http://hawstein.com/posts/ctci-solutions-contents.html LeetCode:http://leetcode.com/ 這裏有很多筆試題集錦:http://blog.csdn.net/hackbuteer1 程序員編程藝術:面試和算法心得http://taop.marchtea.com/