Массив против эффективности объекта в JavaScript

У меня есть модель с возможно тысячами объектов. Мне было интересно, какой будет наиболее эффективный способ их хранения и извлечения одного объекта, когда у меня есть его id. Идентификаторы - это длинные номера.

Итак, это два варианта, о которых я думал. в опции один - это простой массив с индексом инкрементирования. в варианте 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];
}

Update:

ОК, я понимаю, что использование второго во втором варианте не может быть и речи. Таким образом, строка объявления второй вариант должен быть действительно: var a = {}; и единственный вопрос: что лучше выполняет поиск объекта с заданным id: массив или объект, где id является ключом.

а также изменится ли ответ, если мне придется сортировать список много раз?

Ответ 1

Краткая версия: Массивы в основном быстрее объектов. Но нет 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";

Вот тест производительности трех возможностей:

Маска Lookup Array vs Holey Array vs Object Performance Test

Отличное чтение по этим темам в Smashing Magazine: эффективное использование быстрой памяти JavaScript

Ответ 2

Это не вопрос производительности, потому что массивы и объекты работают по-разному (или, как минимум, должны быть). Массивы имеют непрерывный индекс 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

Ответ 3

С ES6 наиболее эффективным способом было бы использование Карты.

var myMap = new Map();

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

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

Сегодня вы можете использовать функции ES6 с помощью прокладки (https://github.com/es-shims/es6-shim).

Производительность зависит от браузера и сценария. Но вот один пример, где Map наиболее эффективен: https://jsperf.com/es6-map-vs-object-properties/2


ССЫЛКА https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map

Ответ 4

Я попытался довести это до следующего измерения, буквально.

Учитывая двумерный массив, в котором оси x и y всегда имеют одинаковую длину, быстрее ли это:

a) найдите ячейку, создав двухмерный массив и просматривая первый индекс, за которым следует второй индекс, т.е.

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

или

b) создайте объект со строковым представлением координат x и y, а затем выполните один поиск на этом объекте, i.e:

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

Результат:
Оказывается, что гораздо быстрее выполнять два числовых поиска индекса на массивах, чем один поиск свойств объекта.

Результаты здесь:

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

Ответ 5

В 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

Даже если идентификатор поиска является первым в массиве/объекте:

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

Ответ 6

Это зависит от использования. Если объект поиска - это очень быстро.

Вот пример Plunker для проверки производительности поиска массивов и объектов.

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

Вы увидите это; Если вы ищете 5.000 элементы в массиве массивов длиной 5.000, возьмите 3000 milisecons

Однако поиск объектов 5.000 в объекте имеет свойства 5.000, используйте только 2 или 3 milisecons

Также создание дерева объектов не имеет большого значения

Ответ 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

Поисковый массив: O (n) = n

Поиск объекта по ключу: O (n) = 1

Так что объекты лучше.