前言
队列(Queue)是一种遵循先进先出(FIFO,First In First Out)原则的线性数据结构。在 iOS 开发中,我们经常需要使用队列来处理各种场景,比如:任务调度、消息队列、事件处理等。
在上一篇文章《数据结构:循环数组详解》中,我们详细介绍了循环数组的原理。本文将基于实际项目中的代码,深入对比循环数组实现的队列和链表实现的队列这两种方案,帮助你在实际开发中做出正确的选择。
队列的基本概念
什么是队列?
队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,在表的后端(rear)进行插入操作。
1 2 3 4 5 6
| 入队 (enqueue) 出队 (dequeue) ↓ ↑ +---+---+---+---+---+ | 5 | 4 | 3 | 2 | 1 | ← 队列 +---+---+---+---+---+ 队尾 (tail) 队头 (front)
|
队列的基本操作
- enqueue(入队):在队尾添加一个元素
- dequeue(出队):移除并返回队头元素
- peek(查看队头):查看队头元素但不移除
- isEmpty(判空):判断队列是否为空
- count(计数):获取队列中元素的个数
方案一:基于循环数组的队列实现
设计思路
循环数组实现的队列(LitQueue
)使用固定大小的数组作为底层存储,通过维护 front
指针和 count
计数器来管理队列状态。
核心数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @interface LitQueue ()
@property (nonatomic, strong) NSMutableArray *items;
@property (nonatomic, assign) NSInteger front;
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, assign) NSInteger capacity;
@end
|
关键设计要点
- 预分配空间:创建队列时预先分配固定容量,用
NSNull
填充占位
- 循环索引计算:通过取模运算实现循环访问
- 动态扩容/缩容:满时扩容为 2 倍,使用率低于 25% 时缩容为一半
核心实现代码
1. 初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| - (instancetype)initWithCapacity:(NSInteger)capacity { if (self = [super init]) { _capacity = capacity; _items = [NSMutableArray arrayWithCapacity:capacity]; for (NSInteger i = 0; i < capacity; i++) { [_items addObject:[NSNull null]]; } _front = 0; _count = 0; } return self; }
|
设计细节:
- 预填充
NSNull
是为了让数组达到指定容量,后续可以直接用下标访问
- 如果不预填充,使用
items[index] = obj
会 Crash
- 不预填充的话只能用
addObject
,会导致数组频繁扩容
2. 入队操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| - (void)enqueue:(id)object { if (!object) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Cannot enqueue nil object" userInfo:nil]; return; } if (self.count == self.capacity) { [self resize:self.capacity * 2]; } NSInteger tail = (self.front + self.count) % self.capacity; self.items[tail] = object; self.count++; }
|
关键点:
- 队尾位置计算公式:
tail = (front + count) % capacity
- 满时触发扩容,扩容为原容量的 2 倍
- 时间复杂度:O(1) 摊销(扩容时为 O(n))
3. 出队操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| - (id)dequeue { if (self.isEmpty) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot dequeue from an empty queue" userInfo:nil]; return nil; } id object = self.items[self.front]; self.items[self.front] = [NSNull null]; self.front = (self.front + 1) % self.capacity; self.count--; if (self.count > 0 && self.count == self.capacity / 4 && self.capacity > kDefaultCapacity) { [self resize:self.capacity / 2]; } return object; }
|
关键点:
- 队头指针循环移动:
front = (front + 1) % capacity
- 清空旧位置防止对象被强引用导致内存泄漏
- 智能缩容:使用率低于 25% 时缩容,避免频繁缩容导致的性能抖动
- 时间复杂度:O(1)(大部分情况),缩容时为 O(n)
4. 扩容/缩容实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| - (void)resize:(NSInteger)newCapacity { NSMutableArray *newItems = [NSMutableArray arrayWithCapacity:newCapacity]; for (NSInteger i = 0; i < newCapacity; i++) { [newItems addObject:[NSNull null]]; } for (NSInteger i = 0; i < self.count; i++) { NSInteger oldIndex = (self.front + i) % self.capacity; newItems[i] = self.items[oldIndex]; } self.items = newItems; self.capacity = newCapacity; self.front = 0; }
|
设计亮点:
- 扩容时将所有元素按顺序重新排列到新数组
- 重置
front
为 0,简化后续操作
- 时间复杂度:O(n)
内存布局示意
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 初始状态(capacity = 5, count = 0, front = 0): 0 1 2 3 4 [null][null][null][null][null] ↑ front
入队 A, B, C 后(count = 3, front = 0): 0 1 2 3 4 [ A ][ B ][ C ][null][null] ↑ ↑ front tail
出队 A, B 后,入队 D, E, F(count = 4, front = 2): 0 1 2 3 4 [ E ][ F ][ C ][ D ][null] ↑ ↑ front tail (循环到 1)
此时队列逻辑顺序是:C -> D -> E -> F
|
方案二:基于链表的队列实现
设计思路
链表实现的队列(LitLinkedQueue
)使用单向链表作为底层存储,通过维护 head
和 tail
两个指针来管理队列。
核心数据结构
1 2 3 4 5 6 7 8 9 10 11 12
| @interface LitLinkedQueueNode : NSObject @property (nonatomic, strong) id value; @property (nonatomic, strong, nullable) LitLinkedQueueNode *next; @end
@interface LitLinkedQueue () @property (nonatomic, strong, nullable) LitLinkedQueueNode *head; @property (nonatomic, strong, nullable) LitLinkedQueueNode *tail; @property (nonatomic, assign) NSInteger count; @end
|
关键设计要点
- 双指针管理:维护队头和队尾指针,实现 O(1) 的入队和出队
- 按需分配:不需要预分配空间,动态增长
- 简单高效:无需扩容操作,所有操作都是 O(1)
核心实现代码
1. 入队操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| - (void)enqueue:(id)object { if (!object) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Cannot enqueue nil object" userInfo:nil]; return; } LitLinkedQueueNode *newNode = [LitLinkedQueueNode nodeWithValue:object]; if (self.tail) { self.tail.next = newNode; self.tail = newNode; } else { self.head = newNode; self.tail = newNode; } _count++; }
|
关键点:
- 始终在队尾添加新节点
- 特殊处理空队列的情况
- 时间复杂度:O(1),无扩容开销
2. 出队操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| - (id)dequeue { if (self.isEmpty) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot dequeue from an empty queue" userInfo:nil]; return nil; } id value = self.head.value; self.head = self.head.next; if (!self.head) { self.tail = nil; } _count--; return value; }
|
关键点:
- 直接移动
head
指针,旧节点会被 ARC 自动回收
- 队列变空时需要同步清空
tail
指针
- 时间复杂度:O(1),真正的常数时间
3. 清空队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| - (void)removeAllObjects { LitLinkedQueueNode *current = self.head; while (current) { LitLinkedQueueNode *next = current.next; current.next = nil; current.value = nil; current = next; } self.head = nil; self.tail = nil; _count = 0; }
|
设计细节:
- 显式断开所有节点的
next
和 value
引用
- 帮助 ARC 更快地回收内存,避免循环引用
- 虽然只设置
head = nil
也能让 ARC 最终回收,但显式断开更高效
内存布局示意
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 空队列: head → nil tail → nil
入队 A 后: head → [A | next:nil] ← tail 入队 B, C 后: head → [A | next] → [B | next] → [C | next:nil] ← tail
出队 A 后: head → [B | next] → [C | next:nil] ← tail 注意:旧的节点 A 会被 ARC 自动回收
|
两种实现方式的深度对比
性能对比
操作 |
循环数组 (LitQueue) |
链表 (LitLinkedQueue) |
入队 (enqueue) |
O(1) 摊销 (扩容时 O(n)) |
O(1) 真正常数时间 |
出队 (dequeue) |
O(1) (缩容时 O(n)) |
O(1) 真正常数时间 |
查看队头 (peek) |
O(1) |
O(1) |
查看队尾 (last) |
O(1) |
O(1) |
随机访问 |
O(1) 支持 |
O(n) 不支持 |
空间开销(每个元素) |
8 字节(指针) |
16 字节(对象+指针) |
初始化 |
O(n) 需预分配 |
O(1) 无需预分配 |
清空队列 |
O(n) |
O(n) |
内存特性对比
1. 内存占用
循环数组(LitQueue):
1 2 3 4 5 6 7
| 基础开销: - NSMutableArray 对象:约 32 字节 - 数组内存:capacity × 8 字节(每个指针)
实际占用示例(capacity = 100, count = 50): 32 + 100 × 8 = 832 字节 空间利用率 = 50 / 100 = 50%
|
链表(LitLinkedQueue):
1 2 3 4 5 6 7
| 基础开销: - LitLinkedQueue 对象:约 32 字节 - 每个节点:约 32 字节(对象头 + value + next 指针)
实际占用示例(count = 50): 32 + 50 × 32 = 1632 字节 空间利用率 = 100%(按需分配)
|
2. 缓存友好性
循环数组:
- ✅ 内存连续,CPU 缓存命中率高
- ✅ 适合现代 CPU 的预取机制
- ✅ 遍历性能优秀
链表:
- ❌ 内存分散,缓存命中率低
- ❌ 节点跳转导致缓存失效
- ⚠️ 遍历性能相对较差
3. 内存碎片
循环数组:
- ✅ 单次大块分配,碎片少
- ❌ 扩容时可能产生内存峰值
链表:
- ⚠️ 频繁小对象分配,可能产生碎片
- ✅ 无扩容峰值
优缺点总结
循环数组(LitQueue)
优点:
- ✅ 缓存友好:连续内存,遍历性能优秀
- ✅ 支持随机访问:可通过索引快速访问任意位置元素
- ✅ 内存占用相对较小:没有额外的节点对象开销
- ✅ 空间局部性好:对 CPU 缓存友好
缺点:
- ❌ 需要预分配容量:初始化时需要指定大小
- ❌ 扩容开销:满时需要 O(n) 的扩容操作,可能导致性能抖动
- ❌ 空间浪费:容量大于实际元素数时会浪费空间
- ❌ 扩容时内存峰值:扩容期间会同时存在新旧两个数组
链表(LitLinkedQueue)
优点:
- ✅ 真正的 O(1) 操作:所有操作都是常数时间,无扩容
- ✅ 无需预分配:按需分配,初始化快速
- ✅ 动态增长:天然支持任意大小,不会满
- ✅ 无内存峰值:没有扩容,内存增长平稳
- ✅ 实现简单:代码更简洁,易于理解和维护
缺点:
- ❌ 节点开销:每个元素需要额外的节点对象(约 32 字节)
- ❌ 缓存不友好:内存分散,CPU 缓存命中率低
- ❌ 不支持随机访问:访问第 n 个元素需要 O(n) 时间
- ❌ 内存碎片:频繁分配小对象可能产生碎片
代码复杂度对比
循环数组:
- 代码量:约 210 行
- 需要处理:扩容、缩容、循环索引计算
- 维护成本:较高
链表:
- 代码量:约 190 行
- 逻辑简单:只需维护头尾指针
- 维护成本:较低
适用场景分析
选择循环数组(LitQueue)的场景
队列大小可预估且相对固定
1 2
| LitQueue *audioBuffer = [LitQueue queueWithCapacity:1024];
|
需要频繁遍历队列
1 2 3 4 5
| NSArray *tasks = [queue toArray]; for (Task *task in tasks) { [task checkStatus]; }
|
对内存占用敏感
1 2 3
|
LitQueue *messageQueue = [LitQueue queueWithCapacity:100];
|
对性能稳定性要求不高
1 2
| LitQueue *backgroundTasks = [LitQueue queueWithCapacity:50];
|
需要随机访问队列元素
选择链表(LitLinkedQueue)的场景
队列大小不可预估或变化剧烈
1 2
| LitLinkedQueue *requestQueue = [LitLinkedQueue queue];
|
对性能稳定性要求高
1 2 3
|
LitLinkedQueue *frameQueue = [LitLinkedQueue queue];
|
入队出队频繁,很少遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| LitLinkedQueue *workQueue = [LitLinkedQueue queue];
dispatch_async(producerQueue, ^{ while (producing) { id work = [self produceWork]; [workQueue enqueue:work]; } });
dispatch_async(consumerQueue, ^{ while (consuming) { if (!workQueue.isEmpty) { id work = [workQueue dequeue]; [self consumeWork:work]; } } });
|
需要优先考虑代码简洁性
1 2
| LitLinkedQueue *eventQueue = [LitLinkedQueue queue];
|
频繁创建和销毁队列
1 2 3 4 5 6 7
|
for (int i = 0; i < 100; i++) { LitLinkedQueue *tempQueue = [LitLinkedQueue queue]; [self processWithQueue:tempQueue]; }
|
实际项目场景举例
场景 1:图片下载队列(推荐链表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @interface ImageDownloader : NSObject @property (nonatomic, strong) LitLinkedQueue<ImageDownloadTask *> *downloadQueue; @end
@implementation ImageDownloader
- (instancetype)init { if (self = [super init]) { _downloadQueue = [LitLinkedQueue queue]; } return self; }
- (void)downloadImage:(NSURL *)url completion:(void(^)(UIImage *))completion { ImageDownloadTask *task = [[ImageDownloadTask alloc] initWithURL:url completion:completion]; [self.downloadQueue enqueue:task]; [self processNextTask]; }
- (void)processNextTask { if (!self.downloadQueue.isEmpty) { ImageDownloadTask *task = [self.downloadQueue dequeue]; [self executeTask:task]; } }
@end
|
选择理由:
- 下载任务数量不可预估
- 频繁入队出队,很少遍历
- 需要性能稳定,避免扩容抖动
场景 2:音频环形缓冲(推荐循环数组)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @interface AudioRingBuffer : NSObject @property (nonatomic, strong) LitQueue<AudioSample *> *sampleBuffer; @end
@implementation AudioRingBuffer
- (instancetype)init { if (self = [super init]) { _sampleBuffer = [LitQueue queueWithCapacity:4096]; } return self; }
- (void)writeSample:(AudioSample *)sample { if (self.sampleBuffer.count == 4096) { [self.sampleBuffer dequeue]; } [self.sampleBuffer enqueue:sample]; }
- (AudioSample *)readSample { if (!self.sampleBuffer.isEmpty) { return [self.sampleBuffer dequeue]; } return nil; }
@end
|
选择理由:
- 缓冲区大小固定(4096)
- 对缓存友好性要求高(音频处理)
- 内存占用小
- 永远不会触发扩容(满了就覆盖)
场景 3:消息队列(推荐链表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @interface MessageQueue : NSObject @property (nonatomic, strong) LitLinkedQueue<Message *> *pendingMessages; @end
@implementation MessageQueue
- (instancetype)init { if (self = [super init]) { _pendingMessages = [LitLinkedQueue queue]; } return self; }
- (void)sendMessage:(Message *)message { [self.pendingMessages enqueue:message]; [self processMessages]; }
- (void)processMessages { while (!self.pendingMessages.isEmpty) { Message *message = [self.pendingMessages dequeue]; [self handleMessage:message]; } }
@end
|
选择理由:
- 消息数量完全不可预估
- 可能突然涌入大量消息
- 需要快速入队出队
- 避免扩容带来的延迟
性能测试对比
下面是一个简单的性能测试,对比两种实现在不同操作下的表现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| - (void)performanceTest { NSInteger testCount = 10000; { LitQueue *arrayQueue = [LitQueue queueWithCapacity:10]; LitLinkedQueue *linkedQueue = [LitLinkedQueue queue]; CFTimeInterval start1 = CACurrentMediaTime(); for (NSInteger i = 0; i < testCount; i++) { [arrayQueue enqueue:@(i)]; } CFTimeInterval time1 = CACurrentMediaTime() - start1; CFTimeInterval start2 = CACurrentMediaTime(); for (NSInteger i = 0; i < testCount; i++) { [linkedQueue enqueue:@(i)]; } CFTimeInterval time2 = CACurrentMediaTime() - start2; NSLog(@"入队 %ld 个元素:", testCount); NSLog(@" 循环数组: %.4f 秒 (含扩容)", time1); NSLog(@" 链表: %.4f 秒", time2); } { LitQueue *arrayQueue = [LitQueue queueWithCapacity:1000]; LitLinkedQueue *linkedQueue = [LitLinkedQueue queue]; CFTimeInterval start1 = CACurrentMediaTime(); for (NSInteger i = 0; i < testCount; i++) { [arrayQueue enqueue:@(i)]; if (i % 2 == 0 && !arrayQueue.isEmpty) { [arrayQueue dequeue]; } } CFTimeInterval time1 = CACurrentMediaTime() - start1; CFTimeInterval start2 = CACurrentMediaTime(); for (NSInteger i = 0; i < testCount; i++) { [linkedQueue enqueue:@(i)]; if (i % 2 == 0 && !linkedQueue.isEmpty) { [linkedQueue dequeue]; } } CFTimeInterval time2 = CACurrentMediaTime() - start2; NSLog(@"\n混合操作 %ld 次:", testCount); NSLog(@" 循环数组: %.4f 秒", time1); NSLog(@" 链表: %.4f 秒", time2); } { LitQueue *arrayQueue = [LitQueue queueWithCapacity:testCount]; LitLinkedQueue *linkedQueue = [LitLinkedQueue queue]; for (NSInteger i = 0; i < testCount; i++) { [arrayQueue enqueue:@(i)]; [linkedQueue enqueue:@(i)]; } CFTimeInterval start1 = CACurrentMediaTime(); NSArray *array1 = [arrayQueue toArray]; for (id obj in array1) { [obj description]; } CFTimeInterval time1 = CACurrentMediaTime() - start1; CFTimeInterval start2 = CACurrentMediaTime(); NSArray *array2 = [linkedQueue toArray]; for (id obj in array2) { [obj description]; } CFTimeInterval time2 = CACurrentMediaTime() - start2; NSLog(@"\n遍历 %ld 个元素:", testCount); NSLog(@" 循环数组: %.4f 秒", time1); NSLog(@" 链表: %.4f 秒", time2); } }
|
典型测试结果:
1 2 3 4 5 6 7 8 9 10 11
| 入队 10000 个元素: 循环数组: 0.0023 秒 (含扩容) 链表: 0.0018 秒
混合操作 10000 次: 循环数组: 0.0015 秒 链表: 0.0012 秒
遍历 10000 个元素: 循环数组: 0.0008 秒 链表: 0.0012 秒
|
测试结论:
- 链表在频繁入队时性能更稳定(无扩容抖动)
- 循环数组在遍历时性能更优(缓存友好)
- 混合操作下两者性能接近
- 实际差异在微秒级别,选择时应更关注适用场景
线程安全考虑
上述两种实现都不是线程安全的。在多线程环境下使用时,需要添加同步机制:
方案 1:使用锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @interface ThreadSafeQueue : NSObject @property (nonatomic, strong) LitLinkedQueue *queue; @property (nonatomic, strong) NSLock *lock; @end
@implementation ThreadSafeQueue
- (instancetype)init { if (self = [super init]) { _queue = [LitLinkedQueue queue]; _lock = [[NSLock alloc] init]; } return self; }
- (void)enqueue:(id)object { [self.lock lock]; [self.queue enqueue:object]; [self.lock unlock]; }
- (id)dequeue { [self.lock lock]; id object = nil; if (!self.queue.isEmpty) { object = [self.queue dequeue]; } [self.lock unlock]; return object; }
@end
|
方案 2:使用 GCD 串行队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @interface ConcurrentQueue : NSObject @property (nonatomic, strong) LitLinkedQueue *queue; @property (nonatomic, strong) dispatch_queue_t syncQueue; @end
@implementation ConcurrentQueue
- (instancetype)init { if (self = [super init]) { _queue = [LitLinkedQueue queue]; _syncQueue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_SERIAL); } return self; }
- (void)enqueue:(id)object { dispatch_sync(self.syncQueue, ^{ [self.queue enqueue:object]; }); }
- (id)dequeue { __block id object = nil; dispatch_sync(self.syncQueue, ^{ if (!self.queue.isEmpty) { object = [self.queue dequeue]; } }); return object; }
@end
|
最佳实践建议
1. 根据场景选择
1 2 3 4 5 6 7 8 9 10 11
|
LitQueue *audioBuffer = [LitQueue queueWithCapacity:4096];
LitLinkedQueue *requestQueue = [LitLinkedQueue queue];
LitQueue *taskQueue = [LitQueue queueWithCapacity:10];
|
2. 合理设置初始容量
1 2 3 4 5 6 7 8 9
|
LitQueue *taskQueue = [LitQueue queueWithCapacity:100];
LitQueue *taskQueue = [LitQueue queueWithCapacity:2];
LitQueue *taskQueue = [LitQueue queueWithCapacity:100000];
|
3. 注意内存管理
1 2 3 4 5 6 7 8 9 10
| [queue removeAllObjects]; queue = nil;
@weakify(self); [queue enqueue:^{ @strongify(self); [self doSomething]; }];
|
4. 异常处理
1 2 3 4 5 6 7 8
| if (!queue.isEmpty) { id obj = [queue dequeue]; [self process:obj]; }
id obj = [queue dequeue];
|
总结
通过本文的详细对比,我们可以得出以下结论:
关键区别
维度 |
循环数组 |
链表 |
核心思想 |
预分配+循环索引 |
动态节点+指针 |
性能特点 |
O(1) 摊销 |
O(1) 真实 |
内存占用 |
较小但可能浪费 |
较大但按需分配 |
缓存友好 |
优秀 |
较差 |
代码复杂度 |
较高 |
较低 |
选择建议
选择循环数组(LitQueue)当:
- ✅ 队列大小可预估
- ✅ 需要频繁遍历
- ✅ 内存受限
- ✅ 需要随机访问
选择链表(LitLinkedQueue)当:
- ✅ 队列大小不确定
- ✅ 对性能稳定性要求高
- ✅ 频繁入队出队
- ✅ 很少遍历队列
最终建议
在实际项目中,链表实现(LitLinkedQueue)是更通用的选择,因为:
- 真正的 O(1) 性能,无扩容抖动
- 实现简单,易于维护
- 适应性强,适合大多数场景
只有在以下明确需求时,才考虑使用循环数组:
- 队列大小固定或可准确预估
- 需要高频遍历或随机访问
- 对内存占用极度敏感
记住:过早优化是万恶之源。如果不确定,就选择更简单、更通用的链表实现。等到真正遇到性能瓶颈时,再根据 Profiling 结果进行针对性优化。
参考资料
- Apple Developer Documentation - NSMutableArray
- 数据结构:循环数组详解
- Thomas H. Cormen et al. 《算法导论》(第3版)
- Robert Sedgewick, Kevin Wayne. 《算法》(第4版)
- Queue (abstract data type) - Wikipedia