最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang實(shí)現(xiàn)自己的Redis(有序集合跳表)實(shí)例探究

 更新時(shí)間:2024年01月24日 09:27:18   作者:紹納?nullbody筆記  
這篇文章主要為大家介紹了Golang實(shí)現(xiàn)自己的Redis(有序集合跳表)實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

用11篇文章實(shí)現(xiàn)一個(gè)可用的Redis服務(wù),姑且叫EasyRedis吧,希望通過(guò)文章將Redis掰開(kāi)撕碎了呈現(xiàn)給大家,而不是僅僅停留在八股文的層面,并且有非常爽的感覺(jué),歡迎持續(xù)關(guān)注學(xué)習(xí)。

[x] easyredis之TCP服務(wù)

[x] easyredis之網(wǎng)絡(luò)請(qǐng)求序列化協(xié)議(RESP)

[x] easyredis之內(nèi)存數(shù)據(jù)庫(kù)

[x] easyredis之過(guò)期時(shí)間 (時(shí)間輪實(shí)現(xiàn))

[x] easyredis之持久化 (AOF實(shí)現(xiàn))

[x] easyredis之發(fā)布訂閱功能

[x] easyredis之有序集合(跳表實(shí)現(xiàn))

[ ] easyredis之 pipeline 客戶端實(shí)現(xiàn)

[ ] easyredis之事務(wù)(原子性/回滾)

[ ] easyredis之連接池

[ ] easyredis之分布式集群存儲(chǔ)

EasyRedis之有序集合(跳表實(shí)現(xiàn))

有序集合需求分析:

  • 集合:同一個(gè)member只能出現(xiàn)一次(去重)
  • 有序:可以正序or倒序遍歷

Golang中去重我們可以使用map去重,有序則使用鏈表。所以有序集合的數(shù)據(jù)結(jié)構(gòu)定義如下:

// score可以相同,member不能重復(fù)
type SortedSet struct {
	dict map[string]*Pair // 利用map基于member去重
	skl  *skiplist        // 利用skl進(jìn)行排序
}
// 要存儲(chǔ)的值
type Pair struct {
	Member string
	Score  float64
}

那應(yīng)該怎么利用鏈表實(shí)現(xiàn)有序的?很簡(jiǎn)單,插入/修改/刪除都保證鏈表的有序性,那整個(gè)鏈表最后肯定就是一直有序的

例如:要插入節(jié)點(diǎn)9,為了保證鏈表的有序行,需要找到前驅(qū)節(jié)點(diǎn)。

前驅(qū)節(jié)點(diǎn):即比9小的節(jié)點(diǎn)中的【最大的一個(gè)】。1/2/3/4/5都比9小,但是這些小的當(dāng)中最大的一個(gè)是5。查找的方式簡(jiǎn)單粗暴,就是遍歷鏈表,從頭開(kāi)始遍歷。

偽代碼:

type  node struct{
	val int64
}
var curNode *node = &node{val:9}
if pre.next != nil && pre.next.val < curNode.val
{
	pre = pre.next
}
// 插入
pre.next = curNode

可以看出,每次我要插入一個(gè)新節(jié)點(diǎn),都是要遍歷鏈表,定位出前驅(qū)節(jié)點(diǎn),隨著鏈表長(zhǎng)度越來(lái)越長(zhǎng),是不是整個(gè)鏈表的插入效率就越來(lái)越差。那有沒(méi)有辦法快速定位到鏈表中的前驅(qū)節(jié)點(diǎn)。

這里就引申出了跳表的實(shí)現(xiàn)。 跳表的本質(zhì)就是鏈表,只是鏈表節(jié)點(diǎn)個(gè)頭長(zhǎng)高了。普通的鏈表只有一層,跳表是包含多層的鏈表,

當(dāng)我們要查找前驅(qū)節(jié)點(diǎn)5的時(shí)候, 偽代碼如下:

type Level struct {
    forward *node// 每層的指向
    span int64// 每層的跨度
}
type node struct {
    val int64
    level []*Level //每個(gè)節(jié)點(diǎn)的層
}
var maxlevel = 5
// 被插入的節(jié)點(diǎn)9 
var curNode *node = &node{val:9}
// dummy 節(jié)點(diǎn)
var dummy *node = &node{val:0}
// pre一開(kāi)始指向dummy節(jié)點(diǎn)
var pre *node = dummy
// 從最高層開(kāi)始,這里的i表示層高索引
for i := maxlevel-1;i >= 0;i-- {
    for pre.level[i].forward != nil && pre.level[i].forward.val < curNode.val {
        pre = pre.level[i].forward
    }
}
  • 從最高層開(kāi)始進(jìn)行遍歷,這里的最高層是 pre.level[4]

  • 當(dāng) pre.level[4].forward != nil && pre.level[4].forward.val < 9,更新pre的指向節(jié)點(diǎn)4。繼續(xù)重復(fù)for循環(huán)過(guò)程,因?yàn)閜re指向了節(jié)點(diǎn)4,所以 pre.level[4].forward == nil,(結(jié)合下圖理解)跳出內(nèi)部的for循環(huán),外部的for循環(huán)會(huì)將層高i-1。即:降層

  • 等到所有的層級(jí)全部遍歷完成以后,此時(shí)pre的指向就是最終的節(jié)點(diǎn)5

通過(guò)這種給節(jié)點(diǎn)增加層高的概念,可以看到找到節(jié)點(diǎn)5,我們只需要跳躍兩次從dummy -> 節(jié)點(diǎn)4 -> 節(jié)點(diǎn)5,比遍歷單層的鏈表效率高了很多。

有序集合代碼講解

有了上面的基礎(chǔ)知識(shí),就以zadd key score member向有序集合中插入為例講解下代碼。 入口函數(shù)位于engine/sortedset.go

// zadd key score member [score member...]
func cmdZAdd(db *DB, args [][]byte) protocol.Reply {
	//... 省略...
    // 獲取有序集合對(duì)象
	sortedSet, _, reply := db.getOrInitSortedSetObject(key)
	if reply != nil {
		return reply
	}
	// 將pairs保存到有序集合中
	i := int64(0)
	for _, pair := range pairs {
		if sortedSet.Add(pair.Member, pair.Score) {
			i++ // 新增的個(gè)數(shù)
		}
	}
	//... 省略...
}

其實(shí)就是調(diào)用sortedSet.Add(pair.Member, pair.Score)函數(shù). 在有序集合中,通過(guò)map保證member的唯一性(去重);如果有重復(fù)的,跳表中會(huì)先刪除;通過(guò) s.skl.insert(member, score)將數(shù)據(jù)插入到跳表中,并保證有序行;

// bool 為true表示新增, false表示修改
func (s *SortedSet) Add(member string, score float64) bool {
	pair, ok := s.dict[member]
	s.dict[member] = &Pair{
		Member: member,
		Score:  score,
	}
	// 說(shuō)明是重復(fù)添加
	if ok {
		// 分值不同
		if score != pair.Score {
			// 將原來(lái)的從跳表中刪除
			s.skl.remove(pair.Member, pair.Score)
			// 插入新值
			s.skl.insert(member, score)
		}
		// 分值相同,do nothing...
		returnfalse
	}
	// 新增
	s.skl.insert(member, score)
	returntrue
}

插入的邏輯細(xì)節(jié)很多,但是思想不復(fù)雜;因?yàn)樘硎嵌鄬拥?,所以插入一個(gè)新節(jié)點(diǎn)的時(shí)候,【每層的前驅(qū)節(jié)點(diǎn)】需要記錄下來(lái)beforeNode。如下圖:

如果我向插入一個(gè)節(jié)點(diǎn)3

  • level[4]第4層的前驅(qū)節(jié)點(diǎn)是 dummy
  • level[3]第3層的前驅(qū)節(jié)點(diǎn)是 1
  • level[2]第2層的前驅(qū)節(jié)點(diǎn)是 2
  • level[1]第1層的前驅(qū)節(jié)點(diǎn)是 2
  • level[0]第0層的前驅(qū)節(jié)點(diǎn)是 2

可以看到每一層的前驅(qū)節(jié)點(diǎn)是不一定相同的,所以每一層都記錄下來(lái),當(dāng)節(jié)點(diǎn)3要插入的時(shí)候,需要每一層進(jìn)行相連接。

完整代碼如下:

// 增
func (s *skiplist) insert(member string, score float64) *node {
	beforeNode := make([]*node, defaultMaxLevel)      // 每一層的前驅(qū)節(jié)點(diǎn)
	beforeNodeOrder := make([]int64, defaultMaxLevel) // 每一層的前驅(qū)節(jié)點(diǎn)排序編號(hào)
	node := s.header
	// i從最高層遍歷,通過(guò)遍歷,將member和score在每一層的前驅(qū)節(jié)點(diǎn)全部保存在 beforeNode中,以及節(jié)點(diǎn)的排序保存在beforeNodeOrder
	for i := s.maxLevel - 1; i >= 0; i-- {
		// 節(jié)點(diǎn)node的排序
		if i == s.maxLevel-1 {
			beforeNodeOrder[i] = 0
		} else {
			beforeNodeOrder[i] = beforeNodeOrder[i+1]
		}
		// 節(jié)點(diǎn)node在當(dāng)前i層的forward不為空
		for node.levels[i].forward != nil &&
			// node在層levels[i]的forward節(jié)點(diǎn),分值 < score 或者 分值相同但是成員 < member,說(shuō)明forward指向的節(jié)點(diǎn),作為下一個(gè)前驅(qū)節(jié)點(diǎn)
			(node.levels[i].forward.Score < score || (node.levels[i].forward.Score == score && node.levels[i].forward.Member < member)) {
			beforeNodeOrder[i] += int64(node.levels[i].span) // 更新節(jié)點(diǎn)node的編號(hào)
			node = node.levels[i].forward                    // 更新當(dāng)前節(jié)點(diǎn)node
		}
		beforeNode[i] = node
	}
	// 新節(jié)點(diǎn)層高
	newLevel := randomLevel()
	// 如果新層高比當(dāng)前已經(jīng)存在的層高s.maxLevel都要高,說(shuō)明還缺少了 newLevel - s.maxLevel范圍的前驅(qū)節(jié)點(diǎn)
	if newLevel > s.maxLevel {
		for i := s.maxLevel; i < newLevel; i++ {
			// beforeNode[i] 表示在i層的前驅(qū)節(jié)點(diǎn)
			beforeNode[i] = s.header
			beforeNodeOrder[i] = 0
			beforeNode[i].levels[i].forward = nil
			beforeNode[i].levels[i].span = s.length
		}
	}
	node = newNode(newLevel, member, score)
	// 將節(jié)點(diǎn)插入到多層鏈表中,僅僅對(duì)[0,newLevel)范圍進(jìn)行節(jié)點(diǎn)拼接
	for i := int16(0); i < newLevel; i++ {
		//也就是在每一層插入節(jié)點(diǎn)
		node.levels[i].forward = beforeNode[i].levels[i].forward
		beforeNode[i].levels[i].forward = node
		// 更新本層節(jié)點(diǎn)跨度
		node.levels[i].span = beforeNode[i].levels[i].span - (beforeNodeOrder[0] - beforeNodeOrder[i])
		beforeNode[i].levels[i].span = beforeNodeOrder[0] - beforeNodeOrder[i] + 1
	}
	// 如果新節(jié)點(diǎn)的高度很低,比最高低很多
	for i := newLevel; i < s.maxLevel; i++ {
		beforeNode[i].levels[i].span++ // 超過(guò)的節(jié)點(diǎn)的跨度默認(rèn)+1
	}
	// 修改第0層的 backward指向
	if beforeNode[0] == s.header {
		node.backward = nil
	} else {
		node.backward = beforeNode[0]
	}
	if node.levels[0].forward != nil {
		node.levels[0].forward.backward = node
	} else { // 說(shuō)明node是最后一個(gè)節(jié)點(diǎn)
		s.tailer = node
	}
	// 因?yàn)樾略?,?shù)量+1
	s.length++
	return node
}

代碼中還有一個(gè)細(xì)節(jié):節(jié)點(diǎn)3的高度因?yàn)槭请S機(jī)的,可能很高,也可能很矮

當(dāng)很高的時(shí)候,超過(guò)當(dāng)前最高的部分的前驅(qū)節(jié)點(diǎn)就是s.header 代碼細(xì)節(jié):

// 如果新層高比當(dāng)前已經(jīng)存在的層高s.maxLevel都要高,說(shuō)明還缺少了 newLevel - s.maxLevel范圍的前驅(qū)節(jié)點(diǎn)
	if newLevel > s.maxLevel {
		for i := s.maxLevel; i < newLevel; i++ {
			// beforeNode[i] 表示在i層的前驅(qū)節(jié)點(diǎn)
			beforeNode[i] = s.header
			beforeNodeOrder[i] = 0
			beforeNode[i].levels[i].forward = nil
			beforeNode[i].levels[i].span = s.length
		}
	}

當(dāng)很矮的時(shí)候,比最高部分矮的部分前驅(qū)節(jié)點(diǎn),需要span+1 代碼細(xì)節(jié):

// 如果新節(jié)點(diǎn)的高度很低,比最高低很多
	for i := newLevel; i < s.maxLevel; i++ {
		beforeNode[i].levels[i].span++ // 超過(guò)的節(jié)點(diǎn)的跨度默認(rèn)+1
	}

效果演示

項(xiàng)目代碼地址: https://github.com/gofish2020/easyredis 

以上就是Golang實(shí)現(xiàn)自己的Redis(有序集合跳表)實(shí)例探究的詳細(xì)內(nèi)容,更多關(guān)于Golang Redis有序集合的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語(yǔ)言范圍Range的具體使用

    Go語(yǔ)言范圍Range的具體使用

    range關(guān)鍵字在for循環(huán)中用于遍歷數(shù)組,切片,通道或映射的項(xiàng)目,本文主要介紹了Go語(yǔ)言范圍Range的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Golang?int函數(shù)使用實(shí)例全面教程

    Golang?int函數(shù)使用實(shí)例全面教程

    這篇文章主要為大家介紹了Golang?int函數(shù)使用實(shí)例全面教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • golang創(chuàng)建文件目錄os.Mkdir,os.MkdirAll的區(qū)別說(shuō)明

    golang創(chuàng)建文件目錄os.Mkdir,os.MkdirAll的區(qū)別說(shuō)明

    本文主要講述os.Mkdir、os.MkdirAll區(qū)別以及在創(chuàng)建文件目錄過(guò)程中的一些其他技巧,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-03-03
  • go語(yǔ)言的變量定義示例詳解

    go語(yǔ)言的變量定義示例詳解

    這篇文章主要為大家介紹了go語(yǔ)言的變量定義示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Go事務(wù)中止時(shí)是否真的結(jié)束事務(wù)解析

    Go事務(wù)中止時(shí)是否真的結(jié)束事務(wù)解析

    這篇文章主要為大家介紹了Go事務(wù)中止時(shí)是否真的結(jié)束事務(wù)實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Go語(yǔ)言編譯原理之變量捕獲

    Go語(yǔ)言編譯原理之變量捕獲

    這篇文章主要為大家介紹了Go語(yǔ)言編譯原理之變量捕獲示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Golang語(yǔ)言如何讀取http.Request中body的內(nèi)容

    Golang語(yǔ)言如何讀取http.Request中body的內(nèi)容

    這篇文章主要介紹了Golang語(yǔ)言如何讀取http.Request中body的內(nèi)容問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 試了下Golang實(shí)現(xiàn)try catch的方法

    試了下Golang實(shí)現(xiàn)try catch的方法

    雖然在使用Golang的時(shí)候發(fā)現(xiàn)沒(méi)有try catch這種錯(cuò)誤處理機(jī)制但是想一想golang作為一門(mén)優(yōu)雅的語(yǔ)言,似乎也是情理之中。那么夠怎么捕獲異常呢,本文就來(lái)介紹一下
    2021-07-07
  • Go語(yǔ)言中g(shù)o?mod?vendor使用方法

    Go語(yǔ)言中g(shù)o?mod?vendor使用方法

    go mod vendor的功能是將新增的依賴包自動(dòng)寫(xiě)入當(dāng)前項(xiàng)目的 vendor目錄,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言中g(shù)o?mod?vendor使用的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • 解析Golang中的GoPath和GoModule

    解析Golang中的GoPath和GoModule

    在Golang中,有兩個(gè)概念非常容易弄錯(cuò),第一個(gè)就是GoPath,第二個(gè)則是GoModule,很多初學(xué)者不清楚這兩者之間的關(guān)系,也就難以清晰地了解項(xiàng)目的整體結(jié)構(gòu),今天通過(guò)本文給大家介紹下Golang中的GoPath和GoModule相關(guān)知識(shí),感興趣的朋友一起看看吧
    2022-02-02

最新評(píng)論

筠连县| 隆回县| 府谷县| 丹寨县| 霍山县| 顺平县| 瑞昌市| 威远县| 普洱| 青州市| 华容县| 宜黄县| 石台县| 依安县| 佛学| 武山县| 四平市| 辽中县| 都安| 深泽县| 大方县| 长垣县| 兴城市| 连山| 贡山| 竹溪县| 济阳县| 桃园市| 东安县| 滦平县| 柯坪县| 玉门市| 颍上县| 嵩明县| 汉阴县| 莫力| 博兴县| 应城市| 木兰县| 永兴县| 青铜峡市|