2016-07-06 8 views
2

Ich habe ein Projekt, das eine große Datenmenge verarbeitet, die in eine Excel-Datei geschrieben wird. Ich speichere diese Daten in einer statischen HashMap in der Form Map<List<String>, Integer>, wobei die Größe der Liste nur 3 ist. Die Anzahl der Einträge in der Map kann jedoch irgendwo zwischen 0 und 11.300 liegen.Speicher Effiziente Methode zum Behandeln einer großen HashMap

Der Fluss dieses Projekts ist:

  • laden Karte oben mit Einträgen

  • Iterate Karte und tun Sachen

  • löschen Karte für den nächsten Satz von Einträgen

Was ich kürzlich über HashMap herausgefunden habe, ist, wie es die Größe ändert, wenn die eingestellte Größe verletzt wird. Meine Map wird also nicht nur ständig auf dramatische Längen angepasst, sondern es könnte auch sehr gut 20.000 leere Einträge enthalten, wenn ich den größten Satz von Einträgen lösche.

Also ich versuche, dieses Ding Mikro-optimieren, und ich bin mit einem Dilemma fest, wie dies zu tun ist. Meine beiden Gedanken sind:

  1. Stellen Sie den Standardwert des anfänglichen HashMap auf einen Wert, der es erlauben würde, die meisten immer nur einmal die Größe neu

  2. Reinitialize die HashMap mit der durchschnittlichen Größe, die Neudimensionierung und erlauben den Garbage Collector wird für jeden neuen Eintrag Set erwartet zu begrenzen einige bis reinige

Meine Intuition sagt mir, Option zwei könnte die vernünftigste sein, aber das könnte noch unter Beweis stellen für viele Größenänderung abhängig vom nächsten Eintragssatz. Aber dann beschränkt die erste Option die Größenanpassung auf eine einmalige Operation, lässt dann aber buchstäblich Tausende von Null-Einträgen übrig.

Sind eine meiner zwei vorgeschlagenen Lösungen besser als die andere, gibt es nicht viel Unterschied in der Speicherverbesserung zwischen den beiden, oder könnte es eine andere Lösung geben, die ich übersehen habe (was die Datenstruktur nicht verändert)?

EDIT: Nur für einen Kontext, ich möchte dies tun, weil gelegentlich das Projekt aus Heap-Speicher läuft und ich versuche zu bestimmen, wie viel von Auswirkungen dieser gigantischen Karte ist oder sein könnte.

EDIT2: Nur um zu verdeutlichen, ist die Größe der Map selbst der größere Wert. Die Schlüsselgröße (dh der Liste) ist immer nur am 3.

+1

Tun Sie nicht die Option 2. Wenn Sie genug Speicher haben, um den schlimmsten Fall zu bewältigen, d. H. 11300 'List'-Objekte als Schlüssel zum Zuordnen, dann haben Sie genug Speicher für den gesamten Prozess. Es gibt nichts wirklich gewonnenes, wenn man die Karte schrumpft, aber man verliert Leistung durch die erneute Expansion. Die durch die Schrumpfung gespeicherte Speichermenge ist minimal, verglichen mit allem anderen. Dies ist oder vorausgesetzt, es ist ein kontinuierlicher Prozess. Bewahre die große, aber leere Karte nicht für längere Zeit auf, ohne sie zu benutzen. In diesem Fall entfernen Sie die Karte und weisen Sie sie als nächstes neu zu. – Andreas

+0

Gibt es einen Grund, warum Sie TreeMap nicht verwenden können? Ich denke nicht, dass es merklich langsamer wäre (log_2 (11300) ist nur 13) und es wird kein verschwendeter Speicherplatz übrig bleiben. –

+0

@Oliver Der Map-Key ist eine 'List', die nicht 'Comparable' ist und die Verwendung von' TreeMap' verhindert. Könnte einen benutzerdefinierten "Comparator" liefern, aber dann müssten Sie sich noch für eine Reihenfolge der Listen entscheiden, die möglicherweise nicht durchführbar ist. Außerdem verwendet eine 'TreeMap' mehr Speicherplatz als eine' HashMap'. – Andreas

Antwort

2

Ich habe einige der Forschung, indem Sie auf dieser Seite zu enden: How does a HashMap work in Java

Die zweite letzte Position mit Größenänderung Overhead zu tun hat, die besagt, die Standardeinstellungen für Eine HashMap ist eine size von 16 und eine factorLoad von 0.75.

Sie könnten diese Werte bei der Initialisierung ändern, so die size von 11300 und ein factorLoad von 1, die Karte Bedeutung nicht an Größe zunehmen, bis Ihr Maximum erreicht wurde, was in Ihrem Fall, wie ich es verstehe, wird noch nie.

Ich habe ein schnelles Experiment, mit diesem Code:

public static void main(String[] args) throws Exception { 
    Map<String, Integer> map = new HashMap<>(11000000, 1); 
    //  Map<String, Integer> map = new HashMap<>(); 
    for (int i = 0; i < 11000000; i++) { 
     map.put(i + "", i); 
    } 
    System.out.println(map.size()); 
    Thread.sleep(9000); 
} 

das beide Map Initialisierungen Swapping, und dann den Speicher überprüfte es in Task Manager verbraucht.

Mit der anfänglichen Größe und und factorLoad eingestellt, verwendet es ~1.45GB des Speichers. Ohne die eingestellten Werte verwendet es ~1.87GB des Speichers.

Die Neuinitialisierung der Map jedes Mal, anstatt es für einen potenziell kleineren Map zu löschen, um seinen Platz einzunehmen, wird langsamer sein, aber Sie würden möglicherweise mit mehr Speicher vorübergehend enden.

Sie könnten auch beides tun. Neu initialisieren, um die Anfangsgröße und die factorLoad Eigenschaften festzulegen, sollten Sie die Anzahl der List Objekte für jeden Zyklus kennen. Der Artikel schlägt auch vor, dass die Java 8 HashMap, obwohl potenziell schneller, potenziell mehr Speicheraufwand als in Java 7 haben könnte. Es könnte einen Versuch wert sein, das Programm in beiden Versionen zu kompilieren und zu sehen, die eine verbesserte Speicherlösung bietet . Wäre interessant, wenn nichts anderes.

+0

Das ist ein sehr guter Fund. Zufälligerweise leite ich Java 8, also ist es definitiv einen Versuch wert. Ich werde beide Vorschläge, die Sie gerade gemacht haben, manipulieren und sehen, wie sich das auf meine Leistung auswirkt. –

+0

Es scheint so viel Speicher freizugeben, dass die Anwendung vollständig ausgeführt werden kann. Ich "soll" es unterlassen, Danke zu sagen, aber danke. Dies war ein guter Anfang, um die Speichernutzung zu verbessern –