Leitfaden für E / A in Groovy

1. Einleitung

Obwohl wir in Groovy genauso wie in Java mit E / A arbeiten können, erweitert Groovy die E / A-Funktionalität von Java um eine Reihe von Hilfsmethoden.

In diesem Tutorial werden wir beim Lesen und Schreiben von Dateien suchen, Dateisysteme durchlaufen und Daten und Objekte über Groovy Serialisierung Dateierweiterungsmethoden.

Gegebenenfalls verlinken wir auf unsere relevanten Java-Artikel, um einen einfachen Vergleich mit dem Java-Äquivalent zu ermöglichen.

2. Dateien lesen

Groovy fügt praktische Funktionalität für Dateien in Form der Lese eachLine Methoden, Methoden , um BufferedReader s und Input s, und Möglichkeiten , mit einer Codezeile alle Dateidaten zu erhalten.

Java 7 und Java 8 unterstützen das Lesen von Dateien in Java auf ähnliche Weise.

2.1. Lesen mit jeder Zeile

Beim Umgang mit Textdateien müssen wir häufig jede Zeile lesen und verarbeiten. Groovy bietet eine bequeme Erweiterung von java.io.File mit der eachLine- Methode :

def lines = [] new File('src/main/resources/ioInput.txt').eachLine { line -> lines.add(line) }

Der für jede Zeile bereitgestellte Verschluss hat auch eine nützliche optionale Zeilennummer. Verwenden wir die Zeilennummer, um nur bestimmte Zeilen aus einer Datei abzurufen:

def lineNoRange = 2..4 def lines = [] new File('src/main/resources/ioInput.txt').eachLine { line, lineNo -> if (lineNoRange.contains(lineNo)) { lines.add(line) } }

Standardmäßig beginnt die Zeilennummerierung bei eins. Wir können einen Wert angeben , der als erste Zeilennummer verwendet werden soll, indem wir ihn als ersten Parameter an die eachLine- Methode übergeben.

Beginnen wir unsere Zeilennummern bei Null:

new File('src/main/resources/ioInput.txt').eachLine(0, { line, lineNo -> if (lineNoRange.contains(lineNo)) { lines.add(line) } })

Wenn in jeder Zeile eine Ausnahme ausgelöst wird, stellt Groovy sicher , dass die Dateiressource geschlossen wird . Ähnlich wie ein Versuch mit Ressourcen oder ein Versuch mit Java.

2.2. Lesen mit Reader

Wir können auch leicht einen BufferedReader von einem Groovy File- Objekt erhalten. Wir können withReader verwenden , um einen BufferedReader an das Dateiobjekt zu senden und an einen Abschluss zu übergeben:

def actualCount = 0 new File('src/main/resources/ioInput.txt').withReader { reader -> while(reader.readLine()) { actualCount++ } }

Wie bei jeder Zeile schließt die withReader- Methode die Ressource automatisch, wenn eine Ausnahme ausgelöst wird.

Manchmal möchten wir möglicherweise das BufferedReader- Objekt verfügbar haben. Zum Beispiel könnten wir planen, eine Methode aufzurufen, die eine als Parameter verwendet. Wir können die newReader- Methode dafür verwenden:

def outputPath = 'src/main/resources/ioOut.txt' def reader = new File('src/main/resources/ioInput.txt').newReader() new File(outputPath).append(reader) reader.close()

Im Gegensatz zu den anderen Methoden, die wir bisher betrachtet haben, sind wir dafür verantwortlich, die BufferedReader- Ressource zu schließen, wenn wir auf diese Weise einen Buffered Reader erwerben .

2.3. Lesen mit InputStream s

Ähnlich wie withReader und newReader , Groovy stellt auch Verfahren zur mit leicht arbeiten Input s . Obwohl wir Text mit InputStreams lesen können und Groovy sogar Funktionen hinzufügt, werden InputStreams am häufigsten für Binärdaten verwendet.

Verwenden wir withInputStream , um einen InputStream an einen Abschluss zu übergeben und die Bytes einzulesen:

byte[] data = [] new File("src/main/resources/binaryExample.jpg").withInputStream { stream -> data = stream.getBytes() }

Wenn wir das InputStream- Objekt benötigen , können wir eines mit newInputStream abrufen :

def outputPath = 'src/main/resources/binaryOut.jpg' def is = new File('src/main/resources/binaryExample.jpg').newInputStream() new File(outputPath).append(is) is.close()

Wie beim BufferedReader müssen wir unsere InputStream- Ressource selbst schließen, wenn wir newInputStream verwenden, nicht jedoch, wenn wir withInputStream verwenden .

2.4. Andere Wege lesen

Lassen Sie uns das Thema Lesen beenden, indem wir uns einige Methoden ansehen, mit denen Groovy alle Dateidaten in einer Anweisung erfasst.

Wenn wir die Zeilen unserer Datei in einer Liste haben möchten , können wir collect mit einem Iterator verwenden , der an den Abschluss übergeben wurde:

def actualList = new File('src/main/resources/ioInput.txt').collect {it}

Um die Zeilen unserer Datei in ein Array von Strings zu bringen , können wir als String [] verwenden :

def actualArray = new File('src/main/resources/ioInput.txt') as String[]

Bei kurzen Dateien können wir den gesamten Inhalt eines Strings mithilfe von Text abrufen :

def actualString = new File('src/main/resources/ioInput.txt').text

Und wenn Sie mit Binärdateien arbeiten, gibt es die Byte- Methode:

def contents = new File('src/main/resources/binaryExample.jpg').bytes

3. Dateien schreiben

Bevor wir mit dem Schreiben in Dateien beginnen, richten wir den Text ein, den wir ausgeben:

def outputLines = [ 'Line one of output example', 'Line two of output example', 'Line three of output example' ]

3.1. Schreiben mit Writer

Wie beim Lesen einer Datei können wir auch problemlos einen BufferedWriter aus einem File- Objekt abrufen .

Lassen Sie uns die Verwendung withWriter eine bekommen BufferedWriter und es zu einem Verschluss passieren:

def outputFileName = 'src/main/resources/ioOutput.txt' new File(outputFileName).withWriter { writer -> outputLines.each { line -> writer.writeLine line } }

Wenn Sie withReader verwenden, wird die Ressource geschlossen, falls eine Ausnahme auftritt.

Groovy hat auch eine Methode zum Abrufen des BufferedWriter- Objekts. Lassen Sie uns einen BufferedWriter mit newWriter erstellen :

def outputFileName = 'src/main/resources/ioOutput.txt' def writer = new File(outputFileName).newWriter() outputLines.forEach {line -> writer.writeLine line } writer.flush() writer.close()

We're responsible for flushing and closing our BufferedWriter object when we use newWriter.

3.2. Writing with Output Streams

If we're writing out binary data, we can get an OutputStream using either withOutputStream or newOutputStream.

Let's write some bytes to a file using withOutputStream:

byte[] outBytes = [44, 88, 22] new File(outputFileName).withOutputStream { stream -> stream.write(outBytes) }

Let's get an OutputStream object with newOutputStream and use it to write some bytes:

byte[] outBytes = [44, 88, 22] def os = new File(outputFileName).newOutputStream() os.write(outBytes) os.close()

Similarly to InputStream, BufferedReader, and BufferedWriter, we're responsible for closing the OutputStream ourselves when we use newOutputStream.

3.3. Writing with the << Operator

As writing text to files is so common, the << operator provides this feature directly.

Let's use the << operator to write some simple lines of text:

def ln = System.getProperty('line.separator') def outputFileName = 'src/main/resources/ioOutput.txt' new File(outputFileName) << "Line one of output example${ln}" + "Line two of output example${ln}Line three of output example"

3.4. Writing Binary Data with Bytes

We saw earlier in the article that we can get all the bytes out of a binary file simply by accessing the bytes field.

Let's write binary data the same way:

def outputFileName = 'src/main/resources/ioBinaryOutput.bin' def outputFile = new File(outputFileName) byte[] outBytes = [44, 88, 22] outputFile.bytes = outBytes

4. Traversing File Trees

Groovy also provides us with easy ways to work with file trees. In this section, we're going to do that with eachFile, eachDir and their variants and the traverse method.

4.1. Listing Files with eachFile

Let's list all of the files and directories in a directory using eachFile:

new File('src/main/resources').eachFile { file -> println file.name }

Another common scenario when working with files is the need to filter the files based on file name. Let's list only the files that start with “io” and end in “.txt” using eachFileMatch and a regular expression:

new File('src/main/resources').eachFileMatch(~/io.*\.txt/) { file -> println file.name }

The eachFile and eachFileMatch methods only list the contents of the top-level directory. Groovy also allows us to restrict what the eachFile methods return by passing a FileType to the methods. The options are ANY, FILES, and DIRECTORIES.

Let's recursively list all the files using eachFileRecurse and providing it with a FileType of FILES:

new File('src/main').eachFileRecurse(FileType.FILES) { file -> println "$file.parent $file.name" }

The eachFile methods throw an IllegalArgumentException if we provide them with a path to a file instead of a directory.

Groovy also provides the eachDir methods for working with only directories. We can use eachDir and its variants to accomplish the same thing as using eachFile with a FileType of DIRECTORIES.

Let's recursively list directories with eachFileRecurse:

new File('src/main').eachFileRecurse(FileType.DIRECTORIES) { file -> println "$file.parent $file.name" }

Now, let's do the same thing with eachDirRecurse:

new File('src/main').eachDirRecurse { dir -> println "$dir.parent $dir.name" }

4.2. Listing Files with Traverse

For more complicated directory traversal use cases, we can use the traverse method. It functions similarly to eachFileRecurse but provides the ability to return FileVisitResult objects to control the processing.

Let's use traverse on our src/main directory and skip processing the tree under the groovy directory:

new File('src/main').traverse { file -> if (file.directory && file.name == 'groovy') { FileVisitResult.SKIP_SUBTREE } else { println "$file.parent - $file.name" } }

5. Working with Data and Objects

5.1. Serializing Primitives

In Java, we can use DataInputStream and DataOutputStream to serialize primitive data fields. Groovy adds useful expansions here as well.

Let's set up some primitive data:

String message = 'This is a serialized string' int length = message.length() boolean valid = true

Now, let's serialize our data to a file using withDataOutputStream:

new File('src/main/resources/ioData.txt').withDataOutputStream { out -> out.writeUTF(message) out.writeInt(length) out.writeBoolean(valid) }

And read it back in using withDataInputStream:

String loadedMessage = "" int loadedLength boolean loadedValid new File('src/main/resources/ioData.txt').withDataInputStream { is -> loadedMessage = is.readUTF() loadedLength = is.readInt() loadedValid = is.readBoolean() }

Similar to the other with* methods, withDataOutputStream and withDataInputStream pass the stream to the closure and ensure it's closed properly.

5.2. Serializing Objects

Groovy also builds upon Java's ObjectInputStream and ObjectOutputStream to allow us to easily serialize objects that implement Serializable.

Let's first define a class that implements Serializable:

class Task implements Serializable { String description Date startDate Date dueDate int status }

Now let's create an instance of Task that we can serialize to a file:

Task task = new Task(description:'Take out the trash', startDate:new Date(), status:0)

With our Task object in hand, let's serialize it to a file using withObjectOutputStream:

new File('src/main/resources/ioSerializedObject.txt').withObjectOutputStream { out -> out.writeObject(task) }

Lassen Sie uns abschließend unsere Aufgabe mit withObjectInputStream zurücklesen :

Task taskRead new File('src/main/resources/ioSerializedObject.txt').withObjectInputStream { is -> taskRead = is.readObject() }

Die von uns verwendeten Methoden withObjectOutputStream und withObjectInputStream übergeben den Stream an einen Abschluss und behandeln das Schließen der Ressourcen entsprechend, wie bei den anderen Methoden mit * .

6. Fazit

In diesem Artikel haben wir die Funktionen untersucht, die Groovy zu vorhandenen Java File I / O-Klassen hinzufügt. Wir haben diese Funktion verwendet, um Dateien zu lesen und zu schreiben, mit Verzeichnisstrukturen zu arbeiten und Daten und Objekte zu serialisieren.

Wir haben nur einige der Hilfsmethoden angesprochen, daher lohnt es sich, in der Dokumentation von Groovy nachzuschlagen, was die E / A-Funktionalität von Java noch erweitert.

Der Beispielcode ist über GitHub verfügbar.