我知道Javascript中的unshift()和push()方法有什么区别,但我想知道时间复杂度有什么不同?
我认为push()方法是O(1),因为你只是将一个项添加到数组的末尾,但我不确定unshift()方法,因为,我想你必须“移动”所有其他现有元素都转发,我想是O(log n)或O(n)?
答案 0 :(得分:51)
push()更快。
js>function foo() {a=[]; start = new Date; for (var i=0;i<100000;i++) a.unshift(1); return((new Date)-start)}
js>foo()
2190
js>function bar() {a=[]; start = new Date; for (var i=0;i<100000;i++) a.push(1); return((new Date)-start)}
js>bar()
10
答案 1 :(得分:20)
据我所知,JavaScript语言规范并未规定这些函数的时间复杂度。
使用O(1)push
和unshift
操作实现类似数组的数据结构(O(1)随机访问)当然是可能的。 C ++ std::deque
就是一个例子。因此,使用C ++ deques在内部表示Javascript数组的Javascript实现将具有O(1)push
和unshift
操作。
但是如果你需要保证这样的时间限制,你必须自己动手,如下:
答案 2 :(得分:4)
imho它取决于javascript引擎...... 如果它会使用链表,那么unshift应该很便宜......
答案 3 :(得分:3)
对于对v8实现感到好奇的人,这里是source。由于unshift
接受任意数量的参数,因此数组将自身移动以容纳所有参数。
UnshiftImpl
最终以AddArguments
中的start_position
调用AT_START
,将其踢到此else
statement
// If the backing store has enough capacity and we add elements to the
// start we have to shift the existing objects.
Isolate* isolate = receiver->GetIsolate();
Subclass::MoveElements(isolate, receiver, backing_store, add_size, 0,
length, 0, 0);
并将其带到MoveElements
。
static void MoveElements(Isolate* isolate, Handle<JSArray> receiver,
Handle<FixedArrayBase> backing_store, int dst_index,
int src_index, int len, int hole_start,
int hole_end) {
Heap* heap = isolate->heap();
Handle<BackingStore> dst_elms = Handle<BackingStore>::cast(backing_store);
if (len > JSArray::kMaxCopyElements && dst_index == 0 &&
heap->CanMoveObjectStart(*dst_elms)) {
// Update all the copies of this backing_store handle.
*dst_elms.location() =
BackingStore::cast(heap->LeftTrimFixedArray(*dst_elms, src_index))
->ptr();
receiver->set_elements(*dst_elms);
// Adjust the hole offset as the array has been shrunk.
hole_end -= src_index;
DCHECK_LE(hole_start, backing_store->length());
DCHECK_LE(hole_end, backing_store->length());
} else if (len != 0) {
WriteBarrierMode mode = GetWriteBarrierMode(KindTraits::Kind);
dst_elms->MoveElements(heap, dst_index, src_index, len, mode);
}
if (hole_start != hole_end) {
dst_elms->FillWithHoles(hole_start, hole_end);
}
}
我还想指出v8具有一个不同的element kinds
概念,具体取决于数组包含的内容。这也会影响性能。
实际上很难说出性能是什么,因为它实际上取决于传递的元素类型,数组中有多少孔等等。如果我进一步研究一下,也许我可以给出一个明确的答案,但是总的来说,我假设因为unshift
需要在数组中分配更多的空间,所以总的来说,您可以假设它是O(N)(将根据元素的数量线性缩放),但是如果我错了,请有人纠正我
答案 4 :(得分:2)
使用快速非移位和推送实现数组的一种方法是简单地将数据放入C级数组的中间。这就是Perl的做法,IIRC。
另一种方法是使用两个独立的C级数组,因此push会附加到其中一个,而unshift会附加到另一个。我知道,这种方法对前一种方法没有任何实际好处。
无论它是如何实现的,当内部C级阵列有足够的备用内存时,推送或非移位将花费O(1)时间,否则,当必须重新分配时,至少需要O(N)次复制旧数据到新的内存块。
答案 5 :(得分:0)
是的,你说得对。 push()
的默认复杂度为 O(1),unshift()
为 O(n)。因为 unshift()
必须增加 Array 中已经存在的所有元素。但是,push()
必须在数组的末尾插入一个元素,因此所有 Array 元素的索引都不必更改。但是,由于内存的动态分配,push()
也可以说复杂度为 O(n)。在 javascript 中,当您创建一个新的 Array 而不指定您需要的大小时,它将创建一个默认值的 Array。在默认大小被填满之前,推送操作需要 O(1) 复杂度。但是,如果默认大小已满,编译器必须创建一个新的连续内存块,其大小是默认内存的两倍,并将已存在的元素复制到新分配的内存中。因此,将元素从一个连续内存块移动到另一个连续内存块需要 O(n) 时间。
如果您知道将要放入数组的元素数量,则可以避免插入元素的复杂度为 O(n)。
let array = new Array(size).fill(0)
for (let i = 0; i < size; i++) {
array[i] = i
}
因此,我们更改了元素在其位置的索引,而不是 push()
。与创建具有默认值的数组并将元素推送到该数组相比,它的内存效率更高且更简单。由于我们只使用了所需的内存量,因此不会浪费额外的内存。
答案 6 :(得分:-1)
不变动vs连贯vs点差的基准测试结果
<iframe src="https://measurethat.net/Embed?id=85784" width="100%" height="500px"></iframe>