<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
快速列表(quicklist)是Redis中特有的一種資料結構,主要是為了解決雙端連結串列的弊端:雙端連結串列的附加空間比較高,因為prev
和next
指標會佔掉一部分的空間(64位元系統佔用8 + 8 = 16位元組).而且連結串列的每個節點都是單獨分配記憶體,會加劇記憶體的碎片化。
Redis中的快速列表實際上是zipList(經過優化過的陣列)和linkedList的混合體,它把zipList放在linkedList的每個結點中,實現緊湊儲存。如下圖所示:
由於Go語言自帶slice這種操作方便的“動態陣列”結構,所以我給連結串列的每個節點中都分配一個容量為1024大小的切片,那麼一個連結串列節點就可以看作是一頁,頁大小就是1024。
頁大小
首先定義頁大小為1024:
// pageSize 一頁的大小 const pageSize = 1024
表頭結構
// QuickList 快速列表是在頁(切片)之上建立的連結串列 // QuickList 比普通連結串列的插入、遍歷以及記憶體有著更好的效能 type QuickList struct { data *list.List // 每一頁就是interface{}的切片,大小為1024 size int }
表頭結構中的data欄位直接使用了Go語言list
包中的List結構:
// 雙向連結串列檔頭結構 type List struct { root Element // 哨兵節點 len int // 連結串列的節點個數 }
// 雙向連結串列的節點 type Element struct { // 前向和後向指標 next, prev *Element // 表頭結構 list *List // 值域 Value interface{} }
Go語言自帶的list包實現了對雙向連結串列的封裝,雙向連結串列的節點元素是interface{}型別,利用這種方式實現了泛型。
我們對快速列表的實現,使得上述雙向連結串列節點中儲存的實際上是容量為1024的切片,此後對於連結串列的相關操作,直接呼叫list包向外暴露的API即可。
快速列表的迭代器中有三個欄位:連結串列的節點node(可以看成一頁),元素在頁中的偏移量、表頭結構。
這樣實現的迭代器,使得迭代器既可以在元素之前迭代,也可以在頁之間快速迭代。
// iterator 快速列表的迭代器,在[-1, ql.Len()]之間迭代 type iterator struct { // 快速列表的一頁 node *list.Element // 元素下標在頁中的偏移量 offset int ql *QuickList }
使用迭代器返回一個元素
使用迭代器返回一個元素的複雜度為O(1)
,一個元素的位置可以通過 頁的位置 + 該元素在頁中的位置 快速定位。
// 使用迭代器返回一個元素 func (iter *iterator) get() interface{} { return iter.page()[iter.offset] }
返回迭代器對應的那一頁
上面我們說過,連結串列的節點元素其實就是一個容量為1024的slice,通過型別斷言直接返回即可。
// 返回迭代器對應的那一頁 func (iter *iterator) page() []interface{} { return iter.node.Value.([]interface{}) }
根據元素下標返回對應的迭代器
快速列表查詢元素效率比雙向列表要快,首先利用迭代器一頁一頁進行迭代,當確定了元素在哪一頁後,利用元素的下標直接在頁內的slice中直接定位即可。
func (ql *QuickList) find(index int) *iterator { if ql == nil { panic("list is nil") } if index < 0 || index >= ql.size { panic("index out of bound") } var n *list.Element // 儲存當前頁的所有元素 var page []interface{} // 累加遍歷到當前頁為止,前面的所有元素數量 var pageBeg int if index < ql.size/2 { // 從表頭進行查詢 n = ql.data.Front() pageBeg = 0 for { page = n.Value.([]interface{}) if pageBeg+len(page) > index { break } pageBeg += len(page) n = n.Next() } } else { // 從表尾進行查詢 n = ql.data.Back() pageBeg = ql.size for { page = n.Value.([]interface{}) pageBeg -= len(page) if pageBeg <= index { break } n = n.Prev() } } pageOffset := index - pageBeg return &iterator{ node: n, offset: pageOffset, ql: ql, } }
向後迭代一位
// next 頁內迭代器,向後迭代一位 // 如果當前元素下標未出界且不在最後一位,就向後移動一位,返回true // 如果當前元素下標在快速列表的最後一頁且是最後一個元素,直接返回false // 如果當前元素下標不在快速列表的最後一頁,但是是當前頁的最後一個元素,跳轉到下一頁,返回true func (iter *iterator) next() bool { // 得到迭代器對應的那一頁 page := iter.page() // 當前位置未出界且不在最後一位,就向後移動一位,返回true if iter.offset < len(page)-1 { iter.offset++ return true } // 當前元素在快速列表的最後一頁且是最後一個元素,直接返回false if iter.node == iter.ql.data.Back() { // already at last node iter.offset = len(page) return false } // 當前元素不在快速列表的最後一頁,但是是當前頁的最後一個元素,跳轉到下一頁,返回true iter.offset = 0 iter.node = iter.node.Next() return true }
往前迭代一位
// prev 頁內迭代器,向前迭代一位 func (iter *iterator) prev() bool { if iter.offset > 0 { iter.offset-- return true } if iter.node == iter.ql.data.Front() { iter.offset = -1 return false } iter.node = iter.node.Prev() prevPage := iter.node.Value.([]interface{}) iter.offset = len(prevPage) - 1 return true }
向表尾新增一個元素
向表尾新增元素需要考慮三種情況:
// Add 新增元素到表尾 func (ql *QuickList) Add(val interface{}) { ql.size++ // 列表是空的 if ql.data.Len() == 0 { page := make([]interface{}, 0, pageSize) page = append(page, val) ql.data.PushBack(page) return } // 獲取表尾節點 backNode := ql.data.Back() backPage := backNode.Value.([]interface{}) // 表尾節點頁滿了,需要新建立一頁 if len(backPage) == cap(backPage) { page := make([]interface{}, 0, pageSize) page = append(page, val) ql.data.PushBack(page) return } // 預設將節點新增進表尾頁中 backPage = append(backPage, val) backNode.Value = backPage }
根據下標插入一個元素
// Insert 插入元素 // 插入元素的策略分三種情況: // 1. 向最後一頁的最後一個位置插入元素,直接掉用ql.Add()插入即可 // 2. 某一頁插入一個元素,且該頁未滿,直接插入該頁即可 // 3. 某一頁插入一個元素,該頁滿了,就新建立一頁,然後將前512個元素留在原來那頁,將後512個元素移到新的頁中, // 新插入的元素,如果下標在[0,512]之間,就插入到原來頁,如果下標在[516, 1024]之間,就插入到新建立的頁中 func (ql *QuickList) Insert(index int, val interface{}) { // 向表尾插入元素 if index == ql.size { ql.Add(val) return } iter := ql.find(index) page := iter.node.Value.([]interface{}) // 如果待插入頁的元素小於1024,直接插入到該頁即可 if len(page) < pageSize { // insert into not full page page = append(page[:iter.offset+1], page[iter.offset:]...) page[iter.offset] = val iter.node.Value = page ql.size++ return } // 待插入頁的元素已經滿1024,就需要新建立一頁 var nextPage []interface{} // 後一半的元素放在新建立的頁中,前一半元素放在原來的頁中 nextPage = append(nextPage, page[pageSize/2:]...) // pageSize must be even page = page[:pageSize/2] // 待插入元素的下標小於512,插到前面那頁 if iter.offset < len(page) { page = append(page[:iter.offset+1], page[iter.offset:]...) page[iter.offset] = val } else { // 待插入元素的下標大於512,插到後面那頁 i := iter.offset - pageSize/2 nextPage = append(nextPage[:i+1], nextPage[i:]...) nextPage[i] = val } // 儲存當前頁和新建立的下一頁 iter.node.Value = page ql.data.InsertAfter(nextPage, iter.node) ql.size++ }
// 刪除元素 // 刪除元素分為三種情況: // 1.刪除後的頁不為空,且刪除的不是該頁的最後一個元素,什麼都不用管 // 2.刪除後的頁不為空,且刪除的是該頁的最後一個元素,需要將迭代器移動到下一頁的最後一個元素 // 3.刪除的頁為空(需要刪除該頁),且刪除的頁是最後一頁,將迭代器置空 // 4.刪除的頁為空(需要刪除該頁),且刪除的頁不是最後一頁,將迭代器指向下一頁 func (iter *iterator) remove() interface{} { page := iter.page() val := page[iter.offset] // 先直接在頁中刪除這個元素 page = append(page[:iter.offset], page[iter.offset+1:]...) // 如果刪除後的頁不為空,只更新iter.offset即可 if len(page) > 0 { iter.node.Value = page // 如果刪除的是頁中的最後一個元素,那麼迭代器需要移動到下一頁的第一個元素 if iter.offset == len(page) { if iter.node != iter.ql.data.Back() { iter.node = iter.node.Next() iter.offset = 0 } } } else { // 如果刪除後的頁為空,需要刪除該頁 // 如果刪除的是最後一頁,迭代器需要置空 if iter.node == iter.ql.data.Back() { iter.ql.data.Remove(iter.node) iter.node = nil iter.offset = 0 } else { // 如果刪除的不是最後一頁,迭代器需要指向下一頁 nextNode := iter.node.Next() iter.ql.data.Remove(iter.node) iter.node = nextNode iter.offset = 0 } } iter.ql.size-- return val }
// Consumer 用於遍歷中斷的函數,返回true表示繼續遍歷,可以在Consumer中呼叫自定義函數 type Consumer func(i int, v interface{}) bool
// ForEach 遍歷快速列表中的元素 // 如果consumer返回false,結束遍歷 func (ql *QuickList) ForEach(consumer Consumer) { if ql == nil { panic("list is nil") } if ql.Len() == 0 { return } iter := ql.find(0) i := 0 for { goNext := consumer(i, iter.get()) if !goNext { break } i++ // 遍歷到表尾,結束 if !iter.next() { break } } }
https://github.com/omlight95/GoRedis/blob/master/datastruct/list/quicklist.go
到此這篇關於GoLang完整實現快速列表的文章就介紹到這了,更多相關Go快速列表內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45