SlideShare uma empresa Scribd logo
1 de 31
Baixar para ler offline
Angewandte	
  Informa/k	
  für	
  
Ingenieure/-­‐innen	
  
Michael	
  Zilske	
  
Angenommen,	
  ich	
  möchte	
  	
  
•  eine	
  Textdatei	
  einlesen	
  (zum	
  Beispiel	
  einen	
  Roman),	
  	
  
•  die	
  Häufigkeiten	
  aller	
  unterschiedlichen	
  vorkommenden	
  
Wörter	
  zählen,	
  
•  diese	
  Wörter	
  mit	
  ihren	
  Häufigkeiten	
  als	
  Rangliste	
  ausgeben.	
  
	
	
	
	
	
	
	
	
	
	
	

	the
	to	
	of	
	and
	a 	
	was
	in	
	I 	
	her
	not
	be	

	12231	
	11979	
	10565	
	10285	
	 6627	
	 5341	
	 5334	
	 5206	
	 5015	
	 4115	
	 4078
public static void main(String[] args) throws IOException {	
	new CorpusCount().run();	
}	
	
void run() throws IOException { 	 		
	readFile("files/emma.txt");	
	readFile("files/sense.txt");	
	readFile("files/persuasion.txt");	
	sort();	
	write();	
}	
  

Ich	
  habe	
  mir	
  also	
  drei	
  Jane-­‐Austen-­‐Romane	
  aus	
  dem	
  Internet	
  
besorgt.	
  Weil	
  ich	
  die	
  nacheinander	
  bearbeiten	
  will,	
  schreibe	
  ich	
  
mir	
  eine	
  separate	
  Methode	
  readFile().	
  Danach	
  will	
  ich	
  die	
  
gesammelten	
  Daten	
  sor/eren	
  und	
  dann	
  herausschreiben.	
  
	
  
Da	
  diese	
  Methoden	
  Dateiopera/onen	
  ausführen,	
  deklarieren	
  sie	
  
IOExcep/ons.	
  Ich	
  deklariere	
  sie	
  weiter.	
  
void readFile(String filename) throws IOException {	
	FileReader fr = new FileReader(filename);	
	BufferedReader br = new BufferedReader(fr);	
	String line;	
	while ((line = br.readLine()) != null) {	
	 	String[] words = line.split(" ");	
	 	for (String word : words) {	
	 	 	countWord(word);	
	 	}	
	}	
	br.close();	
}	
  

Diese	
  Methode	
  haben	
  wir	
  im	
  Prinzip	
  letzte	
  Woche	
  schon	
  gesehen.	
  
Man	
  muss	
  Reader	
  und	
  Writer	
  übrigens,	
  wenn	
  man	
  sie	
  nicht	
  mehr	
  
braucht,	
  mit	
  close()	
  schließen,	
  denn	
  Dateizugriffe	
  sind	
  vom	
  
Betriebssystem	
  verwaltete,	
  beschränkte	
  Ressourcen.	
  
private class Entry {	
	String word;	
	int count;	
}	
	
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
}	
  

Was	
  ich	
  berechnen	
  möchte,	
  ist	
  eine	
  Tabelle,	
  deren	
  Einträge	
  
Wörter	
  mit	
  ihren	
  (bisherigen)	
  Häufigkeiten	
  sind.	
  Für	
  jedes	
  Wort,	
  
das	
  ich	
  einlese,	
  suche	
  ich	
  den	
  entsprechenden	
  Eintrag	
  heraus	
  und	
  
erhöhe	
  den	
  Zähler.	
  
	
  
Für	
  die	
  Tabellenzeile	
  eignet	
  sich	
  eine	
  eigene	
  Klasse	
  Entry.	
  Ich	
  kann	
  
eine	
  Klasse	
  innerhalb	
  einer	
  anderen	
  Klasse	
  definieren	
  –	
  wenn	
  ich	
  
sie	
  private	
  deklariere,	
  ist	
  sie	
  nur	
  innerhalb	
  der	
  umschließenden	
  
Klasse	
  sichtbar.	
  Prima	
  für	
  einmalige	
  Dinge	
  wie	
  Tabellenzeilen!	
  
private class Entry {	
	String word;	
	int count;	
}	
	
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
}	
  

Vorteil	
  einer	
  solchen	
  Konstruk/on	
  (private	
  Klasse):	
  Ich	
  brauche	
  
nicht	
  darüber	
  nachzudenken,	
  wie	
  sie	
  verwendet	
  werden	
  könnte.	
  
Im	
  Uhrenladenbeispiel	
  habe	
  ich	
  gesagt,	
  es	
  ist	
  gut,	
  Felder	
  immer	
  
als	
  private	
  zu	
  deklarieren,	
  um	
  nur	
  kontrollierten	
  Zugriff	
  darauf	
  zu	
  
erlauben	
  (wie	
  bei	
  der	
  Uhr,	
  die	
  man	
  nur	
  zerstören,	
  aber	
  nicht	
  
reparieren	
  kann).	
  
Bei	
  einer	
  privaten	
  Klasse	
  ist	
  das	
  unnö/g,	
  denn	
  alle	
  sta]indenden	
  
Zugriffe	
  stehen	
  in	
  derselben	
  Datei,	
  also	
  gibt	
  es	
  nichts	
  zu	
  
kontrollieren.	
  Ich	
  kann	
  munter	
  direkt	
  die	
  Felder	
  verwenden.	
  
private class Entry {	
	String word;	
	int count;	
}	
	
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
}	
  

Wie	
  finde	
  ich	
  nun	
  meinen	
  Eintrag,	
  bzw.	
  wie	
  verwalte	
  ich	
  meine	
  
Einträge	
  überhaupt?	
  
private class Entry {	
	String word;	
	int count;	
}	
	
List<Entry> entries = new ArrayList<Entry>();	
	
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
}	
	
private Entry findEntry(String word) {	
	for (Entry entry : entries) {	
	 	if (word.equals(entry.word)) {	
	 	 	return entry;	
	 	}	
	}	
	Entry entry = new Entry();	
	entry.word = word;	
	entries.add(entry);	
	return entry;	
}	
  
List<Entry> entries = new ArrayList<Entry>();	
	
private Entry findEntry(String word) {	
	for (Entry entry : entries) {	
	 	if (word.equals(entry.word)) {	
	 	 	return entry;	
	 	}	
	}	
	Entry entry = new Entry();	
	entry.word = word;	
	entries.add(entry);	
	return entry;	
}	
  

Das	
  ist	
  eine	
  völlig	
  legi/me	
  Art,	
  mit	
  den	
  bisher	
  gelernten	
  Mi_eln	
  das	
  
Problem	
  zu	
  lösen.	
  
Es	
  wird	
  für	
  ein	
  gegebenes	
  Wort	
  ein	
  bisher	
  vorhandener	
  Eintrag	
  
gesucht.	
  Wenn	
  keiner	
  vorhanden	
  ist,	
  wird	
  einer	
  angelegt.	
  
Warum	
  braucht	
  man	
  “equals”,	
  warum	
  reicht	
  nich	
  “==“?	
  
public static void main(String[] args) {	
	String a = new String(new char[]{'h','e','l','l','o'});	
	String b = new String(new char[]{'h','e','l','l','o'});	
	System.out.println(a == b);	
	System.out.println(a.equals(b));	
Diese	
  Schreibweise	
  ini/alisiert	
  
	b = a;	
ein	
  Array	
  mit	
  den	
  
	System.out.println(a == b);	
angegebenen	
  Werten.	
  
}	
	
false	
true	
true	
  

String	
  ist	
  ein	
  Objek_yp.	
  Das	
  heißt,	
  ==	
  vergleicht,	
  ob	
  die	
  Variablen	
  a	
  
und	
  b	
  dasselbe	
  Objekt	
  im	
  Speicher	
  bezeichnen.	
  
Zwei	
  unterschiedliche	
  String-­‐Objekte	
  im	
  Speicher	
  können	
  aber	
  
durchaus	
  für	
  die	
  gleiche	
  Zeichenke_e	
  stehen.	
  Und	
  das	
  ist	
  die	
  Art	
  
Gleichheit,	
  die	
  uns	
  hier	
  interessiert.	
  
public static void main(String[] args) {	
	String c = "Hello";	
	String d = "Hello";	
	System.out.println(c == d);	
}	
	
true	
  

Das	
  ist	
  eine	
  Anomalie:	
  Strings,	
  die	
  ich	
  als	
  Wert	
  in	
  meinen	
  Quellcode	
  
schreibe	
  (sog.	
  Literale,	
  wie	
  5,	
  0.45,	
  ‘a’	
  oder	
  eben	
  “abc”)	
  werden	
  
vom	
  Java-­‐Compiler	
  als	
  iden/sch	
  erkannt	
  und	
  tatsächlich	
  nur	
  einmal	
  
als	
  Objekt	
  angelegt.	
  Dieser	
  Code	
  wird	
  also	
  intern	
  umgeformt	
  zu	
  so	
  
etwas	
  wie:	
  
public static void main(String[] args) {	
	String HELLO = "Hello";
		
	String c = HELLO;	
	String d = HELLO;	
	System.out.println(c == d);	
}	
  

...	
  und	
  damit	
  ist	
  das	
  Verhalten	
  wieder	
  erklärt.	
  Das	
  nur	
  am	
  
Rande.	
  
public static void main(String[] args) throws IOException {	
	new CorpusCount().run();	
}	
	
void run() throws IOException { 	 		
	readFile("files/emma.txt");	
	readFile("files/sense.txt");	
	readFile("files/persuasion.txt");	
	sort();	
	write();	
}	
  

readFile	
  haben	
  wir	
  jetzt	
  also	
  im	
  Griff.	
  Jetzt	
  schauen	
  wir	
  uns	
  sort	
  
an.	
  
List<Entry> entries = new ArrayList<Entry>();	
	
private class EntryComparator implements
	 	 	 	 	 	 	 	 	 	Comparator<Entry> {	
	 		
	 	public int compare(Entry arg0, Entry arg1) {	
	 	 	return arg0.word.compareTo(arg1.word);	
	 	}	
		
}	
	
private void sort() throws IOException {	
	Collections.sort(entries, new EntryComparator());	
}	
  

Collec/ons.sort	
  ist	
  eine	
  sta/sche	
  Methode,	
  die	
  eine	
  Liste	
  sor/ert.	
  
Neben	
  dieser	
  Liste	
  benö/gt	
  sie	
  als	
  Argument	
  einen	
  Vergleicher,	
  
das	
  heißt,	
  ein	
  Objekt,	
  das	
  das	
  Interface	
  Comparator<T>	
  
implemen/ert,	
  wobei	
  T	
  der	
  Typ	
  der	
  Listenelemente	
  ist.	
  Das	
  
Interface	
  Comparator<T>	
  wiederum	
  verlangt	
  eine	
  Methode	
  
compare(T	
  a,	
  T	
  b).	
  
java.util	
	
Interface Comparator<T>	
	
int compare(T o1, T o2)	
	
Compares its two arguments for order. Returns a negative
integer, zero, or a positive integer as the first argument is
less than, equal to, or greater than the second.	
	
[...]	

Mit	
  einem	
  solchen	
  übergebenen	
  Comparator	
  wird	
  bes/mmt,	
  
wonach	
  die	
  Liste	
  sor/ert	
  werden	
  soll.	
  
	
  
In	
  der	
  Dokumenta/on	
  des	
  Interface	
  Comparator	
  wird	
  
bes/mmt,	
  dass	
  Klassen,	
  die	
  es	
  implemen/eren,	
  darauf	
  achten	
  
müssen,	
  dass	
  compare	
  eine	
  totale	
  Ordnung	
  beschreibt.	
  
List<Entry> entries = new ArrayList<Entry>();	
	
private class EntryComparator implements
	 	 	 	 	 	 	 	 	 	Comparator<Entry> {	
	 		
	public int compare(Entry arg0, Entry arg1) {	
	 	return arg0.word.compareTo(arg1.word);	
	}	
		
}	
	
private void sort() throws IOException {	
	Collections.sort(entries, new EntryComparator());	
}	
  

In	
  diesem	
  Beispiel	
  wird	
  zunächst	
  alphabe/sch	
  sor/ert.	
  String	
  hat	
  
prak/scherweise	
  eine	
  compareTo-­‐Methode,	
  an	
  die	
  wir	
  den	
  
Vergleich	
  delegieren	
  können,	
  wenn	
  wir	
  nach	
  dem	
  String-­‐Feld	
  
word	
  sor/eren	
  wollen.	
  
List<Entry> entries = new ArrayList<Entry>();	
	
private class EntryComparator implements
	 	 	 	 	 	 	 	 	 	Comparator<Entry> {	
	
	public int compare(Entry arg0, Entry arg1) {	
	 	if (arg0.count < arg1.count) return -1;	
	 	else if (arg0.count > arg1.count) return 1;	
	 	else return 0;	
	} 		
	
  
}	

Wenn	
  wir	
  nach	
  dem	
  int-­‐Feld	
  count	
  sor/eren	
  wollen,	
  müssen	
  wir	
  
leider	
  die	
  Bedingung	
  ausschreiben.	
  int	
  ist	
  kein	
  Objek_yp	
  und	
  hat	
  
daher	
  keine	
  compareTo-­‐Methode	
  (oder	
  sonst	
  irgendwelche	
  
Methoden).	
  
	
  
Eine	
  Methode	
  Integer.compare(a,	
  b)	
  gibt	
  es	
  leider	
  erst	
  in	
  Java	
  7.	
  
List<Entry> entries = new ArrayList<Entry>();	
	
private class EntryComparator implements
	 	 	 	 	 	 	 	 	 	Comparator<Entry> {	
	
	public int compare(Entry arg0, Entry arg1) {	
	 	if (arg0.count < arg1.count) return 1;	
	 	else if (arg0.count > arg1.count) return -1;	
	 	else return 0;	
	} 		
	
  
}	

Eigentlich	
  wollen	
  wir	
  aber	
  absteigend	
  sor/eren,	
  also	
  müssen	
  wie	
  
das	
  Vorzeichen	
  umdrehen,	
  so	
  dass	
  der	
  Eintrag	
  mit	
  der	
  größeren	
  
Zahl	
  als	
  kleiner	
  gilt.	
  
private void write() throws IOException {	
	FileWriter fw = new FileWriter("files/words.txt");	
	BufferedWriter bw = new BufferedWriter(fw);	
	for (Entry entry : entries) {	
	 	bw.write(entry.word + "t" + entry.count);	
	 	bw.newLine();	
	}	
	bw.close();	
}	
  

Fehlt	
  nur	
  noch	
  die	
  write-­‐Methode.	
  Hier	
  gehen	
  wir	
  die	
  mi_lerweile	
  
sor/erte	
  Liste	
  durch	
  und	
  geben	
  sie	
  aus.	
  	
  
Wie	
  lange	
  dauert	
  das?	
  
void
	
	
	
	
	
	
	
	
	
}	
  

run() throws IOException {	
	long startTime = System.currentTimeMillis(); 	
	readFile("files/emma.txt");	
	readFile("files/sense.txt");	
	readFile("files/persuasion.txt");	
	long endTime = System.currentTimeMillis();	
	sort();	
	write();	
	System.out.println("Total execution time: " +
	 	 	 	 	(endTime - startTime) );
Wie	
  lange	
  dauert	
  das?	
  
void run() throws IOException {	
	 	long startTime = System.currentTimeMillis(); 	
	 	readFile("files/emma.txt");	
	 	readFile("files/sense.txt");	
	 	readFile("files/persuasion.txt");	
	 	long endTime = System.currentTimeMillis();	
	 	sort();	
	 	write();	
	 	System.out.println("Total execution time: " +
	 	 	 	 	 	(endTime - startTime) ); 		
}	
	
Total execution time: 5744	
  

Auf	
  meinem	
  Rechner:	
  5,7	
  Sekunden.	
  	
  
Das	
  ist	
  eigentlich	
  schon	
  ziemlich	
  schnell.	
  Ich	
  bin	
  überrascht.	
  
Anscheinend	
  sind	
  Laptops	
  so	
  schnell,	
  dass	
  dicke	
  Bücher	
  als	
  
Beispiel	
  für	
  “große	
  Datenmengen”	
  nicht	
  taugen.	
  
	
  
Trotzdem:	
  Bekommen	
  wir	
  es	
  schneller?	
  
Überlegen	
  wir	
  uns,	
  was	
  da	
  passiert:	
  
String line;	
while ((line = br.readLine()) != null) {	
	String[] words = line.split(" ");	
	for (String word : words) {	
	 	countWord(word);	
	}	
}	
br.close();	
	
  

Jede	
  Zeile,	
  und	
  jedes	
  Wort	
  wird	
  einzeln	
  verarbeitet.	
  Daran	
  
kommen	
  wir	
  nicht	
  vorbei,	
  wenn	
  wir	
  Wörter	
  zählen	
  wollen.	
  
Aber	
  bekommen	
  wir	
  die	
  Methode	
  countWord()	
  schneller?	
  
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
	nWords++;	
}	
  

CountWord	
  bleibt	
  auch	
  nicht	
  viel	
  anderes	
  übrig,	
  als	
  den	
  
Eintrag	
  herauszusuchen	
  und	
  zu	
  erhöhen.	
  
	
  
Aber	
  bekommen	
  wir	
  findEntry	
  schneller?	
  
private Entry findEntry(String word) {	
	for (Entry entry : entries) {	
	 	if (word.equals(entry.word)) {	
	 	 	return entry;	
	 	}	
	}	
	Entry entry = new Entry();	
	entry.word = word;	
	entries.add(entry);	
	return entry;	
}	
  

Wir	
  suchen	
  einen	
  Eintrag	
  in	
  der	
  Wortliste,	
  indem	
  wir	
  sie	
  von	
  
vorne	
  nach	
  hinten	
  dursuchen.	
  Das	
  ist	
  nicht	
  effizient,	
  
insbesondere	
  dauert	
  diese	
  Opera/on	
  länger,	
  je	
  mehr	
  Wörter	
  
schon	
  im	
  Katalog	
  stehen.	
  
Im	
  schlimmsten	
  Fall,	
  also	
  wenn	
  das	
  Wort	
  noch	
  gar	
  nicht	
  
vorhanden	
  ist,	
  wird	
  die	
  Liste	
  einmal	
  ganz	
  durchgegangen.	
  
Hypothese:	
  Das	
  Programm	
  wird	
  allmählich	
  langsamer,	
  je	
  mehr	
  
Daten	
  wir	
  schon	
  verarbeitet	
  haben,	
  weil	
  die	
  Wortliste	
  sich	
  füllt	
  
und	
  somit	
  jedes	
  Wort	
  mit	
  der	
  Zeit	
  im	
  Schni_	
  länger	
  gesucht	
  
werden	
  muss.	
  
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
	nWords++;	
	if (nWords % 10000 == 0) {	
	 	final long currentTime = System.currentTimeMillis();	
	 	System.out.println("Intermediate execution time: ”
	 	 	 	+ (currentTime - checkpointTime) );	
	 	checkpointTime = currentTime;	
	}	
}	
  
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
	nWords++;	
	if (nWords % 10000 == 0) {	
	 	final long currentTime = System.currentTimeMillis();	
	 	System.out.println("Intermediate execution time: ”
	 	 	 	+ (currentTime - checkpointTime) );	
	 	checkpointTime = currentTime;	
	}	
}	
In	
  der	
  Tat.	
  Zumindest	
  tendenziell	
  und	
  am	
  Anfang.	
  
	
In	
  eine	
  genaue	
  Analyse	
  würde	
  natürlich	
  mehr	
  
Intermediate execution time: 83	
Intermediate execution time: 79	
hineinspielen:	
  Irgendwann	
  wurden	
  alle	
  gängigen	
  
Intermediate execution time: 89	
englischen	
  Wörter	
  gesehen,	
  so	
  dass	
  sich	
  nicht	
  
Intermediate execution time: 97	
Intermediate execution time: 110	
mehr	
  viel	
  ändert,	
  insbesondere	
  wurden	
  die	
  
Intermediate execution time: 122	
gängigsten	
  Wörter	
  schon	
  ziemlich	
  früh	
  gesehen	
  
Intermediate execution time: 124	
Intermediate execution time: 140	
und	
  damit	
  auch	
  schneller	
  gefunden,	
  usw.	
  
Intermediate execution time: 152	
	
  
Intermediate execution time: 152	
Intermediate execution time: 102	
Man	
  begnügt	
  sich	
  aber	
  normalerweise	
  mit	
  einer	
  
Intermediate execution time: 115	
worst-­‐case	
  Analyse:	
  Bei	
  einem	
  künstlichen	
  Text,	
  
Intermediate execution time: 112	
Intermediate execution time: 115	
der	
  nur	
  unterschiedliche	
  Wörter	
  enthält,	
  würde	
  
Intermediate execution time: 123	
dieses	
  Programm	
  pro	
  Wort	
  immer	
  langsamer	
  
Intermediate execution time: 122	
Intermediate execution time: 198	
werden.	
  	
  
...
Die	
  Lösung	
  
Eine	
  Liste,	
  in	
  der	
  linear	
  nach	
  Einträgen	
  gesucht	
  wird,	
  ist	
  keine	
  
adäquate	
  Datenstruktur	
  für	
  dieses	
  Problem.	
  
Was	
  wir	
  brauchen,	
  ist	
  keine	
  Liste,	
  sondern	
  eine	
  Indexstruktur,	
  in	
  
der	
  auf	
  Einträge	
  unter	
  einem	
  besAmmten	
  Suchschlüssel	
  effizient	
  
zugegriffen	
  werden	
  kann.	
  
	
  
Diese	
  Strukturen	
  sind	
  in	
  Java	
  die	
  Maps.	
  
	
  

Map<String, Entry> map = new HashMap<String,Entry>();	
  
Map<String, Entry> entriesMap = new HashMap<String, Entry>();	
	
private Entry findEntry(String word) {	
	Entry entry = entriesMap.get(word);	
	if (entry == null) {	
	 	entry = new Entry();	
	 	entry.word = word;	
	 	entriesMap.put(word, entry);	
	}	
	return entry;	
}	
  

Abgesehen	
  vom	
  Effizienzgewinn	
  wird	
  auch	
  der	
  Code	
  einfacher.	
  
Die	
  for-­‐Schleife	
  ist	
  weg.	
  Mit	
  Map.get	
  wird	
  ein	
  Eintrag	
  (Wert)	
  
unter	
  einem	
  Schlüssel	
  nachgeschlagen.	
  Mit	
  Map.put	
  wird	
  er	
  
hinzugefügt.	
  
	
  
Die	
  beiden	
  Typparameter	
  (genau	
  behandeln	
  wir	
  die	
  später)	
  
geben	
  den	
  Schlüsseltyp	
  und	
  den	
  Wer_yp	
  an.	
  Dies	
  hier	
  ist	
  eine	
  
Map	
  von	
  String	
  nach	
  Entry.	
  
private void sort() {	
	entries = new ArrayList<Entry>(entriesMap.values());	
	Collections.sort(entries, new EntryComparator());	
}	
  

Zum	
  Sor/eren	
  erzeuge	
  ich	
  weiterhin	
  meine	
  Liste	
  (dann	
  muss	
  ich	
  
auch	
  den	
  Code	
  für	
  das	
  Herausschreiben	
  nicht	
  ändern).	
  
	
  
Dieser	
  Konstruktor	
  von	
  ArrayList	
  erzeugt	
  eine	
  Liste	
  basierend	
  auf	
  
einer	
  bestehenden	
  Liste,	
  und	
  Map.values()	
  liefert	
  eine	
  Liste,	
  die	
  
aus	
  allen	
  Werten	
  der	
  Map	
  besteht.	
  
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Total execution time: 238	
  

46	
25	
20	
17	
9	
13	
5	
5	
4	
9	
10	
10	
2	
2	
1	
2	
10	
3	
2	
2	
2	
2	
1	
2	
7	
2	
2	
2	
2	
2	
1	
2	
5	

Und	
  wie	
  es	
  sich	
  lohnt:	
  Es	
  dauert	
  jetzt	
  nur	
  
noch	
  0,2	
  Sekunden	
  sta_	
  5.	
  
	
  
Die	
  Entwicklung	
  über	
  die	
  Laufzeit	
  ist	
  
auch	
  völlig	
  anders:	
  Das	
  Programm	
  wird	
  
eher	
  schneller.	
  Vermutlich,	
  weil	
  die	
  
Laufzeit	
  nun	
  nicht	
  mehr	
  durch	
  das	
  
Suchen	
  dominiert	
  wird,	
  sondern	
  durch	
  
das	
  Hinzufügen	
  neuer	
  Wörter	
  (und	
  
davon	
  kommen	
  später	
  nicht	
  mehr	
  so	
  
viele).	
  
Sta_	
  
Collections.sort(entries, new EntryComparator());	

kann	
  man	
  auch	
  schreiben:	
  
Collections.sort(entries, new Comparator<Entry>() {	
	public int compare(Entry arg0, Entry arg1) {	
	 	if (arg0.count < arg1.count) return 1;	
	 	else if (arg0.count > arg1.count) return -1;	
	 	else return 0;	
	}	
});	
  

Hier	
  wird	
  eine	
  sogenannte	
  anonyme	
  Klasse	
  übergeben	
  (weil	
  
sie	
  den	
  Namen	
  (EntryComparator)	
  hier	
  gar	
  nicht	
  bekommt).	
  
Das	
  ist	
  gut	
  geeignet	
  für	
  Klassen	
  wie	
  diese,	
  von	
  denen	
  man	
  
nur	
  ein	
  einziges	
  Objekt	
  erzeugt,	
  um	
  es	
  an	
  eine	
  Methode	
  zu	
  
übergeben,	
  und	
  die	
  man	
  danach	
  nie	
  wieder	
  benutzt.	
  
Bleibt	
  zu	
  erklären:	
  Wie	
  macht	
  die	
  HashMap	
  das?	
  

Wikipedia:	
  
Hash	
  Table	
  

Die	
  Einträge	
  werden	
  zu	
  sogenannten	
  Hashwerten	
  verschlüsselt.	
  Die	
  
HashMap	
  ist	
  im	
  Prinzip	
  ein	
  Array	
  aus	
  Listen,	
  und	
  in	
  der	
  Liste	
  an	
  einer	
  
Arrayposi/on	
  n	
  sind	
  alle	
  Werte,	
  deren	
  Hashwert	
  n	
  ist.	
  
Die	
  genauen	
  Eigenschauen	
  dieser	
  Struktur	
  hängen	
  von	
  der	
  Wahl	
  der	
  
Hashfunk/on	
  ab.	
  Die	
  Analyse	
  sprengt	
  hier	
  den	
  Rahmen,	
  aber	
  diese	
  Struktur	
  
ist	
  immer	
  dann	
  Mi_el	
  der	
  Wahl,	
  wenn	
  Dinge	
  nachgeschlagen	
  werden	
  sollen.	
  

Mais conteúdo relacionado

Semelhante a Vorlesung 7: Laufzeit, Maps

Battle of the Languages: Java und Python im Wettstreit beim Lösen von Program...
Battle of the Languages: Java und Python im Wettstreit beim Lösen von Program...Battle of the Languages: Java und Python im Wettstreit beim Lösen von Program...
Battle of the Languages: Java und Python im Wettstreit beim Lösen von Program...gedoplan
 
Scalaz introduction for Java programmers
Scalaz introduction for Java programmersScalaz introduction for Java programmers
Scalaz introduction for Java programmersBernhard Huemer
 
Mehr Dynamik Durch Skriptsprachen
Mehr Dynamik Durch SkriptsprachenMehr Dynamik Durch Skriptsprachen
Mehr Dynamik Durch Skriptsprachenjlink
 
Einführung in die funktionale Programmierung
Einführung in die funktionale ProgrammierungEinführung in die funktionale Programmierung
Einführung in die funktionale ProgrammierungDigicomp Academy AG
 
C Sharp Einfuehrung Teil 2
C Sharp Einfuehrung Teil 2C Sharp Einfuehrung Teil 2
C Sharp Einfuehrung Teil 2DraphonyGames
 
Mehr Dynamik Mit Groovy
Mehr Dynamik Mit GroovyMehr Dynamik Mit Groovy
Mehr Dynamik Mit Groovyjlink
 
T2 s4 javascriptfuerfortgeschrittene
T2 s4 javascriptfuerfortgeschritteneT2 s4 javascriptfuerfortgeschrittene
T2 s4 javascriptfuerfortgeschrittenedominion
 
Parallele Softwareentwicklung mit .NET 4.0
Parallele Softwareentwicklung mit .NET 4.0Parallele Softwareentwicklung mit .NET 4.0
Parallele Softwareentwicklung mit .NET 4.0Qiong Wu
 

Semelhante a Vorlesung 7: Laufzeit, Maps (14)

Scala Workshop
Scala WorkshopScala Workshop
Scala Workshop
 
Battle of the Languages: Java und Python im Wettstreit beim Lösen von Program...
Battle of the Languages: Java und Python im Wettstreit beim Lösen von Program...Battle of the Languages: Java und Python im Wettstreit beim Lösen von Program...
Battle of the Languages: Java und Python im Wettstreit beim Lösen von Program...
 
Scalaz introduction for Java programmers
Scalaz introduction for Java programmersScalaz introduction for Java programmers
Scalaz introduction for Java programmers
 
Mehr Dynamik Durch Skriptsprachen
Mehr Dynamik Durch SkriptsprachenMehr Dynamik Durch Skriptsprachen
Mehr Dynamik Durch Skriptsprachen
 
SQL
SQLSQL
SQL
 
Pyparsing
PyparsingPyparsing
Pyparsing
 
Einführung in die funktionale Programmierung
Einführung in die funktionale ProgrammierungEinführung in die funktionale Programmierung
Einführung in die funktionale Programmierung
 
C Sharp Einfuehrung Teil 2
C Sharp Einfuehrung Teil 2C Sharp Einfuehrung Teil 2
C Sharp Einfuehrung Teil 2
 
3.2 stacks und arrays
3.2   stacks und arrays3.2   stacks und arrays
3.2 stacks und arrays
 
Mehr Dynamik Mit Groovy
Mehr Dynamik Mit GroovyMehr Dynamik Mit Groovy
Mehr Dynamik Mit Groovy
 
JavaSPEKTRUM - Scala 3
JavaSPEKTRUM - Scala 3JavaSPEKTRUM - Scala 3
JavaSPEKTRUM - Scala 3
 
T2 s4 javascriptfuerfortgeschrittene
T2 s4 javascriptfuerfortgeschritteneT2 s4 javascriptfuerfortgeschrittene
T2 s4 javascriptfuerfortgeschrittene
 
Slides
SlidesSlides
Slides
 
Parallele Softwareentwicklung mit .NET 4.0
Parallele Softwareentwicklung mit .NET 4.0Parallele Softwareentwicklung mit .NET 4.0
Parallele Softwareentwicklung mit .NET 4.0
 

Vorlesung 7: Laufzeit, Maps

  • 1. Angewandte  Informa/k  für   Ingenieure/-­‐innen   Michael  Zilske  
  • 2. Angenommen,  ich  möchte     •  eine  Textdatei  einlesen  (zum  Beispiel  einen  Roman),     •  die  Häufigkeiten  aller  unterschiedlichen  vorkommenden   Wörter  zählen,   •  diese  Wörter  mit  ihren  Häufigkeiten  als  Rangliste  ausgeben.   the to of and a was in I her not be 12231 11979 10565 10285 6627 5341 5334 5206 5015 4115 4078
  • 3. public static void main(String[] args) throws IOException { new CorpusCount().run(); } void run() throws IOException { readFile("files/emma.txt"); readFile("files/sense.txt"); readFile("files/persuasion.txt"); sort(); write(); }   Ich  habe  mir  also  drei  Jane-­‐Austen-­‐Romane  aus  dem  Internet   besorgt.  Weil  ich  die  nacheinander  bearbeiten  will,  schreibe  ich   mir  eine  separate  Methode  readFile().  Danach  will  ich  die   gesammelten  Daten  sor/eren  und  dann  herausschreiben.     Da  diese  Methoden  Dateiopera/onen  ausführen,  deklarieren  sie   IOExcep/ons.  Ich  deklariere  sie  weiter.  
  • 4. void readFile(String filename) throws IOException { FileReader fr = new FileReader(filename); BufferedReader br = new BufferedReader(fr); String line; while ((line = br.readLine()) != null) { String[] words = line.split(" "); for (String word : words) { countWord(word); } } br.close(); }   Diese  Methode  haben  wir  im  Prinzip  letzte  Woche  schon  gesehen.   Man  muss  Reader  und  Writer  übrigens,  wenn  man  sie  nicht  mehr   braucht,  mit  close()  schließen,  denn  Dateizugriffe  sind  vom   Betriebssystem  verwaltete,  beschränkte  Ressourcen.  
  • 5. private class Entry { String word; int count; } private void countWord(String word) { Entry entry = findEntry(word); entry.count++; }   Was  ich  berechnen  möchte,  ist  eine  Tabelle,  deren  Einträge   Wörter  mit  ihren  (bisherigen)  Häufigkeiten  sind.  Für  jedes  Wort,   das  ich  einlese,  suche  ich  den  entsprechenden  Eintrag  heraus  und   erhöhe  den  Zähler.     Für  die  Tabellenzeile  eignet  sich  eine  eigene  Klasse  Entry.  Ich  kann   eine  Klasse  innerhalb  einer  anderen  Klasse  definieren  –  wenn  ich   sie  private  deklariere,  ist  sie  nur  innerhalb  der  umschließenden   Klasse  sichtbar.  Prima  für  einmalige  Dinge  wie  Tabellenzeilen!  
  • 6. private class Entry { String word; int count; } private void countWord(String word) { Entry entry = findEntry(word); entry.count++; }   Vorteil  einer  solchen  Konstruk/on  (private  Klasse):  Ich  brauche   nicht  darüber  nachzudenken,  wie  sie  verwendet  werden  könnte.   Im  Uhrenladenbeispiel  habe  ich  gesagt,  es  ist  gut,  Felder  immer   als  private  zu  deklarieren,  um  nur  kontrollierten  Zugriff  darauf  zu   erlauben  (wie  bei  der  Uhr,  die  man  nur  zerstören,  aber  nicht   reparieren  kann).   Bei  einer  privaten  Klasse  ist  das  unnö/g,  denn  alle  sta]indenden   Zugriffe  stehen  in  derselben  Datei,  also  gibt  es  nichts  zu   kontrollieren.  Ich  kann  munter  direkt  die  Felder  verwenden.  
  • 7. private class Entry { String word; int count; } private void countWord(String word) { Entry entry = findEntry(word); entry.count++; }   Wie  finde  ich  nun  meinen  Eintrag,  bzw.  wie  verwalte  ich  meine   Einträge  überhaupt?  
  • 8. private class Entry { String word; int count; } List<Entry> entries = new ArrayList<Entry>(); private void countWord(String word) { Entry entry = findEntry(word); entry.count++; } private Entry findEntry(String word) { for (Entry entry : entries) { if (word.equals(entry.word)) { return entry; } } Entry entry = new Entry(); entry.word = word; entries.add(entry); return entry; }  
  • 9. List<Entry> entries = new ArrayList<Entry>(); private Entry findEntry(String word) { for (Entry entry : entries) { if (word.equals(entry.word)) { return entry; } } Entry entry = new Entry(); entry.word = word; entries.add(entry); return entry; }   Das  ist  eine  völlig  legi/me  Art,  mit  den  bisher  gelernten  Mi_eln  das   Problem  zu  lösen.   Es  wird  für  ein  gegebenes  Wort  ein  bisher  vorhandener  Eintrag   gesucht.  Wenn  keiner  vorhanden  ist,  wird  einer  angelegt.   Warum  braucht  man  “equals”,  warum  reicht  nich  “==“?  
  • 10. public static void main(String[] args) { String a = new String(new char[]{'h','e','l','l','o'}); String b = new String(new char[]{'h','e','l','l','o'}); System.out.println(a == b); System.out.println(a.equals(b)); Diese  Schreibweise  ini/alisiert   b = a; ein  Array  mit  den   System.out.println(a == b); angegebenen  Werten.   } false true true   String  ist  ein  Objek_yp.  Das  heißt,  ==  vergleicht,  ob  die  Variablen  a   und  b  dasselbe  Objekt  im  Speicher  bezeichnen.   Zwei  unterschiedliche  String-­‐Objekte  im  Speicher  können  aber   durchaus  für  die  gleiche  Zeichenke_e  stehen.  Und  das  ist  die  Art   Gleichheit,  die  uns  hier  interessiert.  
  • 11. public static void main(String[] args) { String c = "Hello"; String d = "Hello"; System.out.println(c == d); } true   Das  ist  eine  Anomalie:  Strings,  die  ich  als  Wert  in  meinen  Quellcode   schreibe  (sog.  Literale,  wie  5,  0.45,  ‘a’  oder  eben  “abc”)  werden   vom  Java-­‐Compiler  als  iden/sch  erkannt  und  tatsächlich  nur  einmal   als  Objekt  angelegt.  Dieser  Code  wird  also  intern  umgeformt  zu  so   etwas  wie:   public static void main(String[] args) { String HELLO = "Hello"; String c = HELLO; String d = HELLO; System.out.println(c == d); }   ...  und  damit  ist  das  Verhalten  wieder  erklärt.  Das  nur  am   Rande.  
  • 12. public static void main(String[] args) throws IOException { new CorpusCount().run(); } void run() throws IOException { readFile("files/emma.txt"); readFile("files/sense.txt"); readFile("files/persuasion.txt"); sort(); write(); }   readFile  haben  wir  jetzt  also  im  Griff.  Jetzt  schauen  wir  uns  sort   an.  
  • 13. List<Entry> entries = new ArrayList<Entry>(); private class EntryComparator implements Comparator<Entry> { public int compare(Entry arg0, Entry arg1) { return arg0.word.compareTo(arg1.word); } } private void sort() throws IOException { Collections.sort(entries, new EntryComparator()); }   Collec/ons.sort  ist  eine  sta/sche  Methode,  die  eine  Liste  sor/ert.   Neben  dieser  Liste  benö/gt  sie  als  Argument  einen  Vergleicher,   das  heißt,  ein  Objekt,  das  das  Interface  Comparator<T>   implemen/ert,  wobei  T  der  Typ  der  Listenelemente  ist.  Das   Interface  Comparator<T>  wiederum  verlangt  eine  Methode   compare(T  a,  T  b).  
  • 14. java.util Interface Comparator<T> int compare(T o1, T o2) Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second. [...] Mit  einem  solchen  übergebenen  Comparator  wird  bes/mmt,   wonach  die  Liste  sor/ert  werden  soll.     In  der  Dokumenta/on  des  Interface  Comparator  wird   bes/mmt,  dass  Klassen,  die  es  implemen/eren,  darauf  achten   müssen,  dass  compare  eine  totale  Ordnung  beschreibt.  
  • 15. List<Entry> entries = new ArrayList<Entry>(); private class EntryComparator implements Comparator<Entry> { public int compare(Entry arg0, Entry arg1) { return arg0.word.compareTo(arg1.word); } } private void sort() throws IOException { Collections.sort(entries, new EntryComparator()); }   In  diesem  Beispiel  wird  zunächst  alphabe/sch  sor/ert.  String  hat   prak/scherweise  eine  compareTo-­‐Methode,  an  die  wir  den   Vergleich  delegieren  können,  wenn  wir  nach  dem  String-­‐Feld   word  sor/eren  wollen.  
  • 16. List<Entry> entries = new ArrayList<Entry>(); private class EntryComparator implements Comparator<Entry> { public int compare(Entry arg0, Entry arg1) { if (arg0.count < arg1.count) return -1; else if (arg0.count > arg1.count) return 1; else return 0; }   } Wenn  wir  nach  dem  int-­‐Feld  count  sor/eren  wollen,  müssen  wir   leider  die  Bedingung  ausschreiben.  int  ist  kein  Objek_yp  und  hat   daher  keine  compareTo-­‐Methode  (oder  sonst  irgendwelche   Methoden).     Eine  Methode  Integer.compare(a,  b)  gibt  es  leider  erst  in  Java  7.  
  • 17. List<Entry> entries = new ArrayList<Entry>(); private class EntryComparator implements Comparator<Entry> { public int compare(Entry arg0, Entry arg1) { if (arg0.count < arg1.count) return 1; else if (arg0.count > arg1.count) return -1; else return 0; }   } Eigentlich  wollen  wir  aber  absteigend  sor/eren,  also  müssen  wie   das  Vorzeichen  umdrehen,  so  dass  der  Eintrag  mit  der  größeren   Zahl  als  kleiner  gilt.  
  • 18. private void write() throws IOException { FileWriter fw = new FileWriter("files/words.txt"); BufferedWriter bw = new BufferedWriter(fw); for (Entry entry : entries) { bw.write(entry.word + "t" + entry.count); bw.newLine(); } bw.close(); }   Fehlt  nur  noch  die  write-­‐Methode.  Hier  gehen  wir  die  mi_lerweile   sor/erte  Liste  durch  und  geben  sie  aus.    
  • 19. Wie  lange  dauert  das?   void }   run() throws IOException { long startTime = System.currentTimeMillis(); readFile("files/emma.txt"); readFile("files/sense.txt"); readFile("files/persuasion.txt"); long endTime = System.currentTimeMillis(); sort(); write(); System.out.println("Total execution time: " + (endTime - startTime) );
  • 20. Wie  lange  dauert  das?   void run() throws IOException { long startTime = System.currentTimeMillis(); readFile("files/emma.txt"); readFile("files/sense.txt"); readFile("files/persuasion.txt"); long endTime = System.currentTimeMillis(); sort(); write(); System.out.println("Total execution time: " + (endTime - startTime) ); } Total execution time: 5744   Auf  meinem  Rechner:  5,7  Sekunden.    
  • 21. Das  ist  eigentlich  schon  ziemlich  schnell.  Ich  bin  überrascht.   Anscheinend  sind  Laptops  so  schnell,  dass  dicke  Bücher  als   Beispiel  für  “große  Datenmengen”  nicht  taugen.     Trotzdem:  Bekommen  wir  es  schneller?   Überlegen  wir  uns,  was  da  passiert:   String line; while ((line = br.readLine()) != null) { String[] words = line.split(" "); for (String word : words) { countWord(word); } } br.close();   Jede  Zeile,  und  jedes  Wort  wird  einzeln  verarbeitet.  Daran   kommen  wir  nicht  vorbei,  wenn  wir  Wörter  zählen  wollen.   Aber  bekommen  wir  die  Methode  countWord()  schneller?  
  • 22. private void countWord(String word) { Entry entry = findEntry(word); entry.count++; nWords++; }   CountWord  bleibt  auch  nicht  viel  anderes  übrig,  als  den   Eintrag  herauszusuchen  und  zu  erhöhen.     Aber  bekommen  wir  findEntry  schneller?  
  • 23. private Entry findEntry(String word) { for (Entry entry : entries) { if (word.equals(entry.word)) { return entry; } } Entry entry = new Entry(); entry.word = word; entries.add(entry); return entry; }   Wir  suchen  einen  Eintrag  in  der  Wortliste,  indem  wir  sie  von   vorne  nach  hinten  dursuchen.  Das  ist  nicht  effizient,   insbesondere  dauert  diese  Opera/on  länger,  je  mehr  Wörter   schon  im  Katalog  stehen.   Im  schlimmsten  Fall,  also  wenn  das  Wort  noch  gar  nicht   vorhanden  ist,  wird  die  Liste  einmal  ganz  durchgegangen.  
  • 24. Hypothese:  Das  Programm  wird  allmählich  langsamer,  je  mehr   Daten  wir  schon  verarbeitet  haben,  weil  die  Wortliste  sich  füllt   und  somit  jedes  Wort  mit  der  Zeit  im  Schni_  länger  gesucht   werden  muss.   private void countWord(String word) { Entry entry = findEntry(word); entry.count++; nWords++; if (nWords % 10000 == 0) { final long currentTime = System.currentTimeMillis(); System.out.println("Intermediate execution time: ” + (currentTime - checkpointTime) ); checkpointTime = currentTime; } }  
  • 25. private void countWord(String word) { Entry entry = findEntry(word); entry.count++; nWords++; if (nWords % 10000 == 0) { final long currentTime = System.currentTimeMillis(); System.out.println("Intermediate execution time: ” + (currentTime - checkpointTime) ); checkpointTime = currentTime; } } In  der  Tat.  Zumindest  tendenziell  und  am  Anfang.   In  eine  genaue  Analyse  würde  natürlich  mehr   Intermediate execution time: 83 Intermediate execution time: 79 hineinspielen:  Irgendwann  wurden  alle  gängigen   Intermediate execution time: 89 englischen  Wörter  gesehen,  so  dass  sich  nicht   Intermediate execution time: 97 Intermediate execution time: 110 mehr  viel  ändert,  insbesondere  wurden  die   Intermediate execution time: 122 gängigsten  Wörter  schon  ziemlich  früh  gesehen   Intermediate execution time: 124 Intermediate execution time: 140 und  damit  auch  schneller  gefunden,  usw.   Intermediate execution time: 152   Intermediate execution time: 152 Intermediate execution time: 102 Man  begnügt  sich  aber  normalerweise  mit  einer   Intermediate execution time: 115 worst-­‐case  Analyse:  Bei  einem  künstlichen  Text,   Intermediate execution time: 112 Intermediate execution time: 115 der  nur  unterschiedliche  Wörter  enthält,  würde   Intermediate execution time: 123 dieses  Programm  pro  Wort  immer  langsamer   Intermediate execution time: 122 Intermediate execution time: 198 werden.     ...
  • 26. Die  Lösung   Eine  Liste,  in  der  linear  nach  Einträgen  gesucht  wird,  ist  keine   adäquate  Datenstruktur  für  dieses  Problem.   Was  wir  brauchen,  ist  keine  Liste,  sondern  eine  Indexstruktur,  in   der  auf  Einträge  unter  einem  besAmmten  Suchschlüssel  effizient   zugegriffen  werden  kann.     Diese  Strukturen  sind  in  Java  die  Maps.     Map<String, Entry> map = new HashMap<String,Entry>();  
  • 27. Map<String, Entry> entriesMap = new HashMap<String, Entry>(); private Entry findEntry(String word) { Entry entry = entriesMap.get(word); if (entry == null) { entry = new Entry(); entry.word = word; entriesMap.put(word, entry); } return entry; }   Abgesehen  vom  Effizienzgewinn  wird  auch  der  Code  einfacher.   Die  for-­‐Schleife  ist  weg.  Mit  Map.get  wird  ein  Eintrag  (Wert)   unter  einem  Schlüssel  nachgeschlagen.  Mit  Map.put  wird  er   hinzugefügt.     Die  beiden  Typparameter  (genau  behandeln  wir  die  später)   geben  den  Schlüsseltyp  und  den  Wer_yp  an.  Dies  hier  ist  eine   Map  von  String  nach  Entry.  
  • 28. private void sort() { entries = new ArrayList<Entry>(entriesMap.values()); Collections.sort(entries, new EntryComparator()); }   Zum  Sor/eren  erzeuge  ich  weiterhin  meine  Liste  (dann  muss  ich   auch  den  Code  für  das  Herausschreiben  nicht  ändern).     Dieser  Konstruktor  von  ArrayList  erzeugt  eine  Liste  basierend  auf   einer  bestehenden  Liste,  und  Map.values()  liefert  eine  Liste,  die   aus  allen  Werten  der  Map  besteht.  
  • 29. Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Total execution time: 238   46 25 20 17 9 13 5 5 4 9 10 10 2 2 1 2 10 3 2 2 2 2 1 2 7 2 2 2 2 2 1 2 5 Und  wie  es  sich  lohnt:  Es  dauert  jetzt  nur   noch  0,2  Sekunden  sta_  5.     Die  Entwicklung  über  die  Laufzeit  ist   auch  völlig  anders:  Das  Programm  wird   eher  schneller.  Vermutlich,  weil  die   Laufzeit  nun  nicht  mehr  durch  das   Suchen  dominiert  wird,  sondern  durch   das  Hinzufügen  neuer  Wörter  (und   davon  kommen  später  nicht  mehr  so   viele).  
  • 30. Sta_   Collections.sort(entries, new EntryComparator()); kann  man  auch  schreiben:   Collections.sort(entries, new Comparator<Entry>() { public int compare(Entry arg0, Entry arg1) { if (arg0.count < arg1.count) return 1; else if (arg0.count > arg1.count) return -1; else return 0; } });   Hier  wird  eine  sogenannte  anonyme  Klasse  übergeben  (weil   sie  den  Namen  (EntryComparator)  hier  gar  nicht  bekommt).   Das  ist  gut  geeignet  für  Klassen  wie  diese,  von  denen  man   nur  ein  einziges  Objekt  erzeugt,  um  es  an  eine  Methode  zu   übergeben,  und  die  man  danach  nie  wieder  benutzt.  
  • 31. Bleibt  zu  erklären:  Wie  macht  die  HashMap  das?   Wikipedia:   Hash  Table   Die  Einträge  werden  zu  sogenannten  Hashwerten  verschlüsselt.  Die   HashMap  ist  im  Prinzip  ein  Array  aus  Listen,  und  in  der  Liste  an  einer   Arrayposi/on  n  sind  alle  Werte,  deren  Hashwert  n  ist.   Die  genauen  Eigenschauen  dieser  Struktur  hängen  von  der  Wahl  der   Hashfunk/on  ab.  Die  Analyse  sprengt  hier  den  Rahmen,  aber  diese  Struktur   ist  immer  dann  Mi_el  der  Wahl,  wenn  Dinge  nachgeschlagen  werden  sollen.