【问题标题】:Array vs. Object efficiency in JavaScriptJavaScript 中的数组与对象效率
【发布时间】:2013-06-22 02:46:33
【问题描述】:

我有一个可能包含数千个对象的模型。我想知道一旦我拥有它的 id,存储它们和检索单个对象的最有效方法是什么。 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 为键的数组或对象。

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

【问题讨论】:

  • 您是否始终需要排序集合?如果是这样,除了数组之外别无选择(尽管不像你现在那样使用索引)。
  • @Jon 实际上,我愿意。 “像现在这样”是什么意思?
  • @MosheShaham:数组(应该)有从 0 开始的连续索引。如果你使用数组,不要做任何其他事情。
  • 我猜这个基准会回答你问题的第一部分:jsben.ch/#/Y9jDP

标签: javascript performance


【解决方案1】:

这根本不是一个性能问题,因为数组和对象的工作方式非常不同(或者至少应该如此)。数组具有连续索引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 中没有非关联数组这样的东西。如果您需要键(数字或字符串),请使用对象。如果您只需要一个(有序)列表,请使用数组。时期。性能不进入讨论。如果性能至关重要,并且无论哪种方式您都可以使用您的密钥,请尝试哪种方式更适合您。
  • 但我想知道什么表现更好:从数组中检索一个对象(通过循环遍历它)或从 id 为键的“关联”对象中检索。如果我的问题不清楚,我很抱歉...
  • @Moshe 如果您通过键访问任何内容,无论是在对象还是数组中,它总是比遍历容器试图找到您想要的内容要快得多。通过键访问数组或对象中的项目的差异可能可以忽略不计。循环显然更糟。
  • @deceze —“关于保存用户对象的数组并获取用户的对象,需要一个循环来获取基于 user_id 的用户对象”与“具有键为 user_id 的对象因此可以使用user_id 作为键来访问用户对象”?哪一个在性能方面更好?对此的任何建议表示赞赏:)
【解决方案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'});
}

原帖 - 说明

您的问题存在一些误解。

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";

这是对三种可能性的性能测试:

Lookup Array vs Holey Array vs Object Performance Test

在 Smashing Magazine 上阅读有关这些主题的精彩文章:Writing fast memory efficient JavaScript

【讨论】:

  • @Moshe 因此,所有关于 Javascript 性能的讨论都应该完成。 :P
  • 这实际上取决于您正在使用的数据的数据和大小。非常小的数据集和小对象在使用数组时会表现得更好。如果您在使用对象作为地图的大型数据集中谈论查找,那么对象会更有效。 jsperf.com/array-vs-object-performance/35
  • 同意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更正了
  • 可以通过在这篇文章中总结 jsPerf 结论来改进这个答案 - 特别是因为 jsPerf 结果是问题的真正答案。其余是额外的。这在 jsPerf 关闭的时候(比如现在)更相关。 meta.stackexchange.com/questions/8231/…
  • 测试有缺陷。现实中的“数组”方法并没有那么慢。 首先,在生成元素时,oa2只有在他们还没有的时候才得到一个新元素,而一个新的元素被推入@987654339 @总是。如果两次生成相同的数字,则不会添加到oa2,而是会推送到a1。不太可能,但仍然...... 其次,在a1 的测试中,任何正常人一旦找到该项目就会打破循环......这大大改变了结果。 Check for yourself.
【解决方案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

【讨论】:

    【解决方案4】:

    对于 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 比对象和数组都慢
    • 它更“语义化”,而不是更高效,这是问题所在。
    • @AlexG 很确定标题清楚地说明了efficiency
    【解决方案5】:

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

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

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

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

    但是在对象中查找 5.000 个项目具有 5.000 属性,只需要 23 毫秒

    同样制作对象树也没有太大的不同

    【讨论】:

      【解决方案6】:

      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
      

      【讨论】:

        【解决方案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
        

        【讨论】:

          【解决方案8】:
          1. 索引字段(带有数字键的字段)存储为对象内部的神圣数组。因此查找时间为 O(1)

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-03-01
            • 2011-04-19
            • 1970-01-01
            • 1970-01-01
            • 2011-06-05
            • 2011-12-09
            • 1970-01-01
            • 2010-10-15
            相关资源
            最近更新 更多