Groovy: Dateien und Datenströme

Ein weiterer großer Block von vordefinierten Methoden hat mit der Eingabe und Ausgabe von Daten bzw. mit der Navigation im Dateisystem zu tun. Sie sind für Dateien sowie für die Eingabe- und Ausgabe-Ströme, Reader und Writer definiert und sorgen dafür, dass manch Ein- oder Ausgabeoperation mit wenig Code erledigt werden kann. Außerdem nehmen Sie Ihnen in vielen Fällen die zwangsweise Behandlung von Exceptions ab, die in der Praxis immer wieder vergessen wird.

Navigieren im Dateisystem Bearbeiten

Die Klasse java.io.File repräsentiert in Java einen Datei- oder Verzeichnisnamen und bietet darüber hinaus einige Methoden zum Konvertieren dieser Namen sowie zur Navigation in Dateisystemen. Die Groovy-Laufzeitbibliothek definiert eine Reihe zusätzlicher Methoden für diese Klasse. Sie erlauben einerseits den direkten Zugriff auf die Daten; darauf kommen wir etwas weiter unten zu sprechen. Daneben gibt es einige Methoden, die das Operieren im Dateisystem auf Groovy-typische Weise mit Closures erlauben.

Angenommen, das Wurzelverzeichnis Ihrer Groovy-Installation liegt ebenfalls unter C:\java\groovy_1.0, dann können Sie mit der File-Methode eachFile() wie im folgenden Beispiel dessen Inhalt in einer Schleife durchlaufen:

groovy> f = new File('C:/java/groovy-1.0')
groovy> f.eachFile { println it }
C:\java\groovy-1.0\bin
C:\java\groovy-1.0\conf
C:\java\groovy-1.0\docs
C:\java\groovy-1.0\embeddable
C:\java\groovy-1.0\groovy-1.0.jar
C:\java\groovy-1.0\lib
C:\java\groovy-1.0\LICENSE.txt

In Unix-Dateisystemen bleiben dabei die dort üblichen Verzeichniseinträge für das aktuelle und das übergeordnete Verzeichnis (».« und »..«) unbeachtet.

Die Methode eachDir() tut im Prinzip dasselbe, beachtet auf Ihrem Wege allerdings nur Unterverzeichnisse:

groovy> f.eachDir { println it }
C:\java\groovy-1.0\bin
C:\java\groovy-1.0\conf
C:\java\groovy-1.0\docs
C:\java\groovy-1.0\embeddable
C:\java\groovy-1.0\lib

Eine Methode, mit der nur echte Dateien (also alles außer Verzeichnissen) gelistet werden, fehlt leider. Weiter unten werden Sie allerdings sehen, dass sich dies mit sehr wenig Mühe ausgleichen lässt.

Eine weitere Variante der Schleife gibt Ihnen die Möglichkeit, einen Vergleichsausdruck für den Dateinamen anzugeben:

groovy> f.eachFileMatch (~"...") { println it }
C:\java\groovy-1.0\bin
C:\java\groovy-1.0\lib

Das erste Argument der vordefinierten Methode eachFileMatch() akzeptiert einen Ausdruck, dessen Typ die Methode isCase() implementiert. Diese Methode ist eigentlich für case-Verzweigungen vorgesehen und liefert normalerweise dasselbe Ergebnis wie equals(). Im obigen Beispiel verwenden wir als erstes Argument einen regulären Ausdruck, bei dem die isCase()-Methode einen Mustervergleich durchführt. (Sie erinnern sich: Der Operator ~ vor einem String bewirkt, dass der String in ein Pattern-Objekt umgewandelt wird. ~"..." ist also ein Muster für einen aus drei beliebigen Zeichen bestehenden String.)

Wir hätten stattdessen auch eine Closure verwenden können, bei der isCase() die Closure selbst, die ein boolesches Ergebnis liefern muss, ausführt:

groovy> f.eachFileMatch { it.length()==3 } { println it }
C:\java\groovy-1.0\bin
C:\java\groovy-1.0\lib

Die letzte Methode dieser Gruppe, eachFileRecurse(), durchläuft, wie der Name schon ahnen lässt, den gesamten Verzeichnisbaum unterhalb des angegebenen Wurzelverzeichnisses rekursiv. Das folgende kleine Skript ermittelt die Anzahl der zur Groovy-Installation gehörenden Dateien ‒ ohne Berücksichtigung der Verzeichnisse:

groovy> anzahl = 0
groovy> f.eachFileRecurse { if (it.file) anzahl++ }
groovy> println anzahl
854

Textdaten Bearbeiten

Java unterscheidet bei seinen Ein- und Ausgabeoperationen sorgfältig zwischen Text- und Binärdaten. Während letztere einfach nur uninterpretierte Rohdaten in Form von Bytes darstellen, bestehen Textdaten aus Unicode-Buchstaben, die als char bzw. Character oder String-Objekte gehalten werden. Da bei der Ein- und Ausgabe letztendlich immer nur Rohdaten transportiert werden, ist eine Kodierung bzw. Dekodierung der Textdaten erforderlich. Wenn man bei plattformübergreifender Programmierung in Java nicht darauf achtet, dass dabei immer die gleichen Zeichenkodierungen verwendet werden, findet schnell anstatt der erwarteten Umlaute und Sonderzeichen umgekehrte Fragezeichen und merkwürdige Sequenzen vor. Das Angenehme an Groovy ist, dass es uns weitgehend von den Kodierungsproblemen befreit und gleichzeitig noch viele Lese- und Schreiboperationen mit wenigen Programmzeilen erledigen lässt.

Auf komplette Textdateien zugreifen Bearbeiten

Sehr einfach ist es, wenn Sie eine Textdatei in einem Stück aus einem String heraus schreiben wollen.

groovy> text = 'Erste Zeile.\nZweite Zeile.\nDritteZeile.\n'
groovy> new File('Beispiel.txt').write(text)

Das Öffnen der Datei, das Schreiben, Schließen und die Kontrolle, dass Fehler richtig behandelt werden, also alles, was die Arbeit mit Dateien immer etwas mühsam macht, übernimmt für Sie die für die Klasse File vordefinierte Methode write().

Natürlich lassen sich die Daten auch wieder in einem Rutsch einlesen. Dies geht mit der Methode getText(), die wir selbstverständlich auch als Property ansprechen können.

groovy> println (new File('Beispiel.txt').text)
Erste Zeile.
Zweite Zeile.
DritteZeile.

Alternativ besteht auch die Möglichkeit, die Datei mittels der Methode readLines() nicht in einen einzigen String, sondern in eine Liste von Strings einzulesen, wobei jedes Listenelement eine Zeile enthält.

groovy> liste = new File('Beispiel.txt').readLines()
groovy> println liste
["Erste Zeile.", "Zweite Zeile.", "DritteZeile."]

Zeichensätze berücksichtigen Bearbeiten

Oben haben wir weder beim Lesen noch beim Schreiben den zu verwendenden Zeichensatz angegeben. Wie unter Java üblich, wird dann der Systemzeichensatz verwendet; auf westeuropäischen Arbeitsplatzrechnern ist dies normalerweise Latin-1 (unter Windows als Cp1252 und unter anderen Betriebssystemen als ISO-8859-1 bezeichnet) oder UTF-8. Welchen Zeichensatz Sie haben, bekommen Sie leicht mit folgender Groovy-Zeile heraus:

groovy> System.properties.'file.encoding'
===> Cp1252

Sobald Sie Dateien austauschen wollen oder Programme schreiben, die auf einem Server laufen, müssen Sie auf das richtige Encoding achten, damit Umlaute und Sonderzeichen korrekt behandelt werden. Bei (fast) allen Methoden, die Strings in Bytes und umgekehrt umwandeln, kann daher das Encoding angegeben werden. Wir probieren dies aus, indem wir einen Satz mit vielen Umlauten verwenden und die Unicode-Kodierung UTF-8 anwenden.

groovy> text = 'Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich.'
groovy> new File('Beispiel.txt').write(text,'UTF8')
groovy> println (new File('Beispiel.txt').getText('UTF8'))
Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich.

Merkwürdigerweise funktioniert dies mit Groovy beim Lesen auch ohne Angabe des Zeichensatzes:

groovy> println (new File('Beispiel.txt').text)
Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich.

Mit einem normalen Java-Programm würde dies nicht ohne Weiteres funktionieren ‒ statt der Umlaute und des ß-Zeichens würden Sie merkwürdige Zeichenkombinationen vorfinden. Der Grund für diesen Unterschied besteht darin, dass Groovy beim Lesen einer Datei von sich aus versucht, das Encoding herauszubekommen, wenn Sie es nicht explizit angeben. Dazu bedient es sich einer Klasse in der Standardbibliothek mit dem Namen groovy.util.CharsetToolkit, die mit Hilfe der ersten vier Kilobyte einer Textdatei deren Zeichensatz ermittelt. Probieren wir dies einmal selbst aus:

groovy> ctk = new CharsetToolkit(new File('Beispiel.txt'))
groovy> println ctk.charset
UTF-8

Treffer. Das kann natürlich theoretisch schiefgehen, wenn in den ersten viertausend Zeichen einer Datei keine Umlaute und Sonderzeichen vorkommen, an denen sich der Zeichensatz ermitteln lässt. Die Erfahrung zeigt allerdings, dass dies in der Praxis extrem unwahrscheinlich ist.[1]

Versuchen wir es noch einmal mit der Textdatei aus dem ersten Versuch:

groovy> text = 'Erste Zeile.\nZweite Zeile.\nDritteZeile.\n'
groovy> new File('Beispiel2.txt').write(text,'UTF-8')
groovy> println (new CharsetToolkit(new File('Beispiel2.txt')).charset)
windows-1252

Obwohl wir die Datei mit UTF-8 geschrieben haben, meint das CharsetToolkit, es sei der Zeichensatz »windows-1252« gewesen ‒ ein Synonym für Cp1252. Dies liegt aber daran, dass dieser Text weder Umlaute noch Sonderzeichen enthält und es daher völlig egal ist, welcher Zeichensatz verwendet wird; und dann entscheidet sich das CharsetToolkit für den aktuellen Standard-Zeichensatz.

Das CharsetToolkit verfügt übrigens noch ein paar weitere praktische Methoden in Bezug auf Zeichensätze; in Anhang - Wichtige Klassen und Interfaces ist die Klasse ausführlicher erläutert.

Texte aus anderen Quellen lesen Bearbeiten

Die Methode getText() ist nicht nur für Dateien, sondern auch für verschiedene andere Datenquellen wie Streams, Reader, Prozesse und auch URLs definiert. Letzteres können wir bequem dafür verwenden, Dateien über das Netzwerk zu laden. Das folgende kurze Beispiel liest eine Webseite und speichert sie als Datei ab.

groovy> seite = new URL('http://de.wikibooks.org/wiki/Hauptseite').text
groovy> new File('wikibuch-home.html').write(seite)

Öffnen Sie anschließend die Datei mit einem Browser und Sie finden tatsächlich die WikiBooks-Startseite vor ‒ möglicherweise etwas abgemagert, wenn das richtige Stylesheet und die Bilddateien fehlen, aber deutlich erkennbar.

Natürlich funktioniert dies nur, wenn Sie am Internet angeschlossen sind. Falls Sie sich über einen Netzwerk-Proxy verbinden müssen, dürfen Sie nicht vergessen, zuvor die entsprechenden System-Properties zu setzen.

groovy> System.properties.proxySet = 'true'
groovy> System.properties.proxyHost = 'proxy.somedomain.org'
groovy> System.properties.proxyPort = '3128'

Eine weitere mögliche Quelle für Eingabedaten sind Prozesse. Im folgenden Beispiel führt die vordefinierte String-Methode execute() den Inhalt des Strings als Systembefehl aus; der Rückgabewert ist eine Instanz des Typs java.lang.Process. Und dafür gibt es wiederum eine vordefinierte Methode getText(), mit der die gesamte Standardausgabe dieses Prozesses ausgelesen werden kann. Das folgende Beispiel liest die aktuelle Netzwerkkonfiguration unter Windows.

groovy> ipconfig = "ipconfig".execute().text
groovy> println ipconfig
Windows-IP-Konfiguration
Drahtlos-LAN-Adapter Drahtlosnetzwerkverbindung:
Verbindungsspezifisches DNS-Suffix: 
Verbindungslokale IPv6-Adresse . : fe80::1d2c:2980:5be5:f725%10
IPv4-Adresse . . . . . . . . . . : 192.168.1.2
Subnetzmaske . . . . . . . . . . : 255.255.255.0
Standardgateway . . . . . . . . . : 192.168.1.1
...

Texte zeilenweise lesen und schreiben Bearbeiten

In vielen Fällen werden Texte ‒ vor allem wenn sie länger werden können ‒ nicht im Ganzen sondern Zeile für Zeile ein- oder ausgegeben. Auch dies vereinfacht Groovy durch entsprechende vordefinierte Methoden zur Klasse File.

Wenn Sie eine Datei zeilenweise lesen möchten, benutzen Sie am besten die Methode eachLine(). Sie nimmt eine Closure als Argument an, die jede einzelne Zeile des Textes übergeben bekommt. Lassen Sie uns auf diese Weise die dreizeilige Datei von oben auslesen.

groovy> zaehl = 0
groovy> new File('Beispiel.txt').eachLine { zeile -> 
groovy> println "${++zaehl} - $zeile"
groovy> }
1 - Erste Zeile.
2 - Zweite Zeile.
3 - DritteZeile.

Dies geht auch mit anderen Datenquellen und bietet sich insbesondere dann an, wenn das Lesen der Daten ein längerer Prozess ist, so dass es hilfreich ist, wenn man schon mal einige Zeilen zur Verfügung hat, obwohl der Lesevorgang noch im Gange ist.

Angenommen, Sie wollten mit Hilfe des Systembefehls tracert unter Windows das Routing eines Webseiten-Abrufs nachvollziehen. Dies zieht sich normalerweise etwas hin, daher kann es hier sinnvoll sein, das Ergebnis zeilenweise einzulesen.

groovy> ['tracert','www.google.de'].execute().in.eachLine { println it }
Routenverfolgung zu www.l.google.com [209.85.129.147] über maximal 30 Abschnitte:
1 35 ms 35 ms 35 ms 209.85.249.180 
2 34 ms 35 ms 35 ms 72.14.232.201 
3 * 35 ms 34 ms de-cix10.net.google.com [80.81.192.108] 
4 38 ms 35 ms 35 ms 72.14.233.210 
5 34 ms 35 ms 37 ms fk-in-f147.google.com [209.85.129.147]

Tatsächlich erscheinen die einzelnen Stationen Zeile für Zeile einzeln auf dem Bildschirm. Hier sind aber noch ein paar Erklärungen fällig. Die vordefinierte Methode execute() kann auch auf String-Arrays angewendet werden; in diesem Fall ist das erste Array-Element der Befehl und alle weiteren Elemente enthalten die Argumente. Zurückgeliefert wird eine java.lang.Process-Instanz. Zu Process gibt es aber leider keine vordefinierte Methode eachLine(), mit der wir deren Ausgabe direkt zeilenweise auslesen könnten. Wohl aber gibt es eine Methode getIn(), die uns die Standardausgabe des Prozesses als InputStream gibt, und zu diesem wiederum ist ein eachLine() definiert.

Für den umgekehrten Weg ‒ Daten zeilenweise schreiben ‒ bietet sich eine Gruppe von vordefinierten Methoden an, die eine Datei oder eine andere Datenquelle öffnet, einer als Argument übergebenen Closure einen Datenstrom übergeben und am Ende die Datei in jedem Fall auch wieder schließen. Innerhalb der Closure können Sie mit dem Datenstrom im Prinzip machen, was Sie wollen; um eine angemessene Behandlung der Ressource, d.h. das Öffnen und Schließen sowie um die Fehlerbehandlung, brauchen Sie sich nicht zu kümmern. Diese Art von Methoden gibt es nicht nur für Schreiboperationen, sie bieten sich hier aber besonders an, weil ein Vorgehen wie bei der bequemen Methode eachLine() in umgekehrter Richtung nicht anwendbar ist.

groovy> new File('Beispiel3.txt').withWriter { out ->
groovy> 'ABC'.each { out.writeLine "Zeile $it" } 
groovy> }
groovy> println (new File('Beispiel3.txt').text)
Zeile A
Zeile B
Zeile C

Hier bekommt die Closure in withWriter() einen BufferedWriter übergeben, der in die Datei Beispiel3.txt schreibt. Innerhalb dieser Closure schreiben wir mit Hilfe einer each()-Schleife drei Zeilen in den Writer. Das ist auf diese Weise möglich, weil es für den BufferedWriter eine vordefinierte Methode writeLine() gibt. Nach der Beendigung der Closure schließt withWriter() den Writer ordnungsgemäß, so dass wir uns um diese Einzelheiten nicht kümmern müssen.

Filter und Transformationen Bearbeiten

Wir möchten Sie gerne noch mit ein paar Methoden bekannt machen, die sehr praktisch sind, wenn Sie Textdaten während des Lesens oder Schreibens konvertieren oder bestimmte Zeilen oder Zeichen herausfiltern möchten.

Die für beliebige Reader definierte Methode transformLine() nimmt einen Writer und eine Closure als Argument. Sie übergibt der Closure jede Zeile einzeln und schreibt das jeweilige Ergebnis in den Writer. Das folgende Beispiel kopiert eine Textdatei und stellt dabei jeder Zeile eine Zeilennummer in Klammern voran.

groovy> reader = new FileReader(new File('Beispiel.txt'))
groovy> writer = new FileWriter(new File('BeispielNummeriert.txt'))
groovy> zaehler = 0
groovy> reader.transformLine (writer) { "(${++zaehler}) $it" }
groovy> println (new File('BeispielNummeriert.txt').text)
(1) Erste Zeile.
(2) Zweite Zeile.
(3) Dritte Zeile.

Auch hier brauchen Sie übrigens keine Sorge haben, dass der Reader oder der Writer offen bleiben könnten. Die Methode transformLine() schließt beide ordnungsgemäß.

Analog gibt es eine Methode transformChar(), sie funktioniert genau so wie transformLine(), bekommt aber anstelle der Zeilen jedes einzelne Zeichen als Argument übergeben.

In ähnlicher Weise funktionieren einige Methoden, mit denen Daten zeilenweise gefiltert werden können. Die für Reader definierte Methode filterLine() etwa nimmt eine Closure als Argument an, die für jede Zeile entscheidet, ob sie übertragen werden soll oder nicht.

In dem folgenden Beispiel werden nur Zeilen durchgelassen, die den Buchstaben »t« genau einmal enthalten.

groovy> writer = new FileWriter('BeispielGefiltert.txt')
groovy> new File('Beispiel.txt').filterLine (writer) { it.count('t')==1 }
groovy> println (new File('BeispielGefiltert.txt').text)
Erste Zeile.
Zweite Zeile.

Writable-Objekte Bearbeiten

Wie viele andere vordefinierte Methoden für Ein-Ausgabe-Operationen gibt es auch die Methode filterLine() in verschiedenen Varianten. Das folgende Skript verwendet eine Version der Methode, die für Reader definiert ist. Sie erhält aber keinen Writer als Argument, sondern liefert stattdessen ein spezielles Objekt, das groovy.lang.Writable implementiert. Dieses Interface ist eine spezielle Schöpfung der Groovy-Bibliotheken, die Schreiboperationen besonders effizient machen sollen. Ein Writable-Objekt implementiert die Methoden writeTo(), die einen Writer annimmt, und den Inhalt des Objektes in diese Writer hineinschreibt. Der Vorteil besteht darin, dass der Inhalt des Objektes nicht vor dem Schreiben insgesamt in einen String verwandelt wird, sondern gewissermaßen fließend ausgegeben werden kann.

groovy> reader = new FileReader('Beispiel.txt')
groovy> filter = reader.filterLine { it.count('t')==1 }
groovy> writer = new FileWriter('BeispielGefiltert.txt')
groovy> filter.writeTo(writer)
groovy> writer.close()
groovy> println (new File('BeispielGefiltert.txt').text)
Erste Zeile.
Zweite Zeile.

Beachten Sie, dass wir hier den Writer mit close() selbst schließen müssen und dies nicht einfach einer Groovy-Methode überlassen können. Die vierte Zeile könnten wir mit demselben Ergebnis auch so schreiben:

groovy> writer.write(filter)

Dies liegt daran, dass wir eine vordefinierte write()-Methode für Writer haben, die ein Writable-Argument erkennt und entsprechend behandelt, nämlich bei ihm das writeTo() aufruft.

Writable-Objekten begegnen Sie beim Durchstöbern der Groovy-Standardbibliothek des Öfteren. Beispielsweise können Sie auch ein File-Objekt direkt zu einem Writable machen. Dies ergibt eine einfache Möglichkeit zum Kopieren von Dateien, wie hier von Beispiel.txt nach BeispielKopie.txt:

groovy> new FileWriter('BeispielKopie.txt').withWriter { writer ->
groovy> writer.write( new File('Beispiel.txt').asWritable() )
groovy> }
groovy> println (new File('BeispielKopie.txt').text)
Erste Zeile.
Zweite Zeile.
Dritte Zeile.

Rohdaten Bearbeiten

Ähnliche Möglichkeiten wie für Reader und Writer zum Lesen und Schreiben von Textdaten gibt es auch für Input- und Output-Streams zum Lesen und Schreiben unkodierter Rohdaten. Während aber bei einem Reader oder Writer in der Regel mit Zeilen (manchmal auch mit Zeichen) gearbeitet wird, sind es bei normalen Streams immer Bytes und bei serialisierten Daten immer komplette Objekte oder einzelne primitive Datenelemente.

Binärdaten komplett lesen und schreiben Bearbeiten

Um eine Datei komplett einzulesen, können Sie die für File-Objekte vordefinierte Methode readBytes() verwenden. Sie öffnet die Datei, liest sie komplett in ein byte-Array und sorgt auch wiederum dafür, dass die Datei in jedem Fall geschlossen wird. Im folgenden Beispiel lesen wir die von den obigen Versuchen bekannte Datei Beispiel.txt als Rohdaten ein.

groovy> daten = new File('Beispiel.txt').readBytes()
groovy> println daten
[69, 114, 115, 116, 101, 32, 90, 101, 105, 108, 101, 46, ...

Für den umgekehrten Weg hat Groovy leider noch keine vergleichbare vordefinierte Methode im Angebot. Allerdings ist es auch mit den im Folgenden dargestellten Mitteln recht mühelos möglich, Binärdaten zu schreiben.

Auch hierfür stehen nämlich Methoden zur Verfügung, die einen Stream öffnen und einer Closure übergeben. Innerhalb der Closure haben Sie die volle Kontrolle über den Stream, und nach Beendigung der Closure wird der Stream wieder ordnungsgemäß geschlossen.

Das oben eingelesene byte-Array wieder in eine Datei zu schreiben, gestaltet sich dadurch äußerst simpel:

groovy> new File('Beispiel4.txt').withOutputStream { out -> out.write(daten) }

Und da dies nur eine Zeile braucht, benötigen wir dafür eigentlich keine spezielle Methode. Wenn wir dies und die zuvor betrachtete Methode zusammenfassen, brauchen wir zum Kopieren einer Binärdatei nur eine Zeile:

groovy> file1 = new File('Beispiel.txt')
groovy> file2 = new File('BeispielKopie.txt')
groovy> file2.withOutputStream { it.write(file1.readBytes()) }

Diese Lösung kopiert aber die gesamte Datei in den Hauptspeicher, bevor sie wieder ausgegeben wird. Wie unten beschrieben ist das bei großen Dateien nicht sinnvoll.

Daten über einen InputStream zu lesen, ist nicht in einem Rutsch möglich, zumindest wenn die Länge der Datei nicht vorhergesagt werden kann. Versuchsweise lesen wir die Datei einmal in 10-Byte-Portionen ein.

groovy> daten = new byte[10]
groovy> new File('Beispiel4.txt').withInputStream { inp ->
groovy> def len=0
groovy> while ((len=inp.read(daten)) > 0) { println (daten[0..<len]) }
groovy> }
[69, 114, 115, 116, 101, 32, 90, 101, 105, 108]
[101, 46, 10, 90, 119, 101, 105, 116, 101, 32]
...

Hier wenden wir die standardmäßige read()-Methode des InputStream an, die immer maximal viele Daten einliest, wie in das als Puffer übergebene byte-Array hineinpassen und die Anzahl der gelesenen Bytes zurückgibt. Das ist für Programmierer, die an Groovy gewohnt sind, zwar relativ umständlich, aber immerhin sparen wir uns immer noch das ganze Exception-Handling, das die Arbeit mit Dateien in Java oft recht mühsam macht.

Daten Byte-weise lesen Bearbeiten

Groovy bietet noch ein Bonbon für den Fall, dass einmal ein Stream Byte für Byte gelesen werden muss. Die für File vordefinierte Methode eachByte() lässt uns eine Closure angeben, die mit jedem Byte der Datei einzeln aufgerufen wird. Sehen wir uns auch dies einmal an, indem wir die Bytes in der Datei zählen.

groovy> anzahl = 0
groovy> new File('Beispiel.txt').eachByte { anzahl++ }
groovy> println "Die Datei ist $anzahl Bytes lang."
Die Datei ist 41 Bytes lang.

Dasselbe ist möglich, wenn Sie bereits einen geöffneten InputStream in der Hand haben.

groovy> anzahl = 0
groovy> input = new FileInputStream (new File('Beispiel.txt'))
groovy> input.eachByte { anzahl++ }
groovy> println "Die Datei ist $anzahl Bytes lang."
Die Datei ist 41 Bytes lang.

Auch hier können Sie sicher sein, dass der InputStream am Ende der Schleife geschlossen wird, und brauchen sich nicht mit dem Abfangen möglicher IO-Exceptions beschäftigen ‒ es sei denn, Sie müssen mit dem Auftreten eine Exception rechnen; in diesem Fall können Sie dies natürlich wie gewohnt abfangen, indem Sie die kritischen Zeilen in einen try-catch-Block einschließen.


Sie haben in diesem Kapitel einen Überblick darüber bekommen, welche Standard-Java-Klassen durch vordefinierte Methoden in ihrem Funktionsumfang erweitert worden sind, so dass sie ‒ insbesondere unter Anwendung der Groovy-eigenen Möglichkeiten wie Closures und überladbare Operatoren ‒ in Groovy-Programmen einfacher, übersichtlicher und auch sicherer Verwendet werden können, und wie diese Klassen durch die Sprache unterstützt werden. Wir wenden uns nun weiteren einigen weiteren Goodies zu, die Sie mit Groovy frei Haus geliefert bekommen.


  1. Ein Leser merkt mit recht an, dass man sich als Mathematiker nicht darauf verlassen sollte, dass, wenn bestimmte Zeichen in einem Teil eines Textes nicht auftauchen, dies auch bei dem Rest so ist. Es sind schon ganze Romane ohne den Buchstaben e geschrieben worden (so La Disparition von Georges Perec; deutsch Anton Voyls Fortgang, auch ohne e). Übertragen heißt dies: natürlich ist es auch möglich, einen langen Text ohne Umlaute und Sonderzeichen zu schreiben ‒ allerdings brauchen wir diesen Text dann auch nicht kodieren.