国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

Go 語言下的 Redis 跳表設計與實現

系統 Redis
本文將詳細介紹如何使用 Go 語言從零開始實現一個類似于 Redis 的跳表。我們將探討跳表的基本原理、設計思路以及具體的實現方法。

在現代高性能數據庫和緩存系統中,跳表(Skip List)作為一種高效的有序數據結構,被廣泛應用于快速查找、插入和刪除操作。Redis 是一個開源的鍵值對存儲系統,它支持多種數據類型,并以其出色的性能而聞名。其中,Redis 使用了跳表來實現有序集合(Sorted Set),以保證其高效的數據處理能力。

本文將詳細介紹如何使用 Go 語言從零開始實現一個類似于 Redis 的跳表。我們將探討跳表的基本原理、設計思路以及具體的實現方法。通過本篇文章的學習,你不僅能夠了解跳表的工作機制,還能夠在實際項目中應用這一強大的數據結構。

定義基礎數據結構

redis中跳表通過score標識元素的大小,通過redis obj維護節點的信息,與此同時為了保證查詢的高效,它會為每個節點維護一份隨機高度的索引記錄當前節點的某個前驅節點:

對應我們給出節點的代碼實現:

/*
*
跳表節點的定義
*/
type zskiplistNode struct {
 //記錄元素的redis指針
 obj *robj
 //記錄當前元素的數值,代表當前元素的優先級
 score float64
 //指向當前元素的前驅節點,即小于當前節點的元素
 backward *zskiplistNode
 //用一個zskiplistLevel數組維護本屆點各層索引信息
 level    []zskiplistLevel
}

zskiplistLevel的代碼實現比較簡單,通過forward 記錄本層索引的前驅節點,并用span維護當前節點需要跨幾步才能走到該前驅節點:

type zskiplistLevel struct {
 //記錄本層索引的前驅節點的指針
 forward *zskiplistNode
 //標識節點的本層索引需要跨幾步才能到達該節點
 span    int64
}

通過上述概念構成無數個節點即稱為跳表,如下圖所示,各個節點都用一個level數組記錄本層索引到前驅節點的地址和跨度,而跳表也用一個header和tail指針維護跳表的頭尾節點:

對應的跳表結構體的代碼如下所示:

type zskiplist struct {
 //指向跳表的頭節點
 header *zskiplistNode
 //指向跳表的尾節點
 tail *zskiplistNode
 //維護跳表的長度
 length int64
 //維護跳表當前索引的最高高度
 level int
}

實現初始化方法

對應的我們也給出跳表的初始化代碼,大體邏輯是初始化跳表之后,初始化一個全空的索引和維護跳表的各種初始化信息,對應的筆者也對此代碼做了詳盡的注釋,讀者可自行參閱:

func zslCreate() *zskiplist {
 var j int
 //初始化跳表結構體
 zsl := new(zskiplist)
 //索引默認高度為1
 zsl.level = 1
 //跳表元素初始化為0
 zsl.length = 0
 //初始化一個頭節點socre為0,元素為空
 zsl.header = zslCreateNode(ZSKIPLIST_MAXLEVEL, 0, nil)

 /**
 基于跳表最大高度32初始化頭節點的索引,
 使得前驅指針指向null 跨度也設置為0
 */
 for j = 0; j < ZSKIPLIST_MAXLEVEL; j++ {
  zsl.header.level[j].forward = nil
  zsl.header.level[j].span = 0
 }
 //頭節點的前驅節點指向null,代表頭節點之前沒有任何元素
 zsl.header.backward = nil
 //初始化尾節點
 zsl.tail = nil
 return zsl
}

跳表插入操作

插入新節點時,本質上就是通過各層索引找到小于插入節點x的score的最大值,并記錄到update數組中,同時將頭節點跨到update數組元素的跨度值記錄到rank數組中,如下圖所示,假如我們插入節點1.5,那么對應各層索引的在update和rank兩個數組中維護的信息是:

  • level2級中update記錄header節點,所以跨度為0。
  • level1級中update記錄的是節點1,跨度為1。

然后基于此信息將x插入:

對應的代碼和上述圖解邏輯一致,對應的實現細節筆者都做好了標注:

func zslInsert(zsl *zskiplist, score float64, obj *robj) *zskiplistNode {
 //創建一個update數組,記錄插入節點每層索引中小于該score的最大值
 update := make([]*zskiplistNode, ZSKIPLIST_MAXLEVEL)
 //記錄各層索引走到小于score最大節點的跨區
 rank := make([]int64, ZSKIPLIST_MAXLEVEL)
 //x指向跳表走節點
 x := zsl.header
 var i int
 //從跳表當前最高層索引開始,查找每層小于當前score的節點的最大值節點
 for i = zsl.level - 1; i >= 0; i-- {
  //如果當前索引是最高層索引,那么rank從0開始算
  if i == zsl.level-1 {
   rank[i] = 0
  } else { //反之本層索引直接從上一層的跨度開始往后查找
   rank[i] = rank[i+1]
  }
  /**
  如果前驅節點不為空,且符合以下條件,則指針前移:
  1. 節點小于當前插入節點的score
  2. 節點score一致,且元素值小于或者等于當前score
  */
  if x.level[i].forward != nil &&
   (x.level[i].forward.score < score || (x.level[i].forward.score == score && x.level[i].forward.obj.String() < obj.String())) {
   //記錄本層索引前移跨度
   rank[i] += x.level[i].span
   //索引指針先前移動
   x = x.level[i].forward

  }
  //記錄本層小于當前score的最大節點
  update[i] = x
 }
 //隨機生成新插入節點的索引高度
 level := zslRandomLevel()
 /**
 如果大于當前索引高度,則進行初始化,將這些高層索引的update數組都指向header節點,跨度設置為跳表中的元素數
 意為這些高層索引小于插入節點的最大值就是header
 */
 if level > zsl.level {
  for i := zsl.level; i < level; i++ {
   rank[i] = 0
   update[i] = zsl.header
   update[i].level[i].span = zsl.length
  }
  //更新一下跳表索引的高度
  zsl.level = level
 }
 //基于入參生成一個節點
 x = zslCreateNode(level, score, obj)
 //從底層到當前最高層索引處理節點關系
 for i = 0; i < level; i++ {
  //將小于當前節點的最大節點的forward指向插入節點x,同時x指向這個節點的前向節點
  x.level[i].forward = update[i].level[i].forward
  update[i].level[i].forward = x
  //維護x和update所指向節點之間的跨度信息
  x.level[i].span = update[i].level[i].span - (rank[0] - rank[i])
  update[i].level[i].span = rank[0] - rank[i] + 1
 }
 /**
 考慮到當前插入節點生成的level小于當前跳表最高level的情況
 該邏輯會將這些區間的update索引中的元素到其前方節點的跨度+1,即代表這些層級索引雖然沒有指向x節點,
 但因為x節點插入的緣故跨度要加1
 */
 for i = level; i < zsl.level; i++ {
  update[i].level[i].span++
 }
 //如果1級索引是header,則x后繼節點不指向該節點,反之指向
 if update[0] == zsl.header {
  x.backward = nil
 } else {
  x.backward = update[0]
 }
 //如果x前向節點不為空,則讓前向節點指向x
 if x.level[0].forward != nil {
  x.level[0].forward.backward = x
 } else {//反之說明x是尾節點,tail指針指向它
  zsl.tail = x
 }
 //維護跳表長度信息
 zsl.length++
 return x
}

跳表查詢操作

有了插入操作的基礎后,查詢操作實現也比較容易了,即從頭節點的最高索引開始不斷向前找,如果沒有則往下一級索引前向找,找到后返回經過的跨度即可。

如下圖,我們希望查找元素2,直接從頭節點的2級索引開始看,就是元素2比對一致,返回跨度2,即跨2步就能到達:

對應代碼如下,和筆者說明一致,這里筆者也做了詳盡的標注提供參考:

func zslGetRank(zsl *zskiplist, score float64, obj *robj) int64 {
 var rank int64
 //從索引最高節點開始進行查找
 x := zsl.header
 for i := zsl.level - 1; i >= 0; i-- {
  //如果前向節點不為空且score小于查找節點,或者score相等,但是元素字符序比值小于或者等于則前移,同時用rank記錄跨度
  for x.level[i].forward != nil &&
   (x.level[i].forward.score < score || (x.level[i].forward.score == score && x.level[i].forward.obj.String() <= obj.String())) {
   rank += x.level[i].span
   x = x.level[i].forward
  }
  //上述循環結束,比對一直,則返回經過的跨度
  if x.obj != nil && x.obj.String() == obj.String() {
   return rank
  }
 }
 return 0
}

跳表刪除操作

刪除操作本質上也是找到要刪除節點索引的前后節點,然后將這些節點關聯,并修改其之間跨度,如下圖我們要刪除1.5節點,對應各層查找結果為:

  • 3級索引找到頭節點,因為前方不是1.5的節點索引,直接跨度減1即。
  • 2級索引找到頭節點,前方就是1.5的索引,刪除掉后跨度改為header索引到1.5+1.5到前向節點跨度減去1,這里的減去1代表刪除了節點1.5的跨步。
  • 1級索引同2級索引,不多做贅述。

對應的代碼示例如下,整體邏輯和筆者描述基本一致,先通過update找到刪除節點x的前一個元素,然后調用zslDeleteNode進行刪除:

func zslDelete(zsl *zskiplist, score float64, obj *robj) int64 {
 update := make([]*zskiplistNode, ZSKIPLIST_MAXLEVEL)
 //找到每層索引要刪除節點的前一個節點
 x := zsl.header
 for i := zsl.level - 1; i >= 0; i-- {
  for x.level[i].forward != nil &&
   (x.level[i].forward.score < score || (x.level[i].forward.score == score && x.level[i].forward.obj.String() < obj.String())) {
   x = x.level[i].forward
  }
  update[i] = x
 }
 //查看1級索引前面是否就是要刪除的節點,如果是則直接調用zslDeleteNode刪除節點,并斷掉前后節點關系
 x = x.level[0].forward
 if x != nil && x.obj.String() == obj.String() {
  zslDeleteNode(zsl, x, update)
  return 1
 }
 return 0
}

對應zslDeleteNode細節就如筆者上圖所講解的步驟,讀者可參考注釋進行閱讀:

func zslDeleteNode(zsl *zskiplist, x *zskiplistNode, update []*zskiplistNode) {

 var i int
 for i = 0; i < zsl.level; i++ {
  /*
  如果索引前方就是刪除節點,當前節點span為:
  當前節點到x +x到x前向節點 -1
   */
  if update[i].level[i].forward == x {
   update[i].level[i].span += x.level[i].span - 1
   update[i].level[i].forward = x.level[i].forward
  } else {
   //反之說明該節點前方不是x的索引,直接減去x的跨步1即
   update[i].level[i].span -= 1
  }
 }
 //維護刪除后的節點前后關系
 if x.level[0].forward != nil {
  x.level[0].forward.backward = x.backward
 } else {
  zsl.tail = x.backward
 }
 //將全空層的索引刪除
 for zsl.level > 1 && zsl.header.level[zsl.level-1].forward == nil {
  zsl.level--
 }
 //維護跳表節點信息
 zsl.length--

小結

通過本文的詳細講解,我們從零開始使用 Go 語言實現了一個類似于 Redis 的跳表。我們首先介紹了跳表的基本原理和設計思路,然后逐步實現了跳表的各種核心操作,包括插入、查找和刪除。最后,我們對跳表的性能進行了分析,并探討了其在 Redis 有序集合和其他場景中的應用。

責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關推薦

2025-01-06 08:10:00

Redis跳表索引

2020-12-28 07:33:21

SkipListJava跳表

2021-09-30 09:21:28

Go語言并發編程

2017-06-27 09:43:43

Python機器學習

2023-05-17 00:15:11

TCCXA模式

2025-05-22 08:15:00

2021-07-30 07:28:15

WorkerPoolGo語言

2021-04-07 09:02:49

Go 語言變量與常量

2021-04-13 07:58:42

Go語言函數

2025-02-25 09:29:34

2025-03-20 09:54:47

2023-03-27 00:20:48

2025-08-28 02:12:00

2022-05-09 10:36:05

PythonPyScript開發者

2021-04-20 09:00:48

Go 語言結構體type

2025-03-27 00:45:00

2023-03-21 07:57:37

Go語言設計模式

2021-10-20 07:18:51

Go語言設計

2025-05-30 01:55:00

go語言Redis

2021-07-09 11:59:25

Redis有序集合
點贊
收藏

51CTO技術棧公眾號

亚洲精品久久久久久国产精华液| 亚洲电影成人| 男女羞羞在线观看| 137大胆人体在线观看| 国产高清一区在线观看| 免费av播放| 草裙成人精品一区二区三区 | 欧美成人影院在线播放| 欧美在线观看黄| 国产精品无码免费专区午夜| 色综合视频一区二区三区44| 在线观看免费黄视频| 99riav在线| 在线看的av网站| 国产精品久久九九| 日韩电影在线播放| 97视频资源在线观看| 国产精品久久久久久av福利软件| 国产精品丝袜白浆摸在线| 99理论电影网| 欧美高清性猛交| 中文字幕人成一区| 免费av手机在线观看| 成人av免费在线看| 国产精品久久久久久久久借妻 | 欧美人与禽zozo性伦| 开心九九激情九九欧美日韩精美视频电影| 91jq激情在线观看| 欧美xxxxxxxxx59| www午夜视频| 9999在线观看| 91色精品视频在线| 久久久成人的性感天堂| 91成人免费在线视频| 中文字幕免费不卡在线| www.激情成人| 国产91精品精华液一区二区三区 | 97视频精品| 精品av久久久久电影| 日本欧美一区二区三区| 99精品99| 热久久一区二区| 久久久99精品久久| 久久中文字幕电影| 日韩一区在线播放| 欧美极品美女视频| 亚洲va国产天堂va久久en| 色哟哟一区二区三区| 精品国产91久久久| 精品爽片免费看久久| 操日韩av在线电影| 国产成人精品久久二区二区91| 不卡一区二区三区视频| 欧美日韩另类综合| 久久涩涩网站| 青青成人在线| 欧美一区1区三区3区公司 | 久久久久久久久成人| 老牛精品亚洲成av人片| 99成人在线| 黄网站免费久久| 亚洲国产精品t66y| 亚洲国产精彩中文乱码av| 亚州成人av在线| 这里只有精品66| 毛片免费在线观看| 在线综合色站| 日韩国产一区二| 国产精品成人免费在线| 欧美不卡一区二区三区四区| 日本久久久久久久久久久| 老汉色影院首页| 日本免费高清视频| 青青草在线免费视频| 97久久亚洲| 国产麻豆欧美日韩一区| 成人av一区二区三区| 91.com在线观看| 亚洲综合在线播放| 四虎最新网站| 欧美日韩精品一区二区三区视频| 国产ts一区| 久久久久久久久久美女| 一区二区三区在线视频观看58| 在线视频中文亚洲| 久久精品日产第一区二区三区| aa在线免费观看| 日韩欧美一中文字暮专区| 久久a爱视频| 国产一区清纯| 亚洲人成亚洲人成在线观看图片 | 欧美精品一区二区三区蜜桃| 国产精品网红福利| 九色视频网站入口| 夜夜躁狠狠躁日日躁2021日韩| 激情伊人五月天久久综合| 中文字幕中文字幕中文字幕亚洲无线 | 一区二区三区四区五区精品视频 | 日韩理论在线观看| 国产精品高潮粉嫩av| 天堂a中文在线| 亚洲欧洲一区二区天堂久久| 欧美日韩第一区日日骚| 午夜视频久久久| 只有精品亚洲| 午夜精品福利视频网站| 日本午夜精品一区二区| 国产不卡精品| 色婷婷精品久久二区二区蜜臀av| 日韩国产欧美一区| 成人精品在线| 色狠狠av一区二区三区| 色阁综合av| 99久久婷婷国产综合精品青牛牛| 亚洲图片一区二区| 亚洲啪啪av| 九九精品久久| 欧美一级一区二区| 男女污污的视频| 好吊一区二区三区| 亚洲国产精彩中文乱码av| 97在线资源| 成人免费观看av| 91在线看www| 国产精品亚洲四区在线观看| 性感美女极品91精品| 国产毛片久久久久久国产毛片| 欧美午夜精彩| 中文字幕欧美在线| 成人在线高清视频| 国产精品福利av| 午夜精品一区二区在线观看的| 91久久偷偷做嫩草影院电| 日韩久久久精品| 性感美女激情视频在线观看| 成人一区二区三区视频 | 国产在线1区| 中文字幕国产一区二区| 欧美日韩精品不卡| 日韩国产欧美一区二区| 国产+成+人+亚洲欧洲| 精品久久福利| 亚洲人成电影网| av漫画网站在线观看| 日韩一区二区高清| dy888亚洲精品一区二区三区| 欧美综合视频在线观看| 亚洲第一se情网站| a亚洲天堂av| 一区二区三区四区| 在线成人www免费观看视频| 欧美极品少妇xxxxⅹ喷水| 9i看片成人免费高清| 欧美日韩精品是欧美日韩精品| 在线视频中文字幕久| 久久久久久久久99精品| 狠狠色综合欧美激情| 欧美中文字幕一区二区| 俺去亚洲欧洲欧美日韩| 电影k8一区二区三区久久| 高跟丝袜一区二区三区| 99热自拍偷拍| 免费成人小视频| 国内精品一区二区| 国内精品伊人久久久| 久久久国产影院| 九七电影院97理论片久久tvb| 激情av一区| 成人精品一区二区三区电影免费 | 日韩经典中文字幕一区| 蜜桃传媒视频麻豆第一区免费观看| 亚洲精品欧洲| 视频一区二区三区免费观看| 久久狠狠亚洲综合| 欧美又粗又长又爽做受| 欧美国产激情一区二区三区蜜月| 国产专区视频| 91久久精品一区二区三区| 污视频在线看操| 国产盗摄视频一区二区三区| 欧美精品18videosex性欧美| 91视频成人免费| 国产精品羞羞答答在线观看| 亚洲免费av电影| 男女人搞j网站| 国产一区二区三区四区五区美女 | 在线中文av| 一二三在线视频社区| 国模精品一区二区| 2024最新电影在线免费观看| 亚洲精品成人图区| 91精品国产一区二区在线观看| 豆花视频一区二区| 第一会所亚洲原创| 亚洲国产高清一区二区三区| 日韩av中文在线观看| 成人av先锋影音| 亚洲三级在线播放| 欧美日韩免费观看一区三区| 亚洲欧洲xxxx|