支持分页的环形队列
- 源码
- 解析
-
- [PageCircularQueue 类](#PageCircularQueue 类)
- [readonly 函数](#readonly 函数)
- [PageCircularQueue.new 函数](#PageCircularQueue.new 函数)
- [PageCircularQueue:enqueue 函数](#PageCircularQueue:enqueue 函数)
- [PageCircularQueue:dequeue 函数](#PageCircularQueue:dequeue 函数)
- [PageCircularQueue:peek 函数](#PageCircularQueue:peek 函数)
- [PageCircularQueue:isFull 函数](#PageCircularQueue:isFull 函数)
- [PageCircularQueue:isEmpty 函数](#PageCircularQueue:isEmpty 函数)
- [PageCircularQueue:getSize 函数](#PageCircularQueue:getSize 函数)
- [PageCircularQueue:getRawPage 函数](#PageCircularQueue:getRawPage 函数)
- [PageCircularQueue:getPage 函数](#PageCircularQueue:getPage 函数)
最近我因工作需要使用环形队列,并在常规环形队列上拓展为支持分页环形队列,用于高效地管理大量数据,支持高效的元素添加、删除及分页数据的访问。通过分页的方式,它可以有效地管理大规模的数据集合
源码
lua
-- 创建一个分页的环形队列
local PageCircularQueue = {}
PageCircularQueue.__index = PageCircularQueue
local function readonly(t)
local mt = {
__index = t,
__newindex = function (t, k, v) error("Attempt to modify read-only table", 2) end,
__pairs = function () return pairs(t) end,
__ipairs = function () return ipairs(t) end,
__len = function () return #t end,
__metatable = false
}
return setmetatable({}, mt)
end
--tips: head指向的当前队列中最早入队的元素,tail指向的为最新入队的元素
---创建新的环形队列对象
---@param max_pages number @最大页
---@param page_capacity number @每页最大数量
---@field head_page number @最新页
---@field tail_page number @最尾页
---@field head number @头元素所在页的位置
---@field tail number @尾元素所在页的位置
---@field max_size number @队列容量
---@field size number @队列长度
---@field pages table @页数据
function PageCircularQueue.new(max_pages, page_capacity)
assert(max_pages > 0, "invalid max pages")
assert(page_capacity > 0, "invalid page capacity")
local self = setmetatable({}, PageCircularQueue)
self.max_pages = max_pages
self.page_capacity = page_capacity
self.pages = {} -- 存储每个页的数据
for i = 1, max_pages do
self.pages[i] = {}
end
self.head_page = 1
self.tail_page = 1
self.head = 1
self.tail = 0
self.max_size = self.max_pages * self.page_capacity
self.size = 0
return self
end
-- 向队列中添加数据
function PageCircularQueue:enqueue(value)
if value == nil then
INFO("PageCircularQueue enqueue value is nil")
return false
end
if self.size == self.max_size then
-- 队列已满,覆盖最旧的元素
if self.head == self.page_capacity then
self.head = 1
self.head_page = (self.head_page % self.max_pages) + 1
else
self.head = (self.head % self.page_capacity) + 1
end
else
self.size = self.size + 1
end
self.tail = (self.tail % self.page_capacity) + 1
self.pages[self.tail_page][self.tail] = value
if self.tail == self.page_capacity then
self.tail_page = (self.tail_page % self.max_pages) + 1
end
return true
end
-- 从队列中移除数据
function PageCircularQueue:dequeue()
if self.size == 0 then
return nil
end
local value = self.pages[self.head_page][self.head]
-- self.pages[self.head_page][self.head] = nil -- 因为会返回raw elements做遍历重建,删除仅做游标移动,不真正删除元素,保障为数组格式
if self.head == self.page_capacity then
self.head = 1
self.head_page = (self.head_page % self.max_pages) + 1
else
self.head = (self.head % self.page_capacity) + 1
end
self.size = self.size - 1
return value
end
function PageCircularQueue:peek()
if self.size == 0 then
return nil
end
return self.pages[self.head_page][self.head]
end
-- 检查队列是否已满
function PageCircularQueue:isFull()
return self.size >= self.max_size
end
-- 检查队列是否为空
function PageCircularQueue:isEmpty()
return self.size == 0
end
-- 获取队列的大小
function PageCircularQueue:getSize()
return self.size
end
-- 获取指定页的详细数据
function PageCircularQueue:getRawPage(page_index)
if page_index < 1 or page_index > self.max_pages then
return false, "page index out of bounds " .. page_index
end
page_index = (self.head_page + page_index - 2) % self.max_pages + 1
local elems = {}
local index = self.head
for i = 1, self.page_capacity do
local target = self.pages[page_index][index]
if not target then break end
elems[i] = target
if index == self.page_capacity then
index = 1
page_index = (page_index % self.max_pages) + 1
else
index = (index % self.page_capacity) + 1
end
end
return true, readonly(elems)
end
function PageCircularQueue:getPage(page_index)
if page_index < 1 or page_index > self.max_pages then
return false, "page index out of bounds " .. page_index
end
local begin_page_index = (self.head_page + page_index - 2) % self.max_pages + 1
local end_page_index = nil
-- 需额外多发下一页
if self.head ~= 1 then
end_page_index = (begin_page_index % self.page_capacity) + 1
end
return true, self.head, self.page_capacity, readonly(self.pages[begin_page_index] or {}), end_page_index and readonly(self.pages[end_page_index] or {}) or {}
end
return PageCircularQueue
--[[
使用示例
for i = 1, 115 do
self:enqueue(i)
end
local res, page_elems = self:getRawPage(1)
for k, v in ipairs(page_elems) do
DEBUG("res==> ", k, inspect(v))
end
只需通过getPage()将页数据同步到客户端,由客户端根据head page_capacity按需获取队列元素
例:head为1,则只需变量self.pages[begin_page_index]中数据即可
里:head为5,则1~6遍历self.pages[begin_page_index],7~10遍历self.pages[end_page_index]中数据
]]--
解析
PageCircularQueue 类
- PageCircularQueue 是一个表,用于表示环形队列的数据结构
- PageCircularQueue.__index = PageCircularQueue 设置元表,允许 PageCircularQueue 的实例通过 __index 查找方法和属性
readonly 函数
- readonly(t) 返回一个只读表,任何对表 t 的修改操作都会引发错误。这个表可以安全地暴露给外部使用者,用于限制外部修改其内容
PageCircularQueue.new 函数
这个函数用于创建一个新的 PageCircularQueue 实例
- max_pages:最大页数,即队列的页数
- page_capacity:每页的最大容量
- self.pages:一个表,用于存储每页的数据。初始化时,每页都是一个空表
- self.head_page 和 self.tail_page:分别指示队列的头页和尾页
- self.head 和 self.tail:指示当前页内头元素和尾元素的位置
- self.max_size:队列的总容量
- self.size:当前队列中的元素数量
PageCircularQueue:enqueue 函数
这个方法用于向队列中添加元素
- 如果队列已满(self.size == self.max_size),它会覆盖最旧的元素,即 head 指向的元素
- 如果 self.head 达到当前页的最大容量(self.page_capacity),则需要将 self.head 重置为1,并且 self.head_page 移动到下一页
- 更新 self.tail 和 self.tail_page 以插入新元素
- self.size 增加1
PageCircularQueue:dequeue 函数
这个方法用于从队列中移除元素
- 如果队列为空(self.size == 0),返回 nil
- 移除 self.head_page 页中的 self.head 位置的元素
- 更新 self.head 和 self.head_page,使其指向下一个元素
- self.size 减少1
PageCircularQueue:peek 函数
这个方法用于查看队列的头元素
- 如果队列为空,返回 nil
- 否则,返回 self.pages[self.head_page][self.head]
PageCircularQueue:isFull 函数
这个方法用于检查队列是否已满
- 如果 self.size 大于或等于 self.max_size,返回 true,否则返回 false
PageCircularQueue:isEmpty 函数
这个方法用于检查队列是否为空
- 如果 self.size 等于0,返回 true,否则返回 false
PageCircularQueue:getSize 函数
这个方法返回队列的当前大小,即 self.size
PageCircularQueue:getRawPage 函数
这个方法用于获取指定页的所有数据
- page_index 需要在有效范围内(1 到 self.max_pages)
- 计算页的实际索引 page_index,从 self.head_page 开始,读取该页的数据并返回
- 使用 readonly 包装返回的数据,确保外部无法修改
PageCircularQueue:getPage 函数
这个方法用于获取指定页的数据及其前后的页数据
- page_index 需要在有效范围内(1 到 self.max_pages)
- 计算起始页的实际索引,并且如果需要,还会获取下一页的数据
- 返回的数据被包装为 readonly 表,确保数据的安全性