JavaScript 中的数组与对象效率

IT技术 javascript performance
2021-01-14 23:47:37

我有一个可能包含数千个对象的模型。我想知道存储它们并在获得单个对象后检索单个对象的最有效方法是什么。id 是很长的数字。

所以这些是我正在考虑的 2 个选项。在选项一中,它是一个带有递增索引的简单数组。在选项 2 中,它是一个关联数组,也可能是一个对象,如果它有所不同的话。我的问题是当我主要需要检索单个对象时,哪个更有效,但有时也需要遍历它们并进行排序。

非关联数组的选项一:

var a = [{id: 29938, name: 'name1'},
         {id: 32994, name: 'name1'}];
function getObject(id) {
    for (var i=0; i < a.length; i++) {
        if (a[i].id == id) 
            return a[i];
    }
}

带有关联数组的选项二:

var a = [];  // maybe {} makes a difference?
a[29938] = {id: 29938, name: 'name1'};
a[32994] = {id: 32994, name: 'name1'};
function getObject(id) {
    return a[id];
}

更新:

好的,我知道在第二个选项中使用数组是不可能的。所以第二个选项的声明行应该是:var a = {};唯一的问题是:在检索具有给定 id 的对象时什么表现更好:数组或其中 id 是键的对象。

而且,如果我必须多次对列表进行排序,答案会改变吗?

6个回答

简短版本:数组通常比对象快。但是没有 100% 正确的解决方案。

2017 年更新 - 测试和结果

var a1 = [{id: 29938, name: 'name1'}, {id: 32994, name: 'name1'}];

var a2 = [];
a2[29938] = {id: 29938, name: 'name1'};
a2[32994] = {id: 32994, name: 'name1'};

var o = {};
o['29938'] = {id: 29938, name: 'name1'};
o['32994'] = {id: 32994, name: 'name1'};

for (var f = 0; f < 2000; f++) {
    var newNo = Math.floor(Math.random()*60000+10000);
    if (!o[newNo.toString()]) o[newNo.toString()] = {id: newNo, name: 'test'};
    if (!a2[newNo]) a2[newNo] = {id: newNo, name: 'test' };
    a1.push({id: newNo, name: 'test'});
}

测试设置 检测结果

原帖 - 说明

你的问题有一些误解。

Javascript 中没有关联数组。只有数组和对象。

这些是数组:

var a1 = [1, 2, 3];
var a2 = ["a", "b", "c"];
var a3 = [];
a3[0] = "a";
a3[1] = "b";
a3[2] = "c";

这也是一个数组:

var a3 = [];
a3[29938] = "a";
a3[32994] = "b";

它基本上是一个有孔的数组,因为每个数组都有连续的索引。它比没有孔的数组慢。但是手动遍历数组甚至更慢(大多数情况下)。

这是一个对象:

var a3 = {};
a3[29938] = "a";
a3[32994] = "b";

下面是三种可能性的性能测试:

查找数组 vs 空洞数组 vs 对象性能测试

Smashing Magazine 上关于这些主题的精彩阅读:编写快速内存高效的 JavaScript

同意 f1v,但 Revision 35 在测试中有一个缺陷:if (a1[i].id = id) result = a1[i];应该是:if (a1[i].id === id) result = a1[i];测试http://jsperf.com/array-vs-object-performance/37更正
2021-03-14 23:47:37
测试有问题。现实中的“数组”方法并没有那么慢。首先,在生成元素时,只有当他们还没有一个新元素时才获取一个新元素,oa2一个新元素总是被推入如果它两次生成相同的数字,则不会将其添加到,而是将其推入不太可能,但仍然......其次,在 的测试中,任何正常人一旦找到该项目就会打破循环......这显着改变了结果。自己检查一下a1 oa2a1a1
2021-03-14 23:47:37
@Moshe 因此,所有关于 Javascript 性能的讨论都应该完成。:P
2021-03-17 23:47:37
这实际上取决于您正在使用的数据和数据的大小。非常小的数据集和小对象在使用数组时会表现得更好。如果您谈论在使用对象作为地图的大型数据集中的查找,那么对象会更有效。 jsperf.com/array-vs-object-performance/35
2021-03-19 23:47:37
这个答案可以通过在这篇文章中总结 jsPerf 结论来改进 - 特别是因为 jsPerf 结果是问题的真正答案。其余的是额外的。这在 jsPerf 关闭的时候更重要(比如现在)。meta.stackexchange.com/questions/8231/...
2021-03-27 23:47:37

这根本不是一个真正的性能问题,因为数组和对象的工作方式非常不同(或者至少应该如此)。数组有一个连续的索引0..n,而对象将任意键映射到任意值。如果想提供特定的键,唯一的选择是一个对象。如果你不关心键,它就是一个数组。

如果您尝试在数组上设置任意(数字)键,您确实会损失性能,因为从行为上讲,该数组将填充其间的所有索引:

> foo = [];
  []
> foo[100] = 'a';
  "a"
> foo
  [undefined, undefined, undefined, ..., "a"]

(请注意,该数组实际上并不包含 99 个undefined值,但它会以这种方式运行,因为您 [应该]在某个时候迭代该数组。)

这两个选项的文字应该清楚地说明如何使用它们:

var arr = ['foo', 'bar', 'baz'];     // no keys, not even the option for it
var obj = { foo : 'bar', baz : 42 }; // associative by its very nature
@Moshe 在 Javascript 中没有非关联数组这样的东西。如果您需要键(数字或字符串),请使用对象。如果您只需要一个(有序)列表,请使用数组。时期。性能没有进入讨论。如果性能至关重要,并且您可以使用任何一种方式使用您的按键,请尝试哪种更适合您。
2021-03-14 23:47:37
我不想提供特定的键。我想知道什么表现得更好,我将与之合作。好的,所以在第二个选项中,数组是不可能的。但是对象与非关联数组呢?
2021-03-27 23:47:37
@Moshe 如果您通过键访问对象或数组中的任何内容,它总是比循环遍历容器以尝试找到您想要的内容要快得多。通过键访问数组或对象中的项目的差异可能可以忽略不计。无论哪种方式,循环显然都更糟。
2021-03-28 23:47:37
@deceze - “关于保存用户对象的数组并获取用户的对象,需要一个循环来获取基于user_id”与“具有键的对象的user_id用户对象因此用户对象可以user_id用作键来访问”?就性能而言,哪个更好?对此的任何建议表示赞赏:)
2021-04-07 23:47:37
但我想知道什么表现更好:从数组中检索对象(通过循环遍历它)或从 id 是关键的“关联”对象中检索对象。如果我的问题不清楚,我很抱歉...
2021-04-08 23:47:37

对于 ES6,最高效的方法是使用 Map。

var myMap = new Map();

myMap.set(1, 'myVal');
myMap.set(2, { catName: 'Meow', age: 3 });

myMap.get(1);
myMap.get(2);

您现在可以使用 shim ( https://github.com/es-shims/es6-shim )使用 ES6 功能

性能会因浏览器和场景而异。但这里有一个Map性能最好的例子https : //jsperf.com/es6-map-vs-object-properties/2


参考 https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map

有任何资源来支持这个吗?根据我目前的观察,ES6 Sets 比数组快,但 ES6 Maps 比对象和数组都慢
2021-03-15 23:47:37
它更“语义化”,而不是更高效,这是问题所在。
2021-03-18 23:47:37
@AlexG 很确定标题清楚地说明了efficiency.
2021-03-24 23:47:37

NodeJS 中,如果您知道ID,则与object[ID].

const uniqueString = require('unique-string');
const obj = {};
const arr = [];
var seeking;

//create data
for(var i=0;i<1000000;i++){
  var getUnique = `${uniqueString()}`;
  if(i===888555) seeking = getUnique;
  arr.push(getUnique);
  obj[getUnique] = true;
}

//retrieve item from array
console.time('arrTimer');
for(var x=0;x<arr.length;x++){
  if(arr[x]===seeking){
    console.log('Array result:');
    console.timeEnd('arrTimer');
    break;
  }
}

//retrieve item from object
console.time('objTimer');
var hasKey = !!obj[seeking];
console.log('Object result:');
console.timeEnd('objTimer');

结果:

Array result:
arrTimer: 12.857ms
Object result:
objTimer: 0.051ms

即使寻找 ID 是数组/对象中的第一个:

Array result:
arrTimer: 2.975ms
Object result:
objTimer: 0.068ms

从字面上看,我试图将其带到下一个维度。

给定一个 2 维数组,其中 x 轴和 y 轴的长度始终相同,是否更快:

a) 通过创建二维数组并查找第一个索引和第二个索引来查找单元格,即:

var arr=[][]    
var cell=[x][y]    

或者

b) 用 x 和 y 坐标的字符串表示创建一个对象,然后对该 obj 进行一次查找,即:

var obj={}    
var cell = obj['x,y']    

结果:
事实证明,在数组上进行两次数字索引查找比在对象上进行一次属性查找要快得多。

结果在这里:

http://jsperf.com/arr-vs-obj-lookup-2