2014-09-29 11 views
6

Ich bin in der Mitte eines Java-Projekts, das ein 'großes Wörterbuch' von Wörtern verwenden wird. Mit 'Wörterbuch' meine ich bestimmte Zahlen (int), die Strings zugewiesen sind. Und mit "groß" meine ich eine Datei in der Größenordnung von 100 MB. Die erste Lösung, die ich gefunden habe, ist wahrscheinlich die einfachste. Bei der Initialisierung lese ich die ganze Datei ein und erstelle eine große HashMap, die später verwendet wird, um Strings zu suchen.'Big Dictionary' Implementierung in Java

Gibt es eine effiziente Möglichkeit, dies zu tun, ohne dass die gesamte Datei bei der Initialisierung gelesen werden muss? Vielleicht nicht, aber was ist, wenn die Datei wirklich groß ist, sagen wir in der Reihenfolge des verfügbaren Arbeitsspeichers? Im Grunde suche ich nach einer Möglichkeit, die Dinge effizient in einem großen Wörterbuch zu suchen.

Danke für die Antworten bis jetzt, als Ergebnis habe ich realisiert, dass ich spezifischer in meiner Frage sein könnte. Wie Sie wahrscheinlich schon vermutet haben, hat die Anwendung mit Text Mining zu tun, insbesondere mit der Darstellung von Text in Form eines spärlichen Vektors (obwohl einige andere erfinderische Ideen hatten :)). Was für die Verwendung entscheidend ist, ist, in der Lage zu sein, Zeichenfolgen im Wörterbuch nachzuschlagen und ihre Schlüssel so schnell wie möglich zu erhalten. Der anfängliche Aufwand, die Wörterbuchdatei zu "lesen" oder in eine Datenbank zu indizieren, ist nicht so wichtig, solange die Nachschlagezeit optimiert ist. Nehmen wir an, dass die Wörterbuchgröße groß ist, vergleichbar mit der Größe des verfügbaren RAM.

+0

Sie können die Datei in bestimmten Byte-Größe lesen, speichern Sie es in einem HashMap-Objekt, und speichern Sie dann dieses Objekt als ein Bytestream-Objekt auf Ihrer Festplatte. Wiederholen Sie dies, bis Sie die gesamte Datei gelesen haben. –

+0

@Mohammad Das löst nicht wirklich den Anwendungsfall, bei dem die Eingabeobjekte größer sind als der verfügbare Speicher.Am Ende des Tages wirst du mit einer HashMap enden, die zu viele Objekte enthält. – Santa

+3

[Memory-Mapped-Dateien] (https://en.wikipedia.org/wiki/Memory-maped_file) kann hilfreich sein. Werfen Sie einen Blick auf [FileChannel.map()] (http://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileChannel.html#map%28java.nio.channels.FileChannel. MapMode,% 20long,% 20long% 29) und Sie können einen 'FileChannel' von einer' RandomAccessFile' erhalten. –

Antwort

3

Betrachten Sie ChronicleMap (https://github.com/OpenHFT/Chronicle-Map) in einem nicht replizierten Modus. Es ist eine Off-Heap-Implementierung von Java Map, oder, aus einem anderen Blickwinkel, ein Superlightweight NoSQL Schlüssel-Wert-Speicher.

Was es für die Aufgabe aus der Box nützlich macht:

  • Persistance auf der Festplatte über Memory-Mapped-Dateien (siehe Kommentar von Michał Kosmulski)
  • Lazy Load (Plattenseiten werden nur bei Bedarf geladen) -> Schnellstart
  • Wenn Ihr Datenvolumen größer ist als der verfügbare Arbeitsspeicher, wird das Betriebssystem selten verwendete Seiten automatisch auflösen.
  • Mehrere JVMs können dieselbe Zuordnung verwenden, da der Off-Heap-Speicher auf Betriebssystemebene gemeinsam genutzt wird. Nützlich, wenn Sie die Verarbeitung in einem Map-Reduced-ähnlichen Framework durchführen, z. G. Hadoop.
  • Strings werden in UTF-8-Form gespeichert, -> ~ 50% Speichereinsparungen, wenn Strings meist ASCII sind (wie maaartinus angegeben)
  • int oder long Werte nur 4 (8) Bytes nimmt, wie wenn Sie primitive- haben spezialisierte Kartenimplementierung.
  • Sehr wenig pro-Eintrag Speicher-Overhead, viel weniger als in Standard HashMap und ConcurrentHashMap
  • Gut konfigurierbare Gleichzeitigkeit über Lock-Striping, wenn Sie bereits benötigen, oder wird die Textverarbeitung in Zukunft parallelisieren.
+0

WOW. +1 von mir! Das ist sehr cool, persönlich scheint das etwas genau das zu sein, wonach er sucht, wenn er nicht zu einem db committieren will. Ich bin gespannt, was die Nachteile solcher "Off-Heap" -Implementierungen sind, das ist das erste, das ich jemals von ihnen gehört habe. Danke, dass du mir etwas wirklich Erstaunliches beigebracht hast! : 0) –

+0

@DevarshDesai was verhindert, dass ChronicleMap eine echte Silberkugel wird, ist, dass Schlüssel/Werte bei jeder Abfrage gemarshallt/demarschalliert werden sollen. Für 'String's ist dies leider der größte Overhead, da die UTF-8 <->' String'-Konvertierung ziemlich kompliziert ist. Wenn jedoch String-Daten als "byte []" gespeichert werden können, ist der Overhead viel niedriger und für primitive Schlüssel/Werte gibt es fast keinen Overhead.Wenn Schlüssel/Werte recht einfache Datenobjekte mehrerer primitiver (oder anderer Datenobjekt-) Felder sind, könnte der Ser/Deser-Overhead auch durch das Unterklassifizieren der "Byteable" -Schnittstelle vermieden werden. – leventov

+0

Es wird wahrscheinlich den Rest des Tages dauern, um zu verdauen, was Sie gerade gesagt haben, aber noch einmal - danke! Das ist sehr, sehr cool, und ich bin nicht überrascht, dass dies aus einem HFT-Repository heraus entstand (das war meine Vermutung, kurz bevor ich darauf klickte); Ich werde definitiv versuchen, zu OpenHFT beizutragen, wenn ich ihm etwas Zeit gebe! Hoffe, du hast eine schöne Woche vor dir! –

2

An der Stelle, an der Ihre Datenstruktur ein paar hundert MB zu RAM-Ordnungen ist, sollten Sie eine Datenstruktur zur Laufzeit nicht initialisieren, sondern eine Datenbank verwenden, die indexing unterstützt (was die meisten heutzutage tun) . Indexierung ist eine der wenigen Möglichkeiten, mit denen Sie den schnellsten Text abrufen können, sobald Ihre Datei so groß ist und Sie mit den Einstellungen Ihrer JVM - Xmx konfrontiert werden. Der Grund dafür ist, dass Ihre Datei unweigerlich zu crash your JVM geht, wenn Ihre Datei so groß oder viel größer als Ihre Einstellungen für die maximale Größe ist.

Wie zum Lesen der gesamten Datei bei der Initialisierung. Sie müssen dies schließlich tun, damit Sie den Text in Ihrem Code effizient suchen und analysieren können. Wenn Sie wissen, dass Sie nur einen bestimmten Teil Ihrer Datei gleichzeitig durchsuchen werden, können Sie lazy loading implementieren. Wenn nicht, können Sie genauso gut in die Kugel beißen und Ihre gesamte Datei in die Datenbank laden. Sie können parallelism in diesem Prozess implementieren, wenn es andere Teile Ihrer Codeausführung gibt, die davon nicht abhängen.

Bitte lassen Sie mich wissen, wenn Sie irgendwelche Fragen haben!

+0

Was auch immer Sie tun, jede Datenbank ist im Vergleich zu einer HashMap sehr langsam. Da das OP von 100 MB spricht, macht es überhaupt keinen Sinn. Was noch schlimmer ist: Wenn die Strings nicht in den Speicher passen, sind Sie Ihrem Betriebssystem und Ihrer Festplatte ausgeliefert ... und verlieren mehr als 5 Größenordnungen an Geschwindigkeit (100 ns HashMap vs. 10 ms Festplatte). Komprimieren der Strings mit einem Trie-Sound viel schneller. – maaartinus

+1

Meiner Erfahrung nach sind Datenbanken nicht wirklich so langsam (mit MongoDB konnte ich nicht einmal den Unterschied sagen), in der Tat können Sie sie extrem schnell machen, wenn Sie in der Lage sind, die von Datenbank-Interna gegebenen Tools richtig zu nutzen. Ich habe noch nie eine Datenstruktur mit einer Größe von 100 MB gesehen und erwähnte auch "RAM-Befehle". Zu diesem Zeitpunkt würde ich persönlich eine Datenbank verwenden, wie auch andere vorgeschlagen haben. Ich stimme zu, dass es viel schneller wäre, mehr Dinge im Speicher zu haben, aber ich gehe nicht davon aus, dass der Autor dieses Beitrags mehr Speicher für dieses Problem kaufen wird. –

+1

Was meinst du mit * so langsam *? Ich habe [diesen Benchmark] gefunden (https://blog.serverdensity.com/mongodb-benchmarks) ... die Zugriffszeiten sind 50 Mikrosekunden, wenn keine Platte involviert ist, das bedeutet 3 Größenordnungen langsamer. Die Zeiten mit einer SSD sind 10 Millisekunden. * Es gibt nichts in einer DB, was schneller als eine HashMap sein könnte, und es gibt ziemlich viel Aufwand. * Allein die [IPC] (http://en.wikipedia.org/wiki/Inter-Process_communication) kostet viel mehr als das Nachschlagen selbst . – maaartinus

2

Wie in einem Kommentar erwähnt, speichert eine Trie viel Speicher.

Sie sollten auch mit byte s statt char s betrachten, da dies Ihnen einen Faktor von 2 für reinen ASCII-Text speichert oder wenn Sie Ihren nationalen charset verwenden, solange sie nicht mehr als 256 verschiedene Buchstaben haben.

Auf den ersten Blick macht die Kombination dieser Low-Level-Optimierung mit tries keinen Sinn, da bei ihnen die Knotengröße von den Zeigern dominiert wird. Aber es gibt einen Weg, wenn du auf ein niedriges Level gehen willst.

Was ist entscheidend für die Verwendung ist in der Lage, Strings nach oben im Wörterbuch aussehen, erhalten Sie ihre Schlüssel so schnell wie möglich.

Dann vergessen Sie keine Datenbank, da sie im Vergleich zu HashMap s verdammt langsam sind.

Wenn es nicht in den Speicher passt, ist die billigste Lösung normalerweise, mehr davon zu bekommen.Andernfalls sollten Sie nur die gebräuchlichsten Wörter laden und etwas langsamer für die anderen tun (z. B. eine Speicherabbilddatei).


Ich wurde gebeten, auf eine gute Tries-Implementierung zu zeigen, besonders Off-Heap. Mir sind keine bekannt.

Angenommen, das OP benötigt keine Wandlungsfähigkeit, insbesondere keine Änderbarkeit der Tasten, alles sieht sehr einfach aus.

Ich denke, das ganze Wörterbuch könnte leicht in eine einzige ByteBuffer verpackt werden. Nimmt man hauptsächlich ASCII an und mit etwas Bithacking, würde ein Pfeil 1 Byte pro Pfeilmarkierungszeichen und 1 bis 5 Bytes für den Kindzeiger benötigen. Der Kindzeiger wäre relativ (d. H. Die Differenz zwischen dem aktuellen Knoten und dem Kind), wodurch die meisten von ihnen in ein einzelnes Byte passen würden, wenn sie in einem base 128 encoding gespeichert würden.

Ich kann nur den Gesamtspeicherverbrauch schätzen, aber ich würde sagen, etwas wie < 4 Bytes pro Wort. Die obige Komprimierung würde das Nachschlagen verlangsamen, aber immer noch nicht annähernd den Wert, den ein einzelner Plattenzugriff benötigt.

0

Es klingt zu groß, um im Speicher zu speichern. Entweder speichern Sie es in einer relationalen Datenbank (einfach, und mit einem Index auf den Hash, schnell), oder eine NoSQL-Lösung, wie Solr (kleine Lernkurve, sehr schnell).

Obwohl NoSQL sehr schnell ist, wenn Sie die Leistung wirklich optimieren möchten und es Einträge gibt, die viel häufiger nachgeschlagen werden als andere, sollten Sie einen Cache mit begrenzter Größe für die zuletzt verwendeten (etwa) 10000 Suchvorgänge verwenden.