版權聲明:本文爲博主原創文章,未經博主允許不得轉載。
前面講到了在 table
中插入數據,然後將數據持久化到磁盤中,這些都是一下底層的操作,用戶真正寫數據是放到內存中。本章就來介紹 key-value
在內存的操作
skiplist
key-value
存放在在內存中採用的結構是 skiplist
,結構如下所示
head_------>7------------------------------------------>31
| |
head_------>7------------------------------------------>31--------------------------------->78
| | |
head_------>7--------------->15------------------------>31--------------->45--------------->78
| | | | |
head_------>7------>10------>15------>24------>29------>31------>34------>45------>56------>78------>100
如上結構,分爲 4
層,每一層都是一個有序單向鏈表。這樣一個結構該怎麼操作呢,接下來用查詢和添加數據來操作 skiplist
1. 首先是查詢,如果我們要找到內容爲 29
的 key
- 首先 skiplist
有一個變量 head_
始終指向第一個節點的最上層,也就是圖中最上層的 head_
- 29
和第 4
層 head_
的 next
節點 7
比較,大於 7
,此時頭節點指向第 4
層的 7
- 29
和第 4
層 7
的 next
節點 31
比較,小於 31
,降一層
- 29
和第 3
層 7
的 next
節點 31
比較,小於 31
,降一層
- 29
和第 2
層 7
的 next
節點 15
比較,大於 15
,此時頭節點指向第 2
層的 15
- 29
和第 2
層 15
的 next
節點 31
比較,小於 31
,降一層
- 29
和第 1
層 15
的 next
節點 24
比較,大於 24
,此時頭節點指向第 1
層的 24
- 29
和第 1
層 24
的 next
節點 29
比較,等於 29
,且是第一層,沒錯就是你了
在上圖中插入
30
- 首先得到一個隨機值(
1
到kMaxHeight
之間)來設置30
這個節點的層數,假設得到的是5
,定義一個prev[5]
,用來存放30
的前一個節點 - 上圖中也就
4
層,咋整?(skiplist
有兩個關於層數的變量,第一個是kMaxHeight
,表示允許的最大層;第二個是max_height_
,表示skiplist
當前最大層,head_
節點在初始話的時候和其他節點不一樣,層數不是隨機得到的,而是直接指定的kMaxHeight
) - 沒事,將大於
4
的層以上的節點的prev
全部指向head_
,也就是將prev[4] = head_(第5層)
, 並將當前最大層數max_height_
設置爲5
- 還是按照上面查詢的步驟,只不過把每次降一層時的節點放在
prev
中,那麼
prev[4] = head_(第5層);
prev[3] = 7(第4層);
prev[2] = 7(第3層);
prev[1] = 15(第2層);
prev[0] = 29(第1層);
- 得到
prev
後,初始化一個高度爲5
值爲30
的節點x
for i in 0..4:
x[i]->next = prev[i]->next;
prev[i]->next = x[i];
- 得到的結果就是
head_-------------------------------------------------->30 | head_------>7------------------------------------------>30------>31 | | | head_------>7------------------------------------------>30------>31--------------------------------->78 | | | | head_------>7--------------->15------------------------>30------>31--------------->45--------------->78 | | | | | | head_------>7------>10------>15------>24------>29------>30------>31------>34------>45------>56------>78------>100
- 首先得到一個隨機值(
Node
skiplist
應該解釋的差不多了,接下來介紹一下 skiplist
的節點 struct Node
// 模板方法,其實很好理解,相當於佔位,Key和Comparator可以替換成任何類型
template<typename Key, class Comparator>
struct SkipList<Key,Comparator>::Node {
// 不允許隱式構造
explicit Node(const Key& k) : key(k) { }
// 結構體中有個Key類型的key
Key const key;
// 返回next_[n],也就是當前節點第n層的next節點,不要被return的那一長串嚇到,後面會介紹到
Node* Next(int n) {
assert(n >= 0);
return reinterpret_cast<Node*>(next_[n].Acquire_Load());
}
// 設置當前節點第n層的next節點爲x,不要被return的那一長串嚇到,後面會介紹到
void SetNext(int n, Node* x) {
assert(n >= 0);
next_[n].Release_Store(x);
}
// 和上面差不多,區別是沒有進行內存隔離,後面會介紹到
Node* NoBarrier_Next(int n) {
assert(n >= 0);
return reinterpret_cast<Node*>(next_[n].NoBarrier_Load());
}
// 和上面差不多,區別是沒有進行內存隔離,後面會介紹到
void NoBarrier_SetNext(int n, Node* x) {
assert(n >= 0);
next_[n].NoBarrier_Store(x);
}
private:
// 這裏定義長度爲1的port::AtomicPointer對象,其實是一個變長結構體,next_指向的是第一個
port::AtomicPointer next_[1];
};
Node
中有兩個變量,key
和 next_
數組,key
是用來存放值的,next_
是用來下一個節點指向的
AtomicPointer
關於內存隔離請看 leveldb-AtomicPointer
skipList 函數實現
void Insert(const Key& key);
bool Contains(const Key& key) const;
Node* FindGreaterOrEqual(const Key& key, Node** prev) const;
skipList
主要包含這兩個函數
// 得到一個隨機的高度
template<typename Key, class Comparator>
int SkipList<Key,Comparator>::RandomHeight() {
static const unsigned int kBranching = 4;
// 初始爲 1
int height = 1;
// 得到一個隨機值,如果隨機值是 4 的倍數就返回 height,否則 keight 就加 1,爲什麼要這麼做?
// 如果我們得到一個隨機值,直接對 kMaxHeight 取模加 1,然後賦值給 height,那麼 height 在 [1~12] 之前出現的概率一樣的
// 如果節點個數爲 n,那麼有 12 層的節點有 n/12 個,11 層的有 n/12+n/12(需要把12層的也加上),節點太多,最上層平均前進一次才右移 12 個節點,下面層就更不用說了,效率低;
// 作者的方法是每一層會按照4的倍數減少,出現4層的概率只有出現3層概率的1/4,這樣查詢起來效率是不是大大提升了呢
while (height < kMaxHeight && ((rnd_.Next() % kBranching) == 0)) {
height++;
}
assert(height > 0);
assert(height <= kMaxHeight);
return height;
}
// 很簡單,就是比較key和n的大小
template<typename Key, class Comparator>
bool SkipList<Key,Comparator>::KeyIsAfterNode(const Key& key, Node* n) const {
// NULL n is considered infinite
return (n != NULL) && (compare_(n->key, key) < 0);
}
template<typename Key, class Comparator>
typename SkipList<Key,Comparator>::Node* SkipList<Key,Comparator>::FindGreaterOrEqual(const Key& key, Node** prev)
const {
// x指向最上層頭指針
Node* x = head_;
// 得到當前最高層
int level = GetMaxHeight() - 1;
while (true) {
// next指向的是x同層的下一個節點,可能爲空
Node* next = x->Next(level);
// 如果key在next的後面,x指針指向next
if (KeyIsAfterNode(key, next)) {
// Keep searching in this list
x = next;
} else {
// 如果next爲空或key在next的前面
if (prev != NULL) prev[level] = x;
if (level == 0) {
// 層數爲0的話,說明兩個節點之間沒有挑過其他節點,而此時next是大於或等於key的,key肯定是大於next的前一個節點的(不大於的話走不到next節點)
// 返回的next節點只能大於或等於key節點
return next;
} else {
// 如果不是最底層的話就降一層
level--;
}
}
}
}
template<typename Key, class Comparator>
bool SkipList<Key,Comparator>::Contains(const Key& key) const {
// 找到大於或等於key的節點,如果相等就返回true
Node* x = FindGreaterOrEqual(key, NULL);
if (x != NULL && Equal(key, x->key)) {
return true;
} else {
return false;
}
}
skipList 內部類 Iterator
// Iterator方法實現比較簡單,這裏就詳細介紹了
class Iterator {
public:
explicit Iterator(const SkipList* list);
// 判斷當前節點是否有效
bool Valid() const;
// 返回當前節點的key
const Key& key() const;
// 返回第0層的下一個節點,需要判斷是否有效
void Next();
// 返回第0層的上一個節點,需要判斷是否有效
void Prev();
// 找到第0層第一個大於或等於target的節點
void Seek(const Key& target);
// 返回第0層的第一個節點
void SeekToFirst();
// 返回第0層最後一個節點
void SeekToLast();
private:
const SkipList* list_;
Node* node_; // 當前節點
};
總結
學習skiplist主要了解三個地方,第一個是Node的定義(內存隔離);第二個是skiplist類本身的幾個方法;第三個就是skiplist的內部類了,用來遍歷鏈表
【作者:antonyxu https://antonyxux.github.io/ 】