爲啥 Redis 使用跳錶而不是紅黑樹

開發者所說,他爲何選用 skiplist The Skip list

There are a few reasons:

  1. They are not very memory intensive. It's up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.
  2. A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.
  3. They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.
    About the Append Only durability & speed, I don't think it is a good idea to optimize Redis at cost of more code and more complexity for a use case that IMHO should be rare for the Redis target (fsync() at every command). Almost no one is using this feature even with ACID SQL databases, as the performance hint is big anyway.
    About threads: our experience shows that Redis is mostly I/O bound. I'm using threads to serve things from Virtual Memory. The long term solution to exploit all the cores, assuming your link is so fast that you can saturate a single core, is running multiple instances of Redis (no locks, almost fully scalable linearly with number of cores), and using the "Redis Cluster" solution that I plan to develop in the future.

簡言之,跳錶和時間複雜度幾乎和紅黑樹一樣,而且實現起來簡單。

跳錶

出自 看了Redis源碼還不懂這個數據結構,白看了

往原始鏈表加多幾級索引的話,查找效率將會進一步提升。這種鏈表加多級索引的結構,就叫做跳錶。

舉個例子,查詢 11 時,只需要 O(logn) 的複雜度

刪除就得先查找,找到後從下往上刪除。

插入的時候,首先要進行查詢,然後從最底層開始,插入被插入的元素。然後看看從下而上,是否需要逐層插入。可是到底要不要插入上一層呢?跳錶的思路是拋硬幣,聽天由命,產生一個隨機數,50%概率再向上擴展,否則就結束。這樣子,每一個元素能夠有X層的概率爲0.5^(X-1)次方。反過來,第X層有多少個元素的數學期望大家也可以算一下。

JS 實現跳錶代碼

出自使用JavaScript實現SkipList這種數據結構

/**
 * author dreamapplehappy
 */

// 代碼使用了ES6以及更高版本的JavaScript來表示,需要使用Babel之類的工具處理一下纔可以在Node或者瀏覽器中運行

// 定義了跳錶索引的最大級數
const MAX_LEVEL = 16;

/**
 * 定義Node類,用來輔助實現跳錶功能
 */
class Node{
  // data屬性存放了每個節點的數據
  data = -1;
  // maxLevel屬性表明了當前節點處於整個跳錶索引的級數
  maxLevel = 0;
  // refer是一個有着MAX_LEVEL大小的數組,refer屬性存放着很多個索引
  // 如果用p表示當前節點,用level表示這個節點處於整個跳錶索引的級數;那麼p[level]表示在level這一層級p節點的下一個節點
  // p[level-n]表示level級下面n級的節點
  refer = new Array(MAX_LEVEL);
}

/**
 * 定義SkipList類
 */
class SkipList{
  // levelCount屬性表示了當前跳錶索引的總共級數
  levelCount = 1;
  // head屬性是一個Node類的實例,指向整個鏈表的開始
  head = new Node();

  // 在跳裏面插入數據的時候,隨機生成索引的級數
  static randomLevel() {
	let level = 1;
	for(let i = 1; i < MAX_LEVEL; i++) {
	  if(Math.random() < 0.5) {
		level++;
	  }
	}
	return level;
  }

  /**
   * 向跳錶裏面插入數據
   * @param value
   */
  insert(value) {
	const level = SkipList.randomLevel();
	const newNode = new Node();
	newNode.data = value;
	newNode.maxLevel = level;
	const update = new Array(level).fill(new Node());
	let p = this.head;
	for(let i = level - 1; i >= 0; i--) {
	  while(p.refer[i] !== undefined && p.refer[i].data < value) {
		p = p.refer[i];
	  }
	  update[i] = p;
	}
	for(let i = 0; i < level; i++) {
	  newNode.refer[i] = update[i].refer[i];
	  update[i].refer[i] = newNode;
	}
	if(this.levelCount < level) {
	  this.levelCount = level;
	}
  }

  /**
   * 查找跳錶裏面的某個數據節點,並返回
   * @param value
   * @returns {*}
   */
  find(value) {
	if(!value){return null}
	let p = this.head;
	for(let i = this.levelCount - 1; i >= 0; i--) {
	  while(p.refer[i] !== undefined && p.refer[i].data < value) {
		p = p.refer[i];
		// 標記1,此處用於文章的說明
	  }
	}

	if(p.refer[0] !== undefined && p.refer[0].data === value) {
	  return p.refer[0];
	}
	return null;
  }

  /**
   * 移除跳錶裏面的某個數據節點
   * @param value
   * @returns {*}
   */
  remove(value) {
	let _node;
	let p = this.head;
	const update = new Array(new Node());
	for(let i = this.levelCount - 1; i >= 0; i--) {
	  while(p.refer[i] !== undefined && p.refer[i].data < value){
		p = p.refer[i];
	  }
	  update[i] = p;
	}

	if(p.refer[0] !== undefined && p.refer[0].data === value) {
	  _node = p.refer[0];
	  for(let i = 0; i <= this.levelCount - 1; i++) {
		if(update[i].refer[i] !== undefined && update[i].refer[i].data === value) {
		  update[i].refer[i] = update[i].refer[i].refer[i];
		}
	  }
	  return _node;
	}
	return null;
  }

  // 打印跳錶裏面的所有數據
  printAll() {
	let p = this.head;
	while(p.refer[0] !== undefined) {
	  // console.log(p.refer[0].data)
	  p = p.refer[0];
	}
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章