JavaScript 中的数组与对象效率

Array vs. Object efficiency in JavaScript

提问人:Moshe Shaham 提问时间:6/25/2013 最后编辑:clemensMoshe Shaham 更新时间:9/24/2020 访问量:172437

问:

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

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

非关联数组的选项 1:

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];
}

更新:

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

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

JavaScript 性能

评论

1赞 Sudhir Bastakoti 6/25/2013
这可能会有所帮助:: stackoverflow.com/questions/13309464/...
0赞 Jon 6/25/2013
您是否需要随时进行分类的收藏?如果是这样,除了数组之外没有其他选择(尽管不像您当前那样使用索引)。
0赞 Moshe Shaham 6/25/2013
@Jon实际上,我是这样做的。 你说的“像你现在一样”是什么意思?
1赞 Jon 6/25/2013
@MosheShaham:数组(应该)具有从 0 开始的连续索引。如果使用数组,请不要执行任何其他操作。
1赞 EscapeNetscape 10/20/2016
我想这个基准测试将回答你问题的第一部分:jsben.ch/#/Y9jDP

答:

29赞 deceze 6/25/2013 #1

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

如果尝试在数组上设置任意(数字)键,则确实会造成性能损失,因为从行为上讲,数组将填充介于两者之间的所有索引:

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

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

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

var arr = ['foo', 'bar', 'baz'];     // no keys, not even the option for it
var obj = { foo : 'bar', baz : 42 }; // associative by its very nature

评论

0赞 Moshe Shaham 6/25/2013
我不想提供特定的密钥。我想知道什么表现得更好,我会用它来工作。好的,所以在第二个选项中,数组是不可能的。但是对象与非关联数组呢?
1赞 deceze 6/25/2013
@Moshe Javascript 中没有非关联数组这样的东西。如果需要键(数字或字符串),请使用对象。如果你只需要一个(有序的)列表,请使用数组。时期。性能不会进入讨论。如果性能至关重要,并且无论哪种方式,您都可以使用钥匙,请尝试哪种方式更适合您。
7赞 Moshe Shaham 6/25/2013
但我想知道什么表现更好:从数组中检索对象(通过循环访问它)或从以 id 为键的“关联”对象中检索对象。如果我的问题不清楚,我很抱歉......
2赞 deceze 6/25/2013
@Moshe 如果你通过键访问任何内容,无论是在对象还是数组中,它总是比循环访问容器试图找到你想要的东西要快得多。通过数组或对象中的键访问项的差异可以忽略不计。无论哪种方式,循环显然都更糟。
1赞 Rayon 9/9/2016
@deceze — 如何“关于保存用户对象的数组并获取用户的对象,需要一个循环来获取基于”与“具有键的对象,因此可以使用作为键访问用户对象”?在性能方面哪个更好?对此的任何建议:)不胜感激user_iduser_iduser_id
174赞 Alp 6/25/2013 #2

简短版本:数组大多比对象快。但是没有 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'});
}

test setup test results

原帖 - 解释

你的问题中有一些误解。

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 Holey 数组与对象性能测试

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

评论

1赞 deceze 6/25/2013
@Moshe 因此,所有关于 Javascript 性能的讨论都应该完成。:P
12赞 f1v 2/15/2014
这实际上取决于您正在处理的数据和数据的大小。非常小的数据集和小对象在数组中的性能会好得多。如果你谈论的是在一个大型数据集中查找,你使用一个对象作为映射,那么一个对象会更有效。jsperf.com/array-vs-object-performance/35
5赞 Charles Byrne 7/1/2014
同意 f1v,但修订版 35 在测试中有一个缺陷: 应该是: 测试 http://jsperf.com/array-vs-object-performance/37 纠正了这一点if (a1[i].id = id) result = a1[i];if (a1[i].id === id) result = a1[i];
5赞 Jeff 7/23/2016
通过总结本文中的 jsPerf 结论,可以改进这个答案 - 特别是因为 jsPerf 结果是该问题的真正答案。其余的都是额外的。这在 jsPerf 关闭时(例如现在)更为相关。meta.stackexchange.com/questions/8231/......
2赞 dodov 9/10/2017
该测试是有缺陷的。现实中的“阵列”方法并没有那么慢。首先,在生成元素时,只有在他们还没有新元素时才获得它,而新元素总是被推入。如果它两次生成相同的数字,则不会将其添加到 和 中,而是会被推入。不太可能,但仍然......其次,在测试中,任何正常人一旦找到物品就会打破循环......这大大改变了结果。自己检查一下。oa2a1oa2a1a1
7赞 Davem M 2/15/2015 #3

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

给定一个二维数组,其中 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

15赞 sandstrom 9/20/2015 #4

在 ES6 中,最高性能的方法是使用 Map。

var myMap = new Map();

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

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

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

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


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

评论

12赞 Steel Brain 2/22/2016
有任何资源来支持吗?根据我到目前为止的观察,ES6 Sets 比数组快,但 ES6 Maps 比对象和数组都慢
1赞 AlexG 9/4/2016
它更“语义”,而不是更高性能,这就是问题所在。
5赞 Qix - MONICA WAS MISTREATED 11/29/2016
@AlexG很确定标题清楚地说明了.efficiency
4赞 Mehmet Otkun 5/24/2016 #5

这取决于使用情况。如果情况是,查找对象的速度非常快。

下面是一个 Plunker 示例,用于测试数组和对象查找的性能。

https://plnkr.co/edit/n2expPWVmsdR3zmXvX4C?p=preview

你会看到; 在 5.000 长度的数组集合中查找 5.000 个项目,接管 milisecons3000

但是,在对象中查找 5.000 个项目具有 5.000 个属性,仅取或 milisecons 23

此外,制作对象树并没有太大的区别

9赞 Paweł 11/2/2017 #6

NodeJS 中,如果您知道 ,则与 相比,遍历数组的速度非常慢。IDobject[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
1赞 PirateApp 11/17/2018 #7

我遇到了一个类似的问题,我需要存储来自仅限于 x 个项目的事件源的实时烛台。我可以将它们存储在一个对象中,其中每根蜡烛的时间戳将充当键,蜡烛本身将充当值。另一种可能性是,我可以将其存储在一个数组中,其中每个项目都是蜡烛本身。关于实时蜡烛的一个问题是,它们会在同一时间戳上发送更新,其中最新更新包含最新数据,因此您可以更新现有项目或添加新项目。因此,这里有一个很好的基准测试,它试图将所有 3 种可能性结合起来。以下解决方案中的阵列平均速度至少快 4 倍。随意玩

"use strict";

const EventEmitter = require("events");
let candleEmitter = new EventEmitter();

//Change this to set how fast the setInterval should run
const frequency = 1;

setInterval(() => {
    // Take the current timestamp and round it down to the nearest second
    let time = Math.floor(Date.now() / 1000) * 1000;
    let open = Math.random();
    let high = Math.random();
    let low = Math.random();
    let close = Math.random();
    let baseVolume = Math.random();
    let quoteVolume = Math.random();

    //Clear the console everytime before printing fresh values
    console.clear()

    candleEmitter.emit("candle", {
        symbol: "ABC:DEF",
        time: time,
        open: open,
        high: high,
        low: low,
        close: close,
        baseVolume: baseVolume,
        quoteVolume: quoteVolume
    });



}, frequency)

// Test 1 would involve storing the candle in an object
candleEmitter.on('candle', storeAsObject)

// Test 2 would involve storing the candle in an array
candleEmitter.on('candle', storeAsArray)

//Container for the object version of candles
let objectOhlc = {}

//Container for the array version of candles
let arrayOhlc = {}

//Store a max 30 candles and delete older ones
let limit = 30

function storeAsObject(candle) {

    //measure the start time in nanoseconds
    const hrtime1 = process.hrtime()
    const start = hrtime1[0] * 1e9 + hrtime1[1]

    const { symbol, time } = candle;

    // Create the object structure to store the current symbol
    if (typeof objectOhlc[symbol] === 'undefined') objectOhlc[symbol] = {}

    // The timestamp of the latest candle is used as key with the pair to store this symbol
    objectOhlc[symbol][time] = candle;

    // Remove entries if we exceed the limit
    const keys = Object.keys(objectOhlc[symbol]);
    if (keys.length > limit) {
        for (let i = 0; i < (keys.length - limit); i++) {
            delete objectOhlc[symbol][keys[i]];
        }
    }

    //measure the end time in nano seocnds
    const hrtime2 = process.hrtime()
    const end = hrtime2[0] * 1e9 + hrtime2[1]

    console.log("Storing as objects", end - start, Object.keys(objectOhlc[symbol]).length)
}

function storeAsArray(candle) {

    //measure the start time in nanoseconds
    const hrtime1 = process.hrtime()
    const start = hrtime1[0] * 1e9 + hrtime1[1]

    const { symbol, time } = candle;
    if (typeof arrayOhlc[symbol] === 'undefined') arrayOhlc[symbol] = []

    //Get the bunch of candles currently stored
    const candles = arrayOhlc[symbol];

    //Get the last candle if available
    const lastCandle = candles[candles.length - 1] || {};

    // Add a new entry for the newly arrived candle if it has a different timestamp from the latest one we storeds
    if (time !== lastCandle.time) {
        candles.push(candle);
    }

    //If our newly arrived candle has the same timestamp as the last stored candle, update the last stored candle
    else {
        candles[candles.length - 1] = candle
    }

    if (candles.length > limit) {
        candles.splice(0, candles.length - limit);
    }

    //measure the end time in nano seocnds
    const hrtime2 = process.hrtime()
    const end = hrtime2[0] * 1e9 + hrtime2[1]


    console.log("Storing as array", end - start, arrayOhlc[symbol].length)
}

结论 10 是这里的限制

Storing as objects 4183 nanoseconds 10
Storing as array 373 nanoseconds 10
1赞 badunius 9/24/2020 #8
  1. 索引字段(带有数字键的字段)作为神圣数组存储在对象内。因此查找时间为 O(1)

  2. 查找数组也是如此,它是 O(1)

  3. 遍历对象数组并根据提供的对象测试其 ID 是一个 O(n) 操作。