Эффективность массивов и объектов в JavaScript

150

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

Вот 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 = {};и единственный вопрос: что лучше работает при извлечении объекта с заданным идентификатором: массив или объект, где идентификатор является ключом.

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

Моше Шахам
источник
1
это может помочь :: stackoverflow.com/questions/13309464/…
Судхир Бастакоти
Вам всегда нужна отсортированная коллекция? Если да, то нет другого варианта, кроме массива (хотя индексы не используются, как сейчас).
Джон
@ Джон, вообще-то, да. что вы подразумеваете под «как сейчас»?
Моше Шахам
1
@MosheShaham: Массивы (должны) иметь непрерывные индексы, начиная с 0. Если вы используете массивы, больше ничего не делайте.
Джон
Думаю, этот тест ответит на первую часть вашего вопроса: jsben.ch/#/Y9jDP
EscapeNetscape

Ответы:

149

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

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

Поисковый массив против дырочного массива против теста производительности объекта

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

Альп
источник
1
@Moshe И поэтому все обсуждения производительности в Javascript должны быть проведены. : P
deceze
10
Это действительно зависит от данных и размера данных, с которыми вы работаете. Очень маленькие наборы данных и небольшие объекты будут работать намного лучше с массивами. Если вы говорите о поиске в большом наборе данных, где вы используете объект в качестве карты, тогда объект более эффективен. jsperf.com/array-vs-object-performance/35
f1v
5
Согласен с f1v, но в версии 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 исправляет это
Чарльз Бирн
1
См. Http://jsperf.com/array-vs-object-performance/71 . Имеет меньшее подмножество данных (я должен был создать цикл для создания данных, но мне нужны дыры в массиве) около 93 объектов по сравнению с 5000. Внешний цикл - это идентификаторы для поиска, разбросанные по массиву объектов (начало, середина и конец) и Я также добавил отсутствующий идентификатор, поэтому поиск в массиве должен был пройти по всем элементам. Holey Array, Object by Key, затем Manual Array. Итак, как заявил f1v, это действительно зависит от размера данных и того, где данные для ручного поиска в массиве.
Чарльз Бирн
4
Этот ответ можно улучшить, суммируя выводы jsPerf здесь, в этом посте, тем более, что результаты jsPerf являются реальным ответом на вопрос. Остальное за дополнительную плату. Это более актуально во времена, когда jsPerf не работает (например, сейчас). meta.stackexchange.com/questions/8231/…
Джефф,
24

Это вообще не вопрос производительности, поскольку массивы и объекты работают по-разному (или, по крайней мере, должны). Массивы имеют непрерывный индекс 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
обмануть
источник
Я не хочу предоставлять конкретные ключи. Я хочу знать, что работает лучше, и я буду над этим работать. Итак, во втором варианте о массиве не может быть и речи. а как насчет объекта и неассоциативного массива?
Моше Шахам
1
@Moshe В Javascript нет такого понятия, как неассоциативный массив. Если вам нужны ключи (числа или строки), используйте объект. Если вам просто нужен (упорядоченный) список, используйте массивы. Период. Спектакль не обсуждается. Если производительность имеет решающее значение и вы можете жить со своими ключами в любом случае, попробуйте, какой из них лучше подходит для вас.
deceze
5
Но я хочу знать, что работает лучше: получение объекта из массива (путем перебора) или из «ассоциативного» объекта, где идентификатор является ключом. Извините, если мой вопрос не был ясен ...
Моше Шахам
2
@Moshe Если вы получаете доступ к чему-либо по ключу, будь то в объекте или массиве, это всегда будет бесконечно быстрее, чем цикл по контейнеру, пытаясь найти то, что вы хотите. Разница в доступе к элементу по ключу в массиве или в объекте, вероятно, незначительна. Цикл в любом случае явно хуже.
deceze
1
@deceze - Как «насчет массива, содержащего объекты пользователя, и чтобы получить объект пользователя, необходим цикл для получения объекта пользователя на основе объекта user_id« vs », имеющего ключи, поскольку, user_idследовательно, к объекту пользователя можно получить доступ с использованием user_idключа»? Какой из них лучше по производительности? Любые предложения по этому поводу приветствуются :)
Район,
14

С 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

Sandstrom
источник
11
Есть какие-нибудь ресурсы, чтобы подтвердить это? По моим наблюдениям, наборы ES6 быстрее массивов, но карты ES6 медленнее, чем объекты и массивы
Steel Brain
1
Он был более «семантическим», а не более производительным, о чем был вопрос.
AlexG
3
@AlexG уверен, что в заголовке ясно сказано efficiency.
Qix - МОНИКА БЫЛА ОБНОВЛЕНА
@Qix Да, мой плохой: o
AlexG
8

В 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
Павел
источник
5

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

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

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

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

или

б) создать объект со строковым представлением координат x и y, а затем выполнить один поиск по этому объекту, то есть:

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

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

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

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

Давем М
источник
3

Это зависит от использования. В случае, если поиск объектов выполняется очень быстро.

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

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

Вы увидите это; Поиск 5.000 элементов в коллекции массивов длиной 5.000 , взять 3000миллисекунды

Однако Глядя на 5000 элементов в объекте имеет 5.000 свойствами, принимают только 2или 3milisecons

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

Мехмет Откун
источник
0

У меня была аналогичная проблема, с которой я столкнулся, когда мне нужно хранить живые свечи из источника событий, ограниченного 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
PirateApp
источник
0
  1. Индексированные поля (поля с цифровыми ключами) хранятся как священный массив внутри объекта. Следовательно, время поиска - O (1)

  2. То же самое для массива поиска это O (1)

  3. Перебор массива объектов и проверка их идентификаторов на соответствие предоставленному - это операция O (n).

Badunius
источник