转载说明出处,谢谢!
为了更好的阅读体验,可移步至:http://zhuzhuyule.xyz/FastQueue
1 队列简介
队列(Queue)
是运算受限的线性表。一种先进先出(First In First Out ,简称FIFO)的线性表。只允许在表的一端进行插入,而在另一端进行删除。先进先出 是队列特有的性质。
举例来说
- 我们购买东西需要排队,而这个排好的队伍就是队列
。先来的人可以先买到东西离开。
- 乘坐电动扶梯时,乘客都是从扶梯的一头进入,另一头离开电梯。先上电梯的乘客,先下电梯。
2 逻辑与实现
2.1 队列的基本功能
理解完队列
概念,再来列出队列
具有的功能:
插入
向队列 队尾 增加一个对象。
弹出
从队列 队首 弹出一个对象。
计数
计算当前队列中数据的总个数。
2.2 队列的需要的变量
好了,功能也有了,现在来更具这些功能来准备准备我们的材料:
单位容量
FCapacity
总要告诉内存你每次打算征用人家多少区域来给你排队用吧。
一块内存
FMem
更具你的单位容量,去申请‘场地’来排队用,用完了再来申请。
队尾计数器
FPopIndex
有了它,你就知道你在队伍里排的多少号了。
队首计数器
FPushIndex
有了它,你就只当队伍最前面的人拍的是几号了。
2.3 队列实现
2.3.1 初始化
先来初始化队列,设置容量并重置计数器。
PageCapacity = 7;
//初始化 计数器
FPushIndex = 0;
FPopIndex = 0;
//更具容量申请 固定大小的空间
SetLength(FMem, PageCapacity);
FCapacity = PageCapacity;
2.3.2 载入数据
向队列加载数据时,队尾计数器自加,自加后如果发现 排号和容量大小一样了,说明空间不够了,就向商场(内存)申请连续的扩容空间(已用空间大小+单位容量)
//新来的元素放到空间末尾
FMem[FPushIndex] = NewItem;
//每加载一个新的元素进来,都要增加FPushIndex
FPushIndex = FPushIndex + 1;
//这里还需要保证 队尾 计数器始终小于 总容量 ,否则 要扩容
if (FPushIndex >= FCapacity)
SetLength(FMem, FCapacity+ PageCapacity);
FCapacity = FCapacity + PageCapacity;
2.3.3 弹出数据
好了,现在队伍有了,该卖东西了,正真的队伍是卖一个走一个,所以我们按照号码(内存下标)来叫号卖。
//按照 队首 下标开始 获取元素( FPopIndex 前提小于 排队人数 FPushIndex )
if FPopIndex < FPushIndex
PopItem = FMem[FPopIndex];
FPopIndex = FPopIndex + 1; //每弹出一个元素,都要增加FPopIndex
弹出数据的过程中也会有新的数据来加载,但是不影响我们改变内存的规律:
载入数据
- 直接将数据放到队列末尾
- 队尾计数器 +1
- 判断内存是否足够
弹出数据
- 拿出队首计数器指向数据
- 队首计数器 +1
2.4 代码实现
废话说了这么多,好了贴代码。
代码功能说明:
- 基本功能
- 载入功能
- 弹出功能
- 清空队列功能
- 队列计数功能
- 自动扩容功能
- 拓展功能
- 为了线程安全,使用临界区加锁控制
- 字符串加载/弹出功能
- 容量设置功能
代码如下:
unit FastDemo;
interface
//线程安全版本
uses
Windows, SysUtils;
const
CONST_BYTE_SIZE = 4;
CONST_PAGE_SINGLE = 1024;
COSNT_PAGE_SINGLE_SIZE = CONST_PAGE_SINGLE * CONST_BYTE_SIZE;
type
TFastQueue = class
private
FCS: TRTLCriticalSection;
//
FMem: Pointer;
FTmpMem: Pointer;
//
FPushIndex: Integer; //压入队列计数器
FPopIndex: Integer; //弹出队列计数器
FCapacity: Integer; //队列容量,始终大于 FPushIndex
//
procedure Lock();
procedure UnLock();
procedure SetCapacity(const AValue: Integer);
//
procedure setSynCapacity(const AValue: Integer);
function getSynCapacity: Integer;
function getSynCurCount: Integer;
public
constructor Create(AInitCapacity: Integer = CONST_PAGE_SINGLE);
destructor Destroy(); override;
//
function Push(AItem: Pointer): Pointer;
function Pop(): Pointer;
function PushString(AItem: string): Pointer;
function PopString(): string;
procedure Clear;
public
property Capacity: Integer read getSynCapacity write setSynCapacity;
property Count: Integer read getSynCurCount;
end;
implementation
{ TFastQueue }
constructor TFastQueue.Create(AInitCapacity: Integer);
begin
InitializeCriticalSection(FCS);
FPushIndex := 0;
FPopIndex := 0;
if AInitCapacity < CONST_PAGE_SINGLE then
AInitCapacity := CONST_PAGE_SINGLE;
SetCapacity(AInitCapacity);
end;
destructor TFastQueue.Destroy;
begin
FreeMem(FMem);
if FTmpMem <> nil then
FreeMem(FTmpMem);
DeleteCriticalSection(FCS);
inherited;
end;
procedure TFastQueue.Lock;
begin
EnterCriticalSection(FCS);
end;
procedure TFastQueue.UnLock;
begin
LeaveCriticalSection(FCS);
end;
procedure TFastQueue.SetCapacity(const AValue: Integer);
var
vPageCount, vOldSize, vNewSize: Integer;
begin
if AValue > FCapacity then
begin
if FTmpMem <> nil then
FreeMem(FTmpMem);
vPageCount := AValue div CONST_PAGE_SINGLE;
if (AValue mod CONST_PAGE_SINGLE) > 0 then
Inc(vPageCount);
//保存旧的容量
vOldSize := FCapacity * CONST_BYTE_SIZE;
//计算新的容量
FCapacity := vPageCount * CONST_PAGE_SINGLE;
vNewSize := FCapacity * CONST_BYTE_SIZE;
//扩容
GetMem(FTmpMem, vNewSize);
FillChar(FTmpMem^, vNewSize, #0);
//转移数据
if FMem <> nil then
begin
Move(FMem^, FTmpMem^, vOldSize);
FreeMem(FMem);
end;
FMem := FTmpMem;
//FTmpMem (保证弹出、插入数据时使用) 与 FMem 大小一致
GetMem(FTmpMem, vNewSize);
end;
end;
function TFastQueue.Push(AItem: Pointer): Pointer;
var
vPMem: PInteger;
vNextPriority, vPriority, vIndex: Integer;
vNotPushed: boolean;
begin
Lock();
try
vPMem := PInteger(FMem);
Inc(vPMem, FPushIndex);
vPMem^ := Integer(AItem);
Inc(FPushIndex);
//检测栈容量是否足够(至少保留一位空位,否则扩容 1024)
if FPushIndex >= FCapacity then
begin
SetCapacity(FCapacity + CONST_PAGE_SINGLE);
end;
finally
UnLock();
end;
end;
function TFastQueue.Pop: Pointer;
procedure MoveMem();
var
vvPSrc: PInteger;
vvTmpMem: Pointer;
begin
FillChar(FTmpMem^, FCapacity * CONST_BYTE_SIZE, #0);
vvPSrc := PInteger(FMem);
Inc(vvPSrc, FPopIndex);
Move(vvPSrc^, FTmpMem^, (FCapacity - FPopIndex) * CONST_BYTE_SIZE);
//交换指针
vvTmpMem := FMem;
FMem := FTmpMem;
FTmpMem := vvTmpMem;
end;
var
vPMem: PInteger;
begin
Lock();
try
Result := nil;
if (FPopIndex = FPushIndex) then
Exit;
// 1.获取弹出元素
vPMem := PInteger(FMem);
Inc(vPMem, FPopIndex);
Result := Pointer(vPMem^);
// 2.弹出元素后 弹出计数器 +1
Inc(FPopIndex);
// 3.队列底部空余内存达到 1024 整体迁移
if FPopIndex = CONST_PAGE_SINGLE then
begin
//迁移数据
MoveMem();
//重置弹出位置
FPopIndex := 0;
//减少压入队列的数量
Dec(FPushIndex, CONST_PAGE_SINGLE);
end;
finally
UnLock();
end;
end;
function TFastQueue.PushString(AItem: string): Pointer;
var
vPChar: PChar;
begin
vPChar := StrAlloc(256);
StrCopy(vPChar, pchar(AItem + ' |' + inttostr(FPushIndex)));
Push(vPChar);
end;
function TFastQueue.PopString: string;
var
vPChar: PChar;
begin
Result := 'nil';
vPChar := Pop;
if vPChar <> '' then
begin
Result := vPChar;
StrDispose(vPChar);
end;
end;
procedure TFastQueue.Clear;
begin
Lock();
try
FPushIndex := 0;
FPopIndex := 0;
SetCapacity(CONST_PAGE_SINGLE);
finally
UnLock();
end;
end;
procedure TFastQueue.setSynCapacity(const AValue: Integer);
begin
Lock();
try
SetCapacity(AValue);
finally
UnLock();
end;
end;
function TFastQueue.getSynCapacity: Integer;
begin
Lock();
try
Result := FCapacity;
finally
UnLock();
end;
end;
function TFastQueue.getSynCurCount: Integer;
begin
Lock();
try
Result := FPushIndex - FPopIndex;
finally
UnLock();
end;
end;
end.
2.5 使用Demo
procedure UseFastQueue();
var
vQueue: TFastQueue;
begin
vQueue := TFastQueue.Create;
try
vQueue.Push('A1');
vQueue.Push('A2');
vQueue.Push('A3');
vQueue.Push('A4');
vQueue.Push('A5');
vQueue.Push('A6');
while vQueue.Count > 0 do
begin
OutputDebugString(vQueue.PopString);
end;
finally
vQueue.Free;
vQueue := nil;
end;
end;
输出:
A1
A2
A3
A4
A5