广告
返回顶部
首页 > 资讯 > 后端开发 > GO >GoLang完整实现快速列表
  • 1321
分享到

GoLang完整实现快速列表

GoLang快速列表Go实现快速列表 2022-12-17 18:12:23 1321人浏览 八月长安
摘要

目录快速列表介绍实现快速列表快速列表的结构快速列表的迭代器添加和插入元素删除元素遍历快速列表完整实现快速列表介绍 快速列表(quicklist)是Redis中特有的一种数据结构,主要

快速列表介绍

快速列表(quicklist)是Redis中特有的一种数据结构,主要是为了解决双端链表的弊端:双端链表的附加空间比较高,因为prevnext指针会占掉一部分的空间(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快速列表内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: GoLang完整实现快速列表

本文链接: https://www.lsjlt.com/news/175170.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • GoLang完整实现快速列表
    目录快速列表介绍实现快速列表快速列表的结构快速列表的迭代器添加和插入元素删除元素遍历快速列表完整实现快速列表介绍 快速列表(quicklist)是Redis中特有的一种数据结构,主要...
    99+
    2022-12-17
    GoLang快速列表 Go实现快速列表
  • Golang列表怎么实现
    本文小编为大家详细介绍“Golang列表怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang列表怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。列表是一种常见的数据结构,在Golang中也不...
    99+
    2023-07-05
  • Golang怎么实现快速求幂
    这篇文章主要介绍了Golang怎么实现快速求幂的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang怎么实现快速求幂文章都会有所收获,下面我们一起来看看吧。为方便起见,此处假设m>=0,对于m<...
    99+
    2023-07-02
  • golang如何实现报销?完整流程浅析
    在众多编程语言中,Go语言(简称golang)因其高效、简单且易于维护而备受青睐。随着该语言的流行度不断提高,它被越来越多的企业用于开发各种应用程序,包括报销流程的应用程序。一个完整的golang报销流程,包含以下几个步骤:登录用户首先登录...
    99+
    2023-05-14
  • Golang模块引入及表格读写业务快速实现示例
    目录介绍正文配置模块引入环境引入excelize库创建表格读取表格写入表格结语介绍 在很多管理系统下都有不少让后端进行表格进行操作的业务需求,本期就带大家了解一下Golang中如何使...
    99+
    2022-11-11
  • Flutter快速实现聊天会话列表效果示例详解
    目录一、目标效果二、原理1、 涉及的方法2、实现逻辑三、使用四、最后一、目标效果 聊天会话页的列表效果 1、聊天数据不满一屏时,顶部显示所有聊天数据2、插入消息时如果最新消息紧靠列表...
    99+
    2022-11-13
  • Golang实现快速求幂的方法详解
    今天讲个有趣的算法:如何快速求nm,其中n和m都是整数。 为方便起见,此处假设m>=0,对于m< 0的情况,求出n|m|后再取倒数即可。 另外此处暂不考虑结果越界的情况(...
    99+
    2022-11-13
  • Python如何快速实现分列转到行
    这篇“Python如何快速实现分列转到行”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python如何快速实现分列转到行”文...
    99+
    2023-07-05
  • Java实现一个顺序表的完整代码
    实现一个顺序表 接口实现 定义一个MyArrayList类,在类中实现以下函数 public class MyArrayList { } 数组的定义 public ...
    99+
    2022-11-12
  • ant desing vue table 实现可伸缩列的完整例子
    完美解决ant-design-vue table 可伸缩列问题,实现在固定列,多级表头情况下的伸缩列 这个东西本来以为手到擒来,因为在官网中已经给出了例子,但是果然还是不能太信任官方...
    99+
    2022-11-12
  • python递归实现链表快速倒转
    本文实例为大家分享了python递归实现链表快速倒转的具体代码,供大家参考,具体内容如下 案例:实现如下链表进行倒转 源代码: ''' Node 用于表示队列中的节点;它包含两个域...
    99+
    2022-11-10
  • 基于python快速实现排列组合算法
    1.python语言简单、方便,其内部可以快速实现排列组合算法,下面做简单介绍、 2.一个列表数据任意组合 2.1主要是利用自带的库 #_*_ coding:utf-8 _*_ #__author__='dragon' impor...
    99+
    2023-01-31
    算法 排列组合 快速
  • python使用redis实现消息队列(异步)的实现完整例程
    目录安装相关库消息队列实现及使用创建配置文件代码实现最近在用fastapi框架开发web后端,由于近几年python异步编程大火,fastapi凭借高性能也火了起来。本篇介绍了在异步环境下实现Redis消息队列的方法,代...
    99+
    2023-01-18
    pythonredis消息队列 pythonredis异步
  • golang归并排序,快速排序,堆排序的实现
    归并排序 归并排序使用经典的分治法(Divide and conquer)策略。分治法会将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得...
    99+
    2022-11-13
  • 数据库的备份与还原系列——单表备份和恢复详细完整实现
    参考实现:https://www.percona.com/doc/percona-xtrabackup/LATEST/innobackupex/innobackupex_script.htmlRestori...
    99+
    2022-10-18
  • Python快速实现分列转到行的示例代码
    之前看到Amily的一篇文章,用Excel快速实现分列转到行的操做。 数据源大致是这样的: 基于此,我动起了一个念头:看看如何用Python快速实现这个操作。 数据源已经构造好,咱...
    99+
    2023-05-13
    Python实现分列转行 Python分列转行 Python 分列
  • Java实现生成Excel树形表头完整代码示例
    本文主要分享了Java实现生成Excel树形表头完整代码示例,没有什么好解释的,直接看看代码过程。源数据格式:String[] targetNames = { "指标名称", "单位", ...
    99+
    2023-05-30
    java excel表头 ava
  • 邻接表无向图的Java语言实现完整源码
    邻接表无向图的介绍邻接表无向图是指通过邻接表表示的无向图。上面的图G1包含了”A,B,C,D,E,F,G”共7个顶点,而且包含了”(A,C),(A,D),(A,F),(B,C),(C,D),(E,G),(F,G)”共7条边。上图右边的矩阵是...
    99+
    2023-05-30
    java 邻接表 无向图
  • 如何快速简单的实现Excel数据按列提取
    这期内容当中小编将会给大家带来有关如何快速简单的实现Excel数据按列提取,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一般常规办法:使用第三方类库(POI或者HSSFWorkbook等)来读取EXCEL...
    99+
    2023-06-03
  • 怎么用python递归实现链表快速倒转
    这篇文章主要介绍“怎么用python递归实现链表快速倒转”,在日常操作中,相信很多人在怎么用python递归实现链表快速倒转问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么用python递归实现链表快速倒转...
    99+
    2023-06-30
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作