Panduan untuk I / O di Groovy

1. Pengenalan

Walaupun di Groovy kita dapat bekerja dengan I / O seperti yang kita lakukan di Java, Groovy memperluas fungsi I / O Java dengan sejumlah kaedah pembantu.

Dalam tutorial ini, kita akan melihat membaca dan menulis fail, melintasi sistem fail dan membuat siri data dan objek melalui kaedah peluasan Fail Groovy .

Sekiranya berkenaan, kami akan memautkan ke artikel Java yang relevan untuk memudahkan perbandingan dengan yang setara dengan Java.

2. Membaca Fail

Groovy menambah fungsi yang sesuai untuk membaca fail dalam bentuk kaedah masing - masing , kaedah untuk mendapatkan BufferedReader dan InputStream s, dan cara untuk mendapatkan semua data fail dengan satu baris kod.

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

2.1. Membaca dengan masing - masing Talian

Semasa berurusan dengan fail teks, kita sering perlu membaca setiap baris dan memprosesnya. Groovy memberikan pelanjutan yang mudah untuk java.io.File dengan kaedah masing - masing :

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

Penutupan yang disediakan untuk setiapLine juga mempunyai nombor garis pilihan yang berguna Mari gunakan nombor baris untuk mendapatkan baris tertentu sahaja dari fail:

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

Secara lalai, penomboran baris bermula pada satu. Kami dapat memberikan nilai untuk digunakan sebagai nombor baris pertama dengan meneruskannya sebagai parameter pertama ke kaedah masing - masing .

Mari mulakan nombor baris kami pada sifar:

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

Sekiranya pengecualian dilemparkan di setiap baris, Groovy memastikan sumber fail ditutup . Sama seperti percubaan dengan sumber atau percubaan akhirnya di Java.

2.2. Membaca dengan Pembaca

Kita juga dapat dengan mudah mendapatkan BufferedReader dari objek Groovy File . Kita dapat 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 setiapLine , kaedah withReader secara automatik akan menutup sumber apabila pengecualian dilemparkan.

Kadang-kadang, kita mungkin mahu objek BufferedReader tersedia. Sebagai contoh, kami mungkin merancang untuk memanggil kaedah yang menggunakan satu sebagai parameter. Kita boleh menggunakan kaedah 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 kaedah lain yang telah kita lihat setakat ini, kita bertanggungjawab untuk menutup sumber BufferedReader apabila kita memperoleh Buffered Reader dengan cara ini.

2.3. Membaca dengan InputStream s

Sama dengan denganReader dan newReader , Groovy juga menyediakan kaedah untuk bekerja dengan mudah dengan InputStream s . Walaupun kita dapat membaca teks dengan InputStream s dan Groovy bahkan menambahkan fungsi untuknya, InputStream s paling sering digunakan untuk data binari.

Mari gunakan denganInputStream untuk menyampaikan InputStream ke penutupan dan membaca dalam byte:

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

Sekiranya kita perlu memiliki objek InputStream , kita bisa mendapatkannya dengan menggunakanInputStream baru :

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 BufferedReader , kita perlu menutup sumber InputStream kita sendiri ketika kita menggunakanInputStream baru, tetapi tidak ketika menggunakan denganInputStream .

2.4. Membaca Cara Lain

Mari kita selesaikan subjek pembacaan dengan melihat beberapa kaedah yang dimiliki oleh Groovy untuk meraih semua data fail dalam satu pernyataan.

Jika kita mahu garis fail kami dalam Senarai , kita boleh menggunakan collect dengan iterator ia diserahkan kepada penutupan:

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

Untuk mendapatkan baris fail kami ke pelbagai Strings , kita boleh menggunakan sebagai String [] :

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

Untuk fail pendek, kita boleh mendapatkan keseluruhan kandungan dalam Rentetan menggunakan teks :

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

Dan semasa bekerja dengan fail binari, ada kaedah bait :

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

3. Menulis Fail

Sebelum kita mula menulis ke fail, mari siapkan teks yang akan kita keluarkan:

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

3.1. Menulis dengan Penulis

Seperti membaca fail, kita juga dapat dengan mudah mengeluarkan BufferedWriter dari objek Fail .

Mari gunakan denganWriter untuk mendapatkan BufferedWriter dan meneruskannya ke penutupan:

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

Menggunakan withReader akan menutup sumber sekiranya berlaku pengecualian.

Groovy juga mempunyai kaedah untuk mendapatkan objek BufferedWriter . Mari 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) }

Akhir sekali, mari kita baca Task semula menggunakan withObjectInputStream :

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

Kaedah yang kami gunakan, denganObjectOutputStream dan withObjectInputStream , meneruskan aliran ke penutupan dan menangani penutupan sumber dengan tepat, seperti yang dilihat dengan kaedah lain dengan * .

6. Kesimpulannya

Dalam artikel ini, kami meneroka fungsi yang ditambahkan oleh Groovy ke kelas Java File I / O yang ada. Kami menggunakan fungsi ini untuk membaca dan menulis fail, bekerja dengan struktur direktori, dan menyusun data dan objek.

Kami hanya menyentuh beberapa kaedah pembantu, jadi perlu menggali dokumentasi Groovy untuk melihat apa lagi yang ditambahkannya pada fungsi I / O Java.

Kod contoh boleh didapati di GitHub.