Panduan untuk I / O di Groovy

1. Perkenalan

Meskipun di Groovy kita dapat bekerja dengan I / O seperti yang kita lakukan di Java, Groovy memperluas fungsionalitas I / O Java dengan sejumlah metode pembantu.

Dalam tutorial ini, kita akan melihat membaca dan menulis file, melintasi sistem file dan membuat serial data dan objek melalui metode ekstensi File Groovy .

Jika memungkinkan, kami akan menautkan ke artikel Java kami yang relevan untuk memudahkan perbandingan dengan Java yang setara.

2. Membaca File

Groovy menambahkan fungsionalitas yang mudah digunakan untuk membaca file dalam bentuk metode eachLine , metode untuk mendapatkan BufferedReader dan InputStream , serta cara mendapatkan semua data file dengan satu baris kode.

Java 7 dan Java 8 memiliki dukungan serupa untuk membaca file di Java.

2.1. Membaca dengan eachLine

Saat berurusan dengan file teks, kita sering kali perlu membaca setiap baris dan memprosesnya. Groovy menyediakan ekstensi yang nyaman untuk java.io.File dengan metode eachLine :

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

Penutupan yang disediakan untuk eachLine juga memiliki nomor baris opsional yang berguna. Mari gunakan nomor baris untuk mendapatkan hanya baris tertentu dari file:

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

Secara default, penomoran baris dimulai dari satu. Kita dapat memberikan nilai untuk digunakan sebagai nomor baris pertama dengan meneruskannya sebagai parameter pertama ke metode eachLine .

Mari kita mulai nomor baris kita dari nol:

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

Jika pengecualian dilemparkan di eachLine, Groovy memastikan sumber daya file ditutup . Mirip seperti percobaan dengan sumber daya atau percobaan terakhir di Java.

2.2. Membaca dengan Pembaca

Kita juga bisa dengan mudah mendapatkan BufferedReader dari objek File Groovy . Kita bisa menggunakan withReader untuk mendapatkan BufferedReader ke objek file dan meneruskannya ke penutupan:

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

Seperti eachLine , metode withReader akan secara otomatis menutup sumber daya saat pengecualian dilemparkan.

Terkadang, kita mungkin ingin agar objek BufferedReader tersedia. Misalnya, kita mungkin berencana memanggil metode yang menggunakan salah satunya sebagai parameter. Kita bisa menggunakan metode newReader untuk ini:

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

Tidak seperti metode lain yang telah kita lihat sejauh ini, kita bertanggung jawab untuk menutup sumber daya BufferedReader saat kita memperoleh Buffered Reader dengan cara ini.

2.3. Membaca dengan InputStream s

Mirip dengan withReader dan newReader , Groovy juga menyediakan metode untuk dengan mudah bekerja dengan InputStream s . Meskipun kita dapat membaca teks dengan InputStream dan Groovy bahkan menambahkan fungsionalitas untuknya, InputStream paling sering digunakan untuk data biner.

Mari kita gunakan withInputStream untuk meneruskan InputStream ke penutupan dan membaca dalam byte:

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

Jika kita perlu memiliki objek InputStream , kita bisa mendapatkannya menggunakan newInputStream :

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

Seperti halnya BufferedReader , kita perlu menutup sendiri resource InputStream saat menggunakan newInputStream, tetapi tidak saat menggunakan withInputStream .

2.4. Membaca Cara Lain

Mari selesaikan subjek pembacaan dengan melihat beberapa metode yang dimiliki Groovy untuk mengambil semua data file dalam satu pernyataan.

Jika kita ingin baris file kita dalam List , kita dapat menggunakan kumpulkan dengan iterator yang diteruskan ke closure:

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

Untuk memasukkan baris-baris file kita ke dalam array Strings , kita bisa menggunakan String [] :

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

Untuk file pendek, kita bisa mendapatkan seluruh konten dalam sebuah String menggunakan teks :

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

Dan saat bekerja dengan file biner, ada metode byte :

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

3. Menulis File

Sebelum kita mulai menulis ke file, mari kita siapkan teks yang akan kita keluaran:

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

3.1. Menulis dengan Penulis

Seperti membaca file, kita juga bisa dengan mudah mengeluarkan BufferedWriter dari objek File .

Mari kita gunakan withWriter untuk mendapatkan BufferedWriter dan meneruskannya ke closure:

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

Menggunakan withReader akan menutup sumber daya jika terjadi pengecualian.

Groovy juga memiliki metode untuk mendapatkan objek BufferedWriter . Mari kita dapatkan BufferedWriter menggunakan newWriter :

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) }

Terakhir, mari kita baca kembali Tugas kita menggunakan withObjectInputStream :

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

Metode yang kami gunakan, withObjectOutputStream dan withObjectInputStream , meneruskan aliran ke closure dan menangani penutupan sumber daya dengan tepat, seperti yang terlihat dengan metode with * lainnya .

6. Kesimpulan

Pada artikel ini, kami menjelajahi fungsionalitas yang ditambahkan Groovy ke kelas I / O File Java yang sudah ada. Kami menggunakan fungsionalitas ini untuk membaca dan menulis file, bekerja dengan struktur direktori, dan membuat serial data dan objek.

Kami hanya menyentuh beberapa metode helper, jadi ada baiknya menggali dokumentasi Groovy untuk melihat apa lagi yang ditambahkannya ke fungsionalitas I / O Java.

Kode contoh tersedia di GitHub.