Массив против эффективности объекта в 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 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";
Вот тест производительности трех возможностей:
Отличное чтение по этим темам в 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
Так что объекты лучше.