Effizienz von Array und Objekt in JavaScript

144

Ich habe ein Modell mit möglicherweise Tausenden von Objekten. Ich habe mich gefragt, wie ich sie am effizientesten speichern und ein einzelnes Objekt abrufen kann, sobald ich seine ID habe. Die IDs sind lange Zahlen.

Das sind also die 2 Optionen, über die ich nachgedacht habe. In Option 1 handelt es sich um ein einfaches Array mit einem inkrementierenden Index. In Option 2 ist es ein assoziatives Array und möglicherweise ein Objekt, wenn es einen Unterschied macht. Meine Frage ist, welches effizienter ist, wenn ich meistens ein einzelnes Objekt abrufen muss, es aber manchmal auch durchlaufen und sortieren muss.

Option eins mit nicht assoziativem Array:

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

Option zwei mit assoziativem Array:

var a = [];  // maybe {} makes a difference?
a[29938] = {id: 29938, name: 'name1'};
a[32994] = {id: 32994, name: 'name1'};
function getObject(id) {
    return a[id];
}

Aktualisieren:

OK, ich verstehe, dass die Verwendung eines Arrays in der zweiten Option nicht in Frage kommt. Die Deklarationszeile, die zweite Option, sollte also wirklich sein: var a = {};und die einzige Frage ist: Was funktioniert besser beim Abrufen eines Objekts mit einer bestimmten ID: ein Array oder ein Objekt, bei dem die ID der Schlüssel ist.

und wird sich auch die Antwort ändern, wenn ich die Liste viele Male sortieren muss?

Moshe Shaham
quelle
1
Dies kann helfen: stackoverflow.com/questions/13309464/…
Sudhir Bastakoti
Benötigen Sie jederzeit eine sortierte Sammlung? In diesem Fall gibt es keine andere Option als ein Array (obwohl die Indizes nicht wie derzeit verwendet werden).
Jon
@ Jon eigentlich tue ich. Was meinst du mit "wie du es gerade tust"?
Moshe Shaham
1
@ MosheShaham: Arrays (sollten) fortlaufende Indizes ab 0 haben. Wenn Sie Arrays verwenden, tun Sie nichts anderes.
Jon
Ich denke, dieser Benchmark wird den ersten Teil Ihrer Frage beantworten: jsben.ch/#/Y9jDP
EscapeNetscape

Antworten:

143

Die Kurzversion: Arrays sind meist schneller als Objekte. Es gibt jedoch keine 100% richtige Lösung.

Update 2017 - Test und Ergebnisse

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'});
}

Versuchsaufbau Testergebnisse

Originaler Beitrag - Erklärung

Ihre Frage enthält einige Missverständnisse.

In Javascript gibt es keine assoziativen Arrays. Nur Arrays und Objekte.

Dies sind Arrays:

var a1 = [1, 2, 3];
var a2 = ["a", "b", "c"];
var a3 = [];
a3[0] = "a";
a3[1] = "b";
a3[2] = "c";

Dies ist auch ein Array:

var a3 = [];
a3[29938] = "a";
a3[32994] = "b";

Es ist im Grunde ein Array mit Löchern, da jedes Array eine kontinuierliche Indizierung aufweist. Es ist langsamer als Arrays ohne Löcher. Das manuelle Durchlaufen des Arrays ist jedoch (meistens) noch langsamer.

Dies ist ein Objekt:

var a3 = {};
a3[29938] = "a";
a3[32994] = "b";

Hier ist ein Leistungstest mit drei Möglichkeiten:

Lookup Array gegen Holey Array gegen Object Performance Test

Eine ausgezeichnete Lektüre zu diesen Themen im Smashing Magazine: Schreiben von schnell speichereffizientem JavaScript

Alp
quelle
1
@ Moshe Und damit sollte jede Diskussion über die Leistung in Javascript geführt werden. : P
Täuschung
9
Dies hängt wirklich von den Daten und der Größe der Daten ab, mit denen Sie arbeiten. Sehr kleine Datensätze und kleine Objekte erzielen mit Arrays eine viel bessere Leistung. Wenn Sie über Suchvorgänge in einem großen Datensatz sprechen, in dem Sie ein Objekt als Karte verwenden, ist ein Objekt effizienter. jsperf.com/array-vs-object-performance/35
f1v
5
Stimmen Sie mit f1v überein, aber Revision 35 hat einen Fehler im Test: if (a1[i].id = id) result = a1[i];Sollte sein: if (a1[i].id === id) result = a1[i];Test http://jsperf.com/array-vs-object-performance/37 korrigiert das
Charles Byrne
1
Siehe http://jsperf.com/array-vs-object-performance/71 . Hat eine kleinere Teilmenge von Daten (ich hätte eine Schleife für die Datenerstellung haben sollen, aber ich wollte Löcher im Array) von ungefähr 93 Objekten im Vergleich zu 5000. Äußere Schleife sind IDs, nach denen im Objektarray (Anfang Mitte und Ende) und gesucht gesucht werden soll Ich habe auch eine fehlende ID hinzugefügt, damit die Array-Suche alle Elemente durchlaufen muss. Holey Array, das Objekt nach Schlüssel, dann Manual Array. Wie f1v sagte, hängt es wirklich von der Größe der Daten ab und davon, wo sich die Daten für die manuelle Arraysuche befinden.
Charles Byrne
4
Diese Antwort könnte verbessert werden, indem die Schlussfolgerungen von jsPerf hier in diesem Beitrag zusammengefasst werden - zumal die Ergebnisse von jsPerf die eigentliche Antwort auf die Frage sind. Der Rest ist extra. Dies ist in Zeiten relevanter, in denen jsPerf nicht verfügbar ist (wie jetzt). meta.stackexchange.com/questions/8231/…
Jeff
23

Es ist überhaupt keine Leistungsfrage, da Arrays und Objekte sehr unterschiedlich funktionieren (oder zumindest sollen). Arrays haben einen kontinuierlichen Index 0..n, während Objekte beliebige Schlüssel beliebigen Werten zuordnen. Wenn Sie bestimmte Schlüssel angeben möchten, ist die einzige Auswahl ein Objekt. Wenn Sie sich nicht für die Schlüssel interessieren, handelt es sich um ein Array.

Wenn Sie Menge frei wählbar (numerisch) Tasten auf einem Array versuchen, Sie haben wirklich einen Leistungsverlust , da verhaltens das Array in allen Indizes füllen in-zwischen:

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

(Beachten Sie, dass das Array tatsächlich keine 99 undefinedWerte enthält, sich jedoch so verhält, da Sie das Array irgendwann iterieren .)

Die Literale für beide Optionen sollten sehr deutlich machen, wie sie verwendet werden können:

var arr = ['foo', 'bar', 'baz'];     // no keys, not even the option for it
var obj = { foo : 'bar', baz : 42 }; // associative by its very nature
täuschen
quelle
Ich möchte keine bestimmten Schlüssel liefern. Ich möchte wissen, was besser abschneidet, und ich werde damit arbeiten. OK, bei der zweiten Option kommt ein Array nicht in Frage. aber was ist mit einem Objekt gegen das nicht assoziative Array?
Moshe Shaham
1
@Moshe In Javascript gibt es kein nicht assoziatives Array. Wenn Sie Schlüssel (Zahlen oder Zeichenfolgen) benötigen, verwenden Sie ein Objekt. Wenn Sie nur eine (geordnete) Liste benötigen, verwenden Sie Arrays. Zeitraum. Leistung geht nicht in die Diskussion ein. Wenn die Leistung entscheidend ist und Sie in beiden Fällen mit Ihren Schlüsseln leben können, versuchen Sie, welche für Sie besser geeignet ist.
Täuschung
5
Aber ich möchte wissen, was besser funktioniert: Abrufen eines Objekts aus einem Array (durch Durchlaufen) oder aus einem "assoziativen" Objekt, bei dem die ID der Schlüssel ist. Es tut mir leid, wenn meine Frage nicht klar war ...
Moshe Shaham
2
@Moshe Wenn Sie in einem Objekt oder Array über einen Schlüssel auf etwas zugreifen, ist dies immer unendlich schneller als das Durchlaufen des Containers, um zu finden, was Sie wollen. Der Unterschied beim Zugriff auf ein Element per Schlüssel in einem Array oder in einem Objekt ist wahrscheinlich vernachlässigbar. Looping ist offensichtlich in beiden Fällen schlimmer.
Täuschung
1
@deceze - Wie wäre es mit einem Array, das Benutzerobjekte enthält, und um das Objekt des Benutzers abzurufen, wird eine Schleife benötigt, um ein Benutzerobjekt basierend auf einem user_id"vs" -Objekt mit Schlüsseln abzurufen, da auf das user_idBenutzerobjekt user_idals Schlüssel zugegriffen werden kann "? Welches ist in Bezug auf die Leistung besser? Anregungen hierzu sind willkommen :)
Rayon
13

Mit ES6 wäre die leistungsfähigste Methode die Verwendung einer Karte.

var myMap = new Map();

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

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

Sie können ES6-Funktionen heute mit einem Shim ( https://github.com/es-shims/es6-shim ) verwenden.

Die Leistung variiert je nach Browser und Szenario. Aber hier ist ein Beispiel, wo Mapes am leistungsfähigsten ist: https://jsperf.com/es6-map-vs-object-properties/2


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

Sandstrom
quelle
11
Haben Sie eine Ressource, um dies zu sichern? Nach meinen bisherigen Beobachtungen sind ES6-Sets schneller als Arrays, aber ES6-Maps sind langsamer als Objekte und Arrays
Steel Brain
1
Es ist mehr "semantisch", nicht performanter, was die Frage war.
AlexG
3
@AlexG ziemlich sicher, dass der Titel klar angibt efficiency.
Qix - MONICA wurde
@ Qix Ja, mein schlechtes: o
AlexG
8

Wenn Sie in NodeJS das kennen ID, ist die Schleife durch das Array im Vergleich zu sehr langsam 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');

Und die Ergebnisse:

Array result:
arrTimer: 12.857ms
Object result:
objTimer: 0.051ms

Auch wenn die Such-ID die erste im Array / Objekt ist:

Array result:
arrTimer: 2.975ms
Object result:
objTimer: 0.068ms
Paweł
quelle
5

Ich habe versucht, dies buchstäblich in die nächste Dimension zu bringen.

Bei einem zweidimensionalen Array, bei dem die x- und y-Achse immer gleich lang sind, ist es schneller:

a) Suchen Sie die Zelle, indem Sie ein zweidimensionales Array erstellen und den ersten Index nachschlagen, gefolgt vom zweiten Index, dh:

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

oder

b) Erstellen Sie ein Objekt mit einer Zeichenfolgendarstellung der x- und y-Koordinaten und führen Sie dann eine einzelne Suche für dieses Objekt durch, dh:

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

Ergebnis: Es stellt
sich heraus, dass es viel schneller ist, zwei numerische Indexsuchen für die Arrays durchzuführen, als eine Eigenschaftssuche für das Objekt.

Ergebnisse hier:

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

Davem M.
quelle
3

Das hängt von der Nutzung ab. Wenn es sich um Suchobjekte handelt, ist dies sehr schnell.

Hier ist ein Plunker-Beispiel zum Testen der Leistung von Array- und Objektsuchen.

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

Du wirst das sehen; Übernehmen Sie Milisecons , um nach 5.000 Artikeln in einer Array-Sammlung mit 5.000 Längen zu suchen3000

Das Suchen nach 5.000 Elementen im Objekt hat jedoch 5.000 Eigenschaften, nur Take 2oder 3Milisecons

Auch das Erstellen eines Objektbaums macht keinen großen Unterschied

Mehmet Otkun
quelle
0

Ich hatte ein ähnliches Problem, bei dem ich Live-Kerzenhalter aus einer Ereignisquelle speichern muss, die auf x Elemente beschränkt ist. Ich könnte sie in einem Objekt speichern lassen, in dem der Zeitstempel jeder Kerze als Schlüssel und die Kerze selbst als Wert fungieren würde. Eine andere Möglichkeit war, dass ich es in einem Array speichern konnte, in dem jeder Gegenstand die Kerze selbst war. Ein Problem bei Live-Kerzen besteht darin, dass sie weiterhin Updates mit demselben Zeitstempel senden, in dem das neueste Update die neuesten Daten enthält. Daher aktualisieren Sie entweder ein vorhandenes Element oder fügen ein neues hinzu. Hier ist also ein schöner Benchmark, der versucht, alle drei Möglichkeiten zu kombinieren. Arrays in der folgenden Lösung sind im Durchschnitt mindestens viermal schneller. Fühlen Sie sich frei zu spielen

"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)
}

Schlussfolgerung 10 ist hier die Grenze

Storing as objects 4183 nanoseconds 10
Storing as array 373 nanoseconds 10
PirateApp
quelle