前言
你应该已经使用过数组了,push, pop,shift,unshift,这些方法能够很方便的让我们在末尾插入,末尾弹出,头部弹出,头部插入,但为了保证你在操作之后,所有的数据都维持一个正确的下标【如你在头部插入一个数据,原来的所有下标都要往后移动一位】,有些操作会很费性能。
举个例子
let len = 1e5;
let arr = [];
let t1 = performance.now();
for (let i = 0; i < len; i++) {
// 末尾插入
arr.push(i);
}
let t2 = performance.now();
for (let i = 0; i < len; i++) {
// 头部弹出
arr.shift();
}
let t3 = performance.now();
// 在我电脑上的结果是push: 1.899999976158142ms shift: 390.10000002384186ms
console.log(`push: ${t2 - t1}ms shift: ${t3 - t2}ms`);
结果显示,shift的时间,远超push,且在数据量更大时,这个差距也会更大,可见,数组在某些方面存在一定的局限性,下面我们再看一个题目
一个题目
有一个日志系统,数据会一直增加,而我只需要保存最近的100W条数据,数据量超100W时,保存最新数据,而舍弃最旧的数据,按照数组的写法,会是下面这个样子
let logs = [];
let limit = 1e6;
function addLog(data) {
if (logs.length >= limit) {
logs.shift();
}
logs.push(data);
}
这份代码就会出现我们上面提到的问题,分析一下,可以发现,在这个例子中,我们只关心两个量:数据总量【是否超过限制】和数据的顺序关系【那个数据在前,那个数据在后】,对比数组,还可以总结一点,就是:我们并不需要下标。
尝试一下实现这样一个数据结构。
实现
let logs = {
len: 0,
front: null,
end: null
}
let limit = 1e6;
function addLog(data) {
let newVal = {
before: null,
after: null,
data: data
}
if (!logs.len) {
// 没有数据时,设置头和尾
logs.front = newVal;
logs.end = newVal;
} else {
// 有数据时,在尾部插入数据
logs.end.after = newVal;
newVal.before = logs.end;
logs.end = newVal;
}
logs.len++;
// 数据超过限制,删除头部的数据
if (logs.len > limit) {
let newFront = logs.front.after;
newFront.before = null;
logs.front = newFront;
logs.len--;
}
}
总结
上面的代码,就是链表的核心:一个单纯的链式结构,只关心谁连着谁或者被谁连着
较数组的优势: 增加或删除数据
较数组的劣势: 没法直接通过下标快速获取数据
ps:
- 以上实现并非标准的链表,在某些时候甚至还有bug【limit为0时没有清空front和end操作】
- 标准的链表代码,网上太多了,这里就不贴了
下一章
栈,队列