本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
开发中,LinkedList和ArrayList是两种最常用的线性表实现,它们在内部结构、性能特性和适用场景上有显著差异:
一、底层实现差异
特性 | LinkedList | ArrayList |
---|---|---|
数据结构 | 双向链表 | 动态数组 |
内存分配 | 非连续内存(节点分散存储) | 连续内存空间 |
节点结构 | 包含前驱/后继指针和数据域 | 仅存储数据 |
扩容机制 | 无扩容概念(按需创建节点) | 容量不足时1.5倍扩容 |
二、操作性能对比
1. 访问操作
// ArrayList访问示例(O(1))
const arrList = new ArrayList<number>([1, 2, 3]);
arrList.get(1); // 直接通过索引访问
// LinkedList访问示例(O(n))
const linkedList = new LinkedList<number>();
linkedList.add(1);
linkedList.add(2);
linkedList.get(1); // 需要从头遍历
性能关键:
- ArrayList通过数组索引直接访问,时间复杂度O(1)
- LinkedList需要遍历节点,平均时间复杂度O(n)
2. 插入/删除操作
// ArrayList中间插入(O(n))
arrList.addByIndex(1, 5); // 需要移动后续元素
// LinkedList中间插入(O(1)已知节点位置)
linkedList.insert(1, 5); // 仅修改指针
性能关键:
- 头部操作:LinkedList O(1),ArrayList O(n)
- 中间操作:LinkedList O(1)(已知位置)/O(n)(需查找),ArrayList O(n)
- 尾部操作:ArrayList O(1)(摊销),LinkedList O(1)
三、内存特性对比
内存特性 | LinkedList | ArrayList |
---|---|---|
内存占用 | 更高(每个节点含两个指针) | 更低(仅存储数据) |
内存局部性 | 差(节点分散) | 好(连续存储) |
缓存命中率 | 低 | 高 |
GC压力 | 更大(频繁创建/销毁节点) | 更小 |
四、功能接口差异
1. 独有方法
LinkedList特有:
.addFirst(element: T): void // 头部添加
.addLast(element: T): void // 尾部添加
.removeFirst(): T // 移除头部
.removeLast(): T // 移除尾部
ArrayList特有:
.trimToCurrentLength(): void // 缩容到当前尺寸
.increaseCapacityTo(newCapacity: number): void // 手动扩容
2. 迭代器差异
// LinkedList迭代器更适合修改操作
const lit = linkedList[Symbol.iterator]();
while (lit.next().done) {
lit.remove(); // 安全删除当前元素
}
// ArrayList迭代器更适合随机访问
const ait = arrList[Symbol.iterator]();
ait.next().value; // 快速获取元素
五、典型应用场景
推荐使用LinkedList的场景
- 频繁在头/中部插入删除:
// 消息队列实现
const messageQueue = new LinkedList<string>();
function processMessages() {
while (messageQueue.length > 0) {
const msg = messageQueue.removeFirst();
// 处理消息...
}
}
2. 实现栈/双端队列:
// 使用LinkedList实现撤销栈
const undoStack = new LinkedList<Command>();
function executeCommand(cmd: Command) {
undoStack.addFirst(cmd);
// 执行命令...
}
3. 不确定数据规模的场景:
// 动态增长的日志记录
const logEntries = new LinkedList<LogEntry>();
function addLogEntry(entry: LogEntry) {
logEntries.addLast(entry); // 无需考虑容量
}
推荐使用ArrayList的场景
- 频繁随机访问:
// 图片缓存池
const imageCache = new ArrayList<PixelMap>(100);
function getImage(index: number): PixelMap {
return imageCache.get(index); // O(1)访问
}
2. 需要内存紧凑的场景:
// 渲染顶点数据
const vertices = new ArrayList<Point>(1000);
function addVertex(point: Point) {
vertices.add(point); // 连续内存提高渲染效率
}
七、建议
-
选择LinkedList当:
- 需要频繁在头部/中部插入删除
- 数据规模变化大且不可预测
- 需要实现队列/栈等数据结构
- 不关心随机访问性能
-
选择ArrayList当:
- 需要频繁按索引访问元素
- 数据量相对稳定或可预测
- 追求内存使用效率
- 需要进行数值计算
-
混合使用建议:
// 读多写少的场景:ArrayList为主
const dataCache = new ArrayList<Data>();
// 写操作专用缓冲区:LinkedList
const writeBuffer = new LinkedList<Data>();
function syncData() {
// 批量转移数据
dataCache.addAll(Array.from(writeBuffer));
writeBuffer.clear();
}