Große Datenmenge wie speichern (HashMap? TreeMap?)

Status
Nicht offen für weitere Antworten.
P

Pida

Gast
Hallo zusammen,

mein Problem sieht folgendermaßen aus:

Aus einem großen Text werden alle Tokens (d.h. in etwa Wörter) eingelesen und mit der Häufigkeit ihres Auftretens gespeichert. Bisher habe ich dafür eine HashMap genommen. Beim Testen mit größeren Dateien funktioniert das aber nicht mehr, bei 5,5 Millionen Tokens (nicht: 5,5 Millionen Key-Value-Paare, damit könnte ich leben) ist quasi Schluss und die Performance des einlesens geht gegen null.

Ich vermute, dass zu viele Rehashes nötig waren und so das Einfügen neuer Elemente zu lange dauert.


Nun habe ich nach Lösungen gesucht und zwei mögliche gefunden:

- Ich verwende eine TreeMap. Laut API wird hier ein Zeitaufwand von log(n) garantiert.
- Ich verwende weiterhin eine HashMap, setze aber die initiale Kapazität und möglicherweise auch den load factor hoch an. Der Platzbedarf ist kein Problem: Das Programm soll nur alle Jubeljahre mal laufen.

Welche Variante ist zu empfehlen? Ich frage mich insbesondere, warum man nicht einfach immer eine TreeMap nehmen sollte und wie der Zeitaufwand der Hashmap aussieht, wenn ich genug Kapazität zuteile und so Rehashes vermeide.

Wenn ich die Daten habe, muss ich sie übrigens in beliebiger Reihenfolge abbarbeiten. Für die weiteren Operationen ist es also nicht notwendig, dass ich sortierten Zugriff auf die Schlüssel-/ Wertepaare erhalte.

Vielen Dank
Pida
 

musiKk

Top Contributor
Naja, das laesst sich doch einfach ausprobieren? Die zweite Variante ist einfach nur ein anderer Konstruktor-Aufruf und die erste klappt mit einer Zeile Aenderung, wenn du immer mit dem Map-Interface arbeitest.
 
P

Pida

Gast
Nicht ganz unrichtig, aber

- ich würde gerne verstehen, warum eine Variante besser ist (mich irritiert die angeblich garantierte logarithmische Zeitaufwand der TreeMap)
- auch ein passender Algorithmus braucht für eine Anwendung dieser Größenordnung ein Weilchen
- der Rechner mit dem Programm ist grade nicht verfügbar ;-)

Gruß
Pida
 
P

Pida

Gast
Um das noch etwas auszuführen:

Lt. API gilt für TreeMap folgendes:
This implementation provides guaranteed log(n) time cost for the containsKey, get, put and remove operations.
M.W. ist schneller als logarithmisch nicht drin, dabei sollte doch die - unsortierte - HashMap beim Einfügen (und das ist doch mit 'put' gemeint, oder?) schneller als die sortierte TreeMap sein.
 

Marco13

Top Contributor
"Konstant" (O(1)) ist schneller als Logarithmisch (O(log(n)). Wenn man bei einer TreeMap ein "put" macht, wird der Baum bis zu den Blättern durchsucht, und dort im passenden Blatt der neue Knoten eingefügt. Bei der HashMap wird (auf Basis des Hash-Wertes des Objekt) direkt die Position ausgerechnet, wo das Objekt eingefügt werden muss.

Falls ich das richtig verstanden habe, machst du jetzt sowas wie
Code:
for (all tokens t)
{
    if (map.containsKey(t)) map.put(t, map.get(t)+1);
    else map.put(t, 1);
}
Da sollte die Anzahl der Tokens auf die Performance des Einfügens keinen Einfluß haben. Man könnte überlegen, ob man sich eine counter-Klasse macht
Code:
for (all tokens t)
{
    Counter counter = map.get(t);
    if (counter != null) 
    {
        counter.increase();
    }
    else map.put(t, new Counter());
}
um das Automboxing zu sparen, aber das hat auf die asymptotische Zeit auch keinen Einfluß.

Bist du sicher, dass nicht das Einlesen selbst so lange dauert?
 
P

Pida

Gast
Danke, da hatte ich einen Denkfehler (konstant vs. log)

Ich mache bisher sowas, wobei der Code nach der While-Schleife nicht erreicht wird. Sprich: Hier liegt das Performance-Problem:

Code:
HashMap count = new HashMap()

while (tokens im input) {
    String token = nächstes token im input
    print (jedes 100 000. token); // bis 5,5 Millionen geht das schnell, dann Einbruch
    if (count[token] == null)
        count[token] = 0;
    count[token]++
}

Gruß
Pida
 

Marco13

Top Contributor
Die Fragen, wie du die Tokens liest (d.h. ob es vielleicht DAran liegt) und wie genau die die Werte in die Map legst, sind damit aber nicht beantwortet.
 
G

Guest

Gast
Hallo,

ich konnte jetzt nochmal testen. Zunächst lief es mit TreeMap deutlich schneller, aber bei 5,5 Mio. Tokens (was hier etwa 300000 Types, also Einträgen in der Map, entsprach) kam es zu Performanceeinbußen. Bei 6 Mio. Einträgen gab es dann eine Exception, da der Speicher ausging.

Nachdem ich der JVM mehr Speicher gegeben habe, läuft es jetzt problemlos und mit der HashMap etwas schneller.

Das Einlesen geschieht übrigens wie folgt (ist Groovy-Code, was sich hier aber nur in der kürzeren Deklaration der Variablen niederschlägt):

Code:
def FileReader fr = new FileReader(this.file)
	def reader = new BufferedReader(fr)
		
	def p = java.util.regex.Pattern.compile(/[,+()"'<>\[\]\s]/) 
	def sc = new Scanner (reader)
	sc.useDelimiter(p)

	while (sc.hasNext()) {
{
....
}

Siehst du da Optimierungspotential?

Danke
Pida
 

Marco13

Top Contributor
OK, wenn's zwischen 5.5M und 6M auf einmal kracht, könnten einige Geschwindigkeitseinbußen vorher schon mit dem Speichermangel zusammengehongen haben.

Mit Groovy kenne ich mich nicht aus. Was der Scanner mit dem Delimiter genau macht, weiß ich auch nicht. Falls er da bei jedem "getNext"-Aufruf irgendein RegEx-Pattern-Matching durchführt, könnte man das evt. deutlich beschleunigen, aber dazu müßte ich mir erstmal den Scanner-Code ansehen (bin im Moment aber an einem JDK-losen Rechner)
 

Ark

Top Contributor
Ich nehme das mal jetzt sofort in Angriff, habe auch schon eine Idee, wie ich das mache, und bitte um ein wenig Geduld. ;)

Ark
 
P

Pida

Gast
@Ark: Danke sehr :)

@Marco: Der Scanner kommt von Java. Mit this.file wird hier auf eine txt-Satei zugegriffen, darauf wird dann ein BufferedReader* erstellt.

Der Scanner holt dann aus diesem Datenstrom jeweils ein Token. Tokens sind definiert als die Zeichengruppen _zwischen_ den unterschiedlichen, durch den RegEx spezifizierten Delimitern (Whitespace, Anführungszeichen, Klammern usw.)

*Hier werde ich auch nochmal mit der Puffergröße experimentieren.
 

Ark

Top Contributor
Bitteschön:
Code:
package wordcounter;

import java.io.*;


/**
 * @author ark
 *
 */
public final class StringTree{

	private static final class Node{
		public char character;
		public int count;
		public Node brother;
		public Node child;
		public Node(){
			// nur zwecks public-Deklaration
		}
	}
	
	private PushbackReader r;
	
	// Die wohl wichtigste Eigenschaft:
	private Node tree;
	
	// Hier wird ein Bitmuster über alle Trennzeichen (Unicode)
	// aufbewahrt. Die Ordnung der Bits folgt der Little-Endian-Idee, wobei
	// gesetzte Bits Trennzeichen darstellen.
	private int[] delimiter=new int[2048];
	
	// Folgende Eigenschaften werden nur intern zur Verarbeitung über
	// Methodengrenzen hinweg genutzt, um Parameterübergaben zu ersparen.
	private char curr;
	private StringBuilder sb;
	
	public StringTree(Reader r,char[] delimiter){
		this.r=new PushbackReader(r,1);
		tree=new Node();
		
		// Hier werden die entsprechenden Bits gesetzt, um später mit O(1)
		// entscheiden zu können, ob ein Zeichen Begrenzer ist oder nicht.
		char c;
		for(int i=0;i<delimiter.length;i++){
			c=delimiter[i];
			this.delimiter[c>>>5]|=1<<c;
		}
		// Zur Wortbegrenzung würde ich spontan das folgende Muster verwenden,
		// aber natürlich kenne ich die gestellten Anforderungen nicht.
		// Begrenzer sind hier alle Zeichen <=0xFF, die nicht Buchstabe und
		// nicht Ziffer sind. Wenn dir dieses Muster gefällt, brauchst du
		// nur das Codestück über diesem Kommentar auszukommentieren und
		// stattdessen das folgende verwenden:
//		this.delimiter[0]=0xFFFFFFFF;
//		this.delimiter[1]=0xFC00FFFF;
//		this.delimiter[2]=0xF8000001;
//		this.delimiter[3]=0xF8000001;
	}
	
	// Hier kannst du den Baum beim Aufbauen bewundern.
	public void buildTree() throws IOException{
		int c;
		while(true){
			if((c=r.read())<0) return;
			r.unread(c);
			put(tree);// Der erste Buchstabe eines Wortes wurde gefunden.
		}
	}
	private void put(Node node) throws IOException{
		while(true){
			if(!next()){
				node.count++;
				//System.out.println("\t"+node.count);// bisher gezählt
				return;
			}
			//System.out.print(curr);// aktuell besuchtes Zeichen
			if(node.child==null){
				node.child=new Node();
				node.child.character=curr;
				node=node.child;
				continue;
			}
			node=findMatchingBrother(node.child,node);
		}		
	}
	// Ein Knoten kennt nicht alle Kinder, wohl aber ein Kind und dieses alle
	// seine Geschwister. Dabei wird deren Reihenfolge schön sortiert gehalten.
	private Node findMatchingBrother(Node brother,Node parent){
		if(brother.character>curr){
			parent.child=new Node();// voranhängen
			parent.child.character=curr;
			parent.child.brother=brother;
			return parent.child;			
		}
		while(brother.character<curr){
			if(brother.brother==null){
				brother.brother=new Node();// ans Ende hängen
				brother.brother.character=curr;
				return brother.brother;
			}
			parent=brother;// nur als Platzhalter bedient
			brother=brother.brother;
			if(brother.character>curr){
				parent.brother=new Node();// dazwischenhängen
				parent.brother.character=curr;
				parent.brother.brother=brother;
				return parent.brother;
			}
		}
		return brother;// existiert bereits
	}

	// Diese kleine Hilfsmethode holt das nächste Zeichen und gibt true zurück,
	// wenn es den Anforderungen an ein Wort genügt.

	private boolean next() throws IOException{
		int x=r.read();
		char c=(char)x;
		if(x<0||((delimiter[c>>>5]&1<<c)!=0)){
			return false;
		}
		curr=(char)x;
		// Wenn Groß-/Kleinschreibung ignoriert werden soll, musst du
		// stattdessen die folgende Zeile verwenden. Die Wörter können dann
		// aber freilich nicht mehr 1:1 wiedergegeben werden.
//		curr=Character.toLowerCase((char)x);
		return true;
	}
	
	// gibt den gesamten Baum aus
	public void printResult(){
		sb=new StringBuilder();
		printNode(tree.child);// die Wurzel hat nur alle Trennzeichen gezählt
		sb=null;
	}
	private void printNode(Node node){
		sb.append(node.character);
		if(node.count>0){
			StringBuilder tempsb=new StringBuilder(
				sb.length()+1+(int)Math.ceil(Math.log10(node.count)+1)
			);
			tempsb.append(sb).append("\t").append(node.count);
			System.out.println(tempsb.toString());
		}
		if(node.child!=null){
			printNode(node.child);
		}
		sb.deleteCharAt(sb.length()-1);
		if(node.brother!=null){
			printNode(node.brother);
		}
	}
	
	// Hier wird der gesamte Baum in die Luft gejagt
	// (also alle Kanten werden entfernt).
	public void dispose(){
		disposeNode(tree);
	}
	private void disposeNode(Node node){
		if(node.child!=null){
			disposeNode(node.child);
			node.child=null;
		}
		if(node.brother!=null){
			disposeNode(node.brother);
			node.brother=null;
		}
	}
	
	// zum Test
	public static void main(String[] args) throws Exception{
		char[] delimiter=new char[]{
			'[',',','+','(',')', '\"','\'','<','>','[',']',
			' ','\t','\n','\u000B','\f','\r'
		};
		Reader r=new FileReader("src/wordcounter/StringTree.java");// UTF-8
		StringTree stree=new StringTree(r,delimiter);
		stree.buildTree();
		r.close();
		stree.printResult();
		stree.dispose();
	}
	// Methoden zum Einfügen, Löschen und Zählen eines einzelnen Wortes
	// kannst du ja dann selbst implementieren. ;-)
}
Lies dir vor Verwendung bitte auch die Kommentare durch, sie könnten interessant sein. ;)

Die Idee hinter diesem Algorithmus ist folgende: Die Reihenfolge der Zeichen im String mit den Zeichen selbst gibt den Pfad an, über den man das entsprechende Wort findet. Wenn z.B. zuerst das Wort

DES

hinzugefügt wird, sieht der entsprechende Pfad im Baum so aus (Anzahl in eckigen Klammern):

D[0] -> E[0] -> S[1]

Wenn jetzt das Wort

DESSEN

hinzugefügt wird, erweitert sich der Baum wie folgt:

D[0] -> E[0] -> S[1] -> S[0] -> E[0] -> N[1]

Leider habe ich bis heute nicht verstanden, wie ein Rot-Schwarz-Baum funktioniert, geschweige denn, wie man ihn implementiert. Aus diesem Grund erfolgt die Suche nach dem passenden Bruder in einer (sortiert gehaltenen) verketteten Liste, also mit O(n) und nicht mit O(log n). Wenn du Lust, Laune, Zeit und das notwendige Know-How hast, kannst du ja einen speziell an die Bedürfnisse angepassten Rot-Schwarz-Baum verwenden. Interesse an einer solchen Implementierung habe ich dann natürlich auch. ;)

Ich bitte um Feedback (Fehler dürfen natürlich auch gemeldet werden ;)) und hoffe, dass damit das Problem gelöst ist. (Ich weiß ja nicht, wie die Verteilung ist. Der Algorithmus arbeitet um so speicherschonender, je länger das Präfix eines Tokens ist, das (Präfix) mit dem Präfix der bisher eingefügten Tokens übereinstimmt. Wenn also vorrangig die Suffixe gleich sind, müsste man an den entsprechenden Stellen anders herum arbeiten bzw. aus dem Stream rückwärts lesen. Wenn man Aussagen über die Anzahl "erlaubter" Zeichen machen kann, kann nötigenfalls von char auf byte reduziert werden, um Speicherplatz zu sparen. Auch eine Verkleinerung der Zählvariablen von 32 auf 24 oder sogar nur 16 Bit wäre denkbar, wenn nötig.)

Ark

EDIT: Ich habe in der Methode dispose() die Zeile herausgenommen, die die Wurzel entfernt hat. Nach Aufruf von dispose() kann der Baum also wiederverwendet werden. Eine Methode, die einen neuen Reader einsetzt, musst du dann noch selbst implementieren, falls du über mehrere Dateien hinweg zählen willst.

EDIT2: Mir ist noch etwas eingefallen, um schonender mit dem Speicher umzugehen. Allerdings würde dann auch Abfall produziert (was bei der momentanen Implementierung der Fall ist). Falls also noch immer der Speicher knapp werden sollte: Es besteht noch immer Optimierungspotential. ;) Allerdings sieht dann der Worst Case speicherplatztechnisch sogar schlechter aus; die von mir gerade angedachte Optimierung ist also nur bedingt eine Optimierung.

Ark
 
Status
Nicht offen für weitere Antworten.
Ähnliche Java Themen
  Titel Forum Antworten Datum
P Datentypen Große Datenmenge Sortiert halten Allgemeine Java-Themen 12
P Performance: Ziehen ohne Zurücklegen (große Datenmenge) Allgemeine Java-Themen 10
B Welcher Datentyp für sehr große Zahlenbereiche? Allgemeine Java-Themen 1
F Große Datenmengen effizient programmieren Allgemeine Java-Themen 51
N Das große O berechnen Allgemeine Java-Themen 2
F Best Practice Große Anzahl an Objekten speichern und lesen Allgemeine Java-Themen 19
R Große Zahlen in Worten abkürzen Allgemeine Java-Themen 10
K Große JSON-Dateien schnell und effizient verarbeiten Allgemeine Java-Themen 16
K Große Mengen an Daten speichern Allgemeine Java-Themen 9
VfL_Freak Große und seltsame Probleme nach Java-Update auf V1.8.0_91 Allgemeine Java-Themen 3
E Best Practice Verdammt große Objekte Allgemeine Java-Themen 10
P Große Datenstruktur im Speicher halten Allgemeine Java-Themen 13
M Einfluss von Caching auf die Performance (große Arrays) Allgemeine Java-Themen 24
U Große Liste von Strings mit indiziertem Zugriff Allgemeine Java-Themen 31
D große Textdatei filtern Allgemeine Java-Themen 13
M Große Datei mit Regex durchsuchen Allgemeine Java-Themen 4
R POI große Exceldatei schreiben Allgemeine Java-Themen 7
R Dateigestützte Collection für große Datenmengen Allgemeine Java-Themen 5
K Scanner - große Textfile, nur 0 ab betim. Wert Allgemeine Java-Themen 4
trash Das große Problem: .jar Archiv Allgemeine Java-Themen 19
J Große Datei einlesen und gestückelt verarbeiten Allgemeine Java-Themen 4
I Große Datei am effektivsten/performantesten auslesen und auswerten? Allgemeine Java-Themen 6
S große CSV-Dateien Importieren. Beste Lösung ?! AWS,S3,Hadoop!? Allgemeine Java-Themen 4
P große double Zahlen und modulo Allgemeine Java-Themen 8
O Große Anzahl Bilder laden Allgemeine Java-Themen 7
A Mit RegEx große Dokumente erfassen Allgemeine Java-Themen 14
X Wie verdammt große Datein öffnen? Allgemeine Java-Themen 2
G Große Datenmengen per JDBC Allgemeine Java-Themen 5
G Große XML-Dateien einlesen und auswerten . Allgemeine Java-Themen 2
I JNI - Große Daten übertragen Allgemeine Java-Themen 6
T Große Dateibestände löschen - Speicherproblem Allgemeine Java-Themen 20
S Große ArrayListen Allgemeine Java-Themen 8
S große Datei einlesen! Allgemeine Java-Themen 7
J Große Zahl (double) as text ausgeben? Allgemeine Java-Themen 2
S Kleines Eclipse Problem, große Wirkung Allgemeine Java-Themen 7
H Referenzen statt Objekte für große Speicherstrukturen Allgemeine Java-Themen 19
K Große Herausforderung Allgemeine Java-Themen 2
F Zu große Werte beim byteweisen Lesen mit BufferedReader.read Allgemeine Java-Themen 5
D Große Klasse - was fällt euch so ins Auge? Kritik bitte! Allgemeine Java-Themen 10
M Große Dateien laden Allgemeine Java-Themen 2
F Große Dateien schnell einlesen Allgemeine Java-Themen 14
OnDemand Zugangsdaten externer Systeme sicher speichern Allgemeine Java-Themen 8
Z Passwort Versuche speichern Allgemeine Java-Themen 8
M Eigene Datenstruktur um eine Menge zu speichern Allgemeine Java-Themen 3
8u3631984 Bilder in Datenbank speichern - sinnvoll Allgemeine Java-Themen 5
melaniemueller Einzelne Zeile aus einer txt Datei in einem String speichern Allgemeine Java-Themen 12
I Hibernate Envers - Aufruf der Methode zum Speichern selbst ausführen oder managen? Allgemeine Java-Themen 0
killig Textdatei einlesen und in HashMap speichern (duplikate entfernen) Allgemeine Java-Themen 12
J (Geplante) Änderungen an einer Datei vorübergehend speichern und anwenden? Allgemeine Java-Themen 12
N zweidimensionalen Array in dreidimensionalen Array speichern Allgemeine Java-Themen 4
temi Lösung zum Speichern von Deltafiles Allgemeine Java-Themen 6
J Java Filechooser Speichern Allgemeine Java-Themen 8
N Arrayliste in eine Datei speichern Allgemeine Java-Themen 4
H Elemente aus ArrayList in Array speichern Allgemeine Java-Themen 8
platofan23 Wie .txtDatei im Java Eclipse-Projekt bzw. in der Jar speichern? Allgemeine Java-Themen 7
MiMa Werte in liste speichern? Allgemeine Java-Themen 3
S Swing Speichern nach Button-Klick Allgemeine Java-Themen 5
H ArrayListe in CSV Datei speichern Allgemeine Java-Themen 6
H Mehrere Datentypen in einer Arraylist speichern Allgemeine Java-Themen 9
H Objekte speichern und laden Allgemeine Java-Themen 10
H Objekte speichern und laden Allgemeine Java-Themen 1
H Objekt speichern und laden Allgemeine Java-Themen 1
H Objekt speichern und laden Allgemeine Java-Themen 1
T Speichern von Objekten Allgemeine Java-Themen 2
M Schnelleres Speichern von XML-Daten über URLConnection Allgemeine Java-Themen 4
D .txt Datei in .jar Datei speichern Allgemeine Java-Themen 3
M Key-File im selben Ordner speichern? Allgemeine Java-Themen 18
J int Werte in einer anderen Klasse in Arrays speichern Allgemeine Java-Themen 3
Aruetiise Funktion(y = mx+n) in String speichern und berechnen Allgemeine Java-Themen 9
S Eindimensionales Array in zweidimensionales Array speichern Allgemeine Java-Themen 5
offi Excel mit Inhalten aus DB öffnen ohne zu speichern Allgemeine Java-Themen 8
MiMa Speichern von Programmeinstellungen in Datei Allgemeine Java-Themen 7
B Von String zu <Objekt> ||Speichern/Laden Allgemeine Java-Themen 17
Arif Input/Output Dateien im Jar-Programm speichern Allgemeine Java-Themen 12
Q-bert Strings aus der JList in eine Datenbank speichern Allgemeine Java-Themen 1
L CSV File lesen, in ArrayList speichern und ausgeben Allgemeine Java-Themen 3
Q-bert Daten von Java Programm speichern Allgemeine Java-Themen 4
@SupressWarnings() Feste Kosten speichern Allgemeine Java-Themen 4
N ZIp datei direkt im eclipse speichern Allgemeine Java-Themen 4
N Das Ende von bestimmten zeilen in text datei ändern und speichern Allgemeine Java-Themen 3
C Best Practice Speichern kleineren Mengen Stammdaten? Allgemeine Java-Themen 3
X Mehrere booleans in Datei Speichern, Updaten und Laden Allgemeine Java-Themen 1
F Json in sql speichern und lesen Allgemeine Java-Themen 10
F Alte Passörter mit Gson und Json in SQL speichern? Allgemeine Java-Themen 5
K API-Key sicher speichern Allgemeine Java-Themen 2
B Zahlen manuell eingeben und in Array Speichern Allgemeine Java-Themen 2
K Input/Output String aus einer Datei einlesen und in anderer Datei speichern Allgemeine Java-Themen 20
Tacofan Bilder in Resource speichern Allgemeine Java-Themen 6
C Objekte in Array List speichern? Allgemeine Java-Themen 1
OnDemand Objekte speichern Allgemeine Java-Themen 8
O Klassen Bruch im gleichen Objekt Speichern Allgemeine Java-Themen 1
J Text lesen und in Variablen speichern Allgemeine Java-Themen 3
U Variablen Stringarrays mit wenig verschiedenen Zeichen effizienter speichern Allgemeine Java-Themen 10
HarleyDavidson Input/Output Heruntergeladene Datei direkt nach dem Download öffnen ohne zu speichern Allgemeine Java-Themen 1
J Daten persistent speichern Allgemeine Java-Themen 14
S JavaMail - MailSubject,MailFrom,MailDate in String Array speichern NullPointerException Allgemeine Java-Themen 2
M Objekt serialisieren/deserialisieren und in einer SQLite-Datenbank speichern Allgemeine Java-Themen 3
R HtmlUnit: Canvas als Bild speichern Allgemeine Java-Themen 0
E KeyCode in anderer Klasse speichern Allgemeine Java-Themen 2
M YouTube-Video herunterladen und speichern Allgemeine Java-Themen 10

Ähnliche Java Themen

Neue Themen


Oben