上週是硬件,本週終於來到軟件領域,明確的欠一個賬,文件系統這塊由於東西比較多,我還沒徹底總結好,先欠着~java
本週,讓咱們作一些準備,來談談映射。計算機就是個分型的系統,而映射這種數據結構,是計算機中很是基礎和常見的一種數據結構,從cpu到文件存儲,再到分佈式文件存儲,其核心都是映射。算法
抄書:映射就是:使得對A中的每一個元素a,按法則f,在B中有惟一肯定的元素b與之對應,則稱f爲從A到B的映射,記做f:A→B。哈哈,數學上的定義是最清晰和明確的。也能夠寫做y=f(x)sql
舉個例子:給定一個映射M,令1–>a,2–>c,3->d。求3所對應的元素的時候,M應該返回的元素是d。數據庫
爲何在準備章節,須要先理解映射呢?天然是由於用的地方太多了--。。數組
當你但願從文件系統中找到標記爲ADEADDAAD12AS的塊所對應的數據的時候,你須要用到映射。網絡
當你寫的程序須要用到方法指針的時候,你須要用到映射。數據結構
當你從網絡中獲取了數據的時候,你須要用到映射。架構
當你須要從數據庫內取出一條記錄的時候,你須要用到映射。nosql
甚至當你敲擊鍵盤的時候,你也同樣須要用到映射,鍵盤會將你的按鍵映射到計算機內的一種電信號。分佈式
天然而然的,在咱們的海量數據處理中,映射也是整個體系中最爲重要的核心組成部分,不管是RDBMS,圖數據庫,仍是NoSQL引擎,他們底層的核心都是映射,而一個通過仔細優化的映射實現將可以直接的決定咱們存儲實現的全部技術特性。
怎樣?可以感覺到映射的重要性了麼?既然這麼重要還不趕忙往下看?!~
想實現這樣的映射關係,在計算機程序裏已經有了現成的方式了,那就是一個方法(method)
intget(intkey){}
這就是一個最簡單的方法了。「get」這就是方法的名字(能夠對應函數下標),intkey是入參(能夠對應函數輸入條件x),返回值是個int,也就是y。
下面咱們舉一個例子,來實際的感覺一下如何實現一套高效的映射。
假設咱們須要找到一系列的函數實現,可以使得2->4,1->2,4–>8,3->6,這樣,當我給定key=1的時候,這個函數應該返回2,而給定key=2的是時候,函數應該返回4。咱們來看看,在計算機領域有哪些比較經常使用的方式和方法。
1.使用:
這也應該是最容易想到的一種存放映射關係的方法了吧。在數據結構中,有不少種結構體能夠支持這樣的一個容器,好比使用數組,鏈表,二叉樹,均可以使用相似的方式來存放這些數據。他們的特徵各不相同,這也是我在這一章的後面要介紹的結構體中的重要主題。這裏,爲了幫助理解,咱們以數組做爲最簡單的實現樣例吧。
咱們可讓數組裏的每個元素都是一個key->valuepair。以下圖所示
2->4 |
1->2 |
4->8 |
3->6 |
這樣,當我要尋找key=2所對應的數據的時候,只須要遍歷這個數組,找到key=2的元素,而後返回這個元素的value=4就好了。
這種方式的適應性是最好的,由於不須要理解這些元素的內在聯繫。不過也有代價,就是須要付出不少的空間成本。
2.定義一個函數
對於2->4,1->2,4–>8,3->6這樣的兩組數據之間的對應關係,咱們能夠用一個算法f(x)=2x.x屬於1~4
用上面的算法,就能夠表示上面的這個映射的關係了。使用這種方式的好處不少,好比空間節省,複雜度比較低,可是他有一個比較大的劣勢,就是否是全部的數據均可以很容易的表示爲上面的那種方式,由於須要理解元素之間內在的聯繫,而且這些元素自己必須有規律可以被認識才行。
3.寫一段窮舉的算法
咱們仍然使用1->2,2->4,3->6,4–>8這樣的兩組數據之間的對應關係。
使用如下算法:
f(x) = { if(x == 1) return 2; else if(x == 2) return 4; else if(x == 3) return 6; else if (x == 4) return 8; else throw exception; }
使用這種算法,能夠針對一些特殊狀況進行特殊的處理,不過主要的代價是。。每次增長一個函數都須要咱去寫代碼兒。。這事兒就有點兒二了。。。哈哈
在這三類中,後面這兩類不大適合在咱們的數據存儲領域內使用,因此,咱們將映射的概念進行一下收縮,後面的主要篇幅,就來介紹一下集合類。
咱們還以剛纔的例子來作分析。
假設咱們須要找到一系列的函數實現,可以使得2->4,1->2,4–>8,3->6,這樣,當我給定key=1的時候,這個函數應該返回2,而給定key=2的是時候,函數應該返回4。
在開始的時候,咱們只採用了簡單的數組結構,讓數組裏的每個元素都是一個key->valuepair。以下圖所示
2->4 |
1->2 |
4->8 |
3->6 |
在查詢的時候,咱們選擇的方式是遍歷整個數組,找到要求的key後,返回這個key對應的value。
這種方式雖然可以正確的返回數據,可是,效率明顯是過低了。好比,若是這個數組的寫入了100W的數據,那麼若是要找到一個要求的數據,在最壞狀況下,須要遍歷整個數組才能取到全部數據。效率過低了這。。
因而,就天然而然的有個需求:可否找到更快的方式來查找數據呢?
在數據查找領域,核心的算法就倆,一個麼叫二分查找,時間複雜度O(log2N),一個麼就是hash..O(1)
二分查找的核心要求主要有仨:
1.數據必須有序。
2.能夠快速的從數據中找到指定位置的數據。
3.能夠獲知數據總個數
爲了查找效率可以到達log2N,咱們來看看,若是要查找到key=3這個數據,具體如何操做。
咱們先對數據排序
1->2 |
2->4 |
3->6 |
4->8 |
而後,就能夠進行折半查找了。
另一種方案就是hash
主要策略實際上是利用hash函數對原始數據作一次預先的計算映射。
好比我能夠選擇一個hash函數key%3
每次插入數據的時候,都先算一下hash函數後,才插入到一個size是3的數組裏面
3->6 |
1->2 |
2->4 |
這幾個數字相對的比較好處理,可是4->8這個數據怎麼辦呢?
若是這個數據計算一下hash函數key%3=4%3=1,這個數據應該也被寫入到位置爲1的這個的地方,可是,這個地方已經有一個數據1->2了,應該怎麼辦呢?
這在hash函數裏面有個專業的名詞,就叫碰撞。
碰撞有不少種不一樣的處理方式,不過這裏我只介紹一種,在java中比較常見的模式:作一個鏈表放在後面
這樣,在查詢的時候,若是須要4->8這個數據,那麼也先運算一下hash函數
key%3=4%3=1。因此在位置等於1的槽位上進行查找,由於第一個值是1->2不符合要求,因此指針下移,找到4->8,符合要求,返回便可。
上面,咱們就介紹了可以提高查詢速度的兩套主要的思路。
看起來挺簡單,其實否則,雖然核心思路簡單,可是須要有不少其餘的領域的不一樣選擇,致使了徹底不同的算法結構。而面向的問題不一樣,解決的方案也就不一樣,咱們來看看還有哪些主要的需求致使了咱們實現上的不一樣呢?
1.是否支持範圍查找?
有些時候,一個數據結構須要進行範圍查詢,好比以時間做爲key的數據,那麼通常來講都會須要查詢某個時間範圍內的全部結果,對於這類的查詢,支持範圍查詢是個必要的條件,不過有些數據結構則可能不可以支持範圍查詢。
2.集合是否可以隨着數據的增加而自動擴展?
大部分狀況下,其實咱們都很難在開始的時候預測到咱們的這些數據結構中到底會有多少的數據,若是可以隨着數據的增加而自動擴展,咱們就不須要擔憂集合過小,數據太多,也不須要擔憂預先申請的空間太大,資源浪費了~惋惜,數組不能自動擴展==。。
3.讀寫性能指標?
這應該是全部集合類都要努力追求和優化的東西~~~hoho
4.是否面向磁盤結構?
這是個很重要的指標,什麼叫面向磁盤的結構呢?爲何btree,LSM就適合在磁盤上存儲呢?本章後面章節會有詳細介紹。
5.並行指標?
當前數據結構是否可以支持並行寫入和讀取,在多核架構下是很是重要的一個指標。
6.內存佔用
以上的結構基本上可以涵蓋一個集合的大部分技術特徵了,而不一樣的集合類在以上這些特徵上的不一樣選擇也直接決定了性能的好壞,而不管是nosql仍是RDBMS,其性能最終的決定性因素都在於集合上的選擇。在後面,我會選擇幾類常見的,比較典型的存儲結構,以上面的這幾個維度來進行一下分析,但願可以讓你們在理解了這些存儲的技術特性後,也可以對目前市面上常見的存儲的性能進行更準確和更客觀的估測:)