Guide to I/O in Groovy – Groovy中的I/O指南

最后修改: 2019年 3月 6日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Although in Groovy we can work with I/O just as we do in Java, Groovy expands on Java’s I/O functionality with a number of helper methods.

虽然在Groovy中,我们可以像在Java中那样处理I/O,但Groovy通过一些辅助方法扩展了Java的I/O功能。

In this tutorial, we’ll look at reading and writing files, traversing file systems and serializing data and objects via Groovy’s File extension methods.

在本教程中,我们将通过Groovy的File 扩展方法来研究读写文件、遍历文件系统以及序列化数据和对象。

Where applicable, we’ll be linking to our relevant Java articles for easy comparison to the Java equivalent.

在适当的地方,我们将链接到我们相关的Java文章,以便于与Java的对应内容进行比较。

2. Reading Files

2.阅读文件

Groovy adds convenient functionality for reading files in the form of the eachLine methods, methods for getting BufferedReaders and InputStreams, and ways to get all the file data with one line of code.

Groovy为读取文件增加了方便的功能,其形式包括eachLine方法、获取BufferedReaders和InputStreams的方法,以及用一行代码获取所有文件数据的方法。

Java 7 and Java 8 have similar support for reading files in Java.

Java 7和Java 8对在Java中阅读文件有类似支持。

2.1. Reading with eachLine

2.1.用eachLine阅读

When dealing with text files, we often need to read each line and process it. Groovy provides a convenient extension to java.io.File with the eachLine method:

在处理文本文件时,我们经常需要读取每一行并对其进行处理。Groovy为java.io.File提供了一个方便的扩展,即 eachLine方法

def lines = []

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

The closure provided to eachLine also has a useful optional line number. Let’s use the line number to get only specific lines from a file:

提供给eachLine的闭包也有一个有用的可选行号。让我们使用行号来从一个文件中只获得特定的行。

def lineNoRange = 2..4
def lines = []

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

By default, the line numbering starts at one. We can provide a value to use as the first line number by passing it as the first parameter to the eachLine method.

默认情况下,行数从一开始。我们可以提供一个值作为第一行的编号,将其作为第一个参数传递给eachLine方法。

Let’s start our line numbers at zero:

让我们从零开始我们的行数。

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

If an exception is thrown in eachLine, Groovy makes sure the file resource gets closed. Much like a try-with-resources or a try-finally in Java.

如果在eachLine中抛出一个异常,Groovy会确保文件资源被关闭。很像Java中的try-with-resourcestry-finally

2.2. Reading with Reader

2.2.用阅读器阅读

We can also easily get a BufferedReader from a Groovy File object. We can use withReader to get a BufferedReader to the file object and pass it to a closure:

我们也可以很容易地从Groovy的File对象中获得一个BufferedReader。我们可以使用withReader来获取文件对象的BufferedReader并将其传递给一个闭包。

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

As with eachLine, the withReader method will automatically close the resource when an exception is thrown.

eachLine一样,withReader方法将在抛出异常时自动关闭资源。

Sometimes, we might want to have the BufferedReader object available. For example, we might plan to call a method that takes one as a parameter. We can use the newReader method for this:

有时,我们可能想让BufferedReader对象可用。例如,我们可能打算调用一个以缓冲器为参数的方法。我们可以为此使用newReader方法。

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

Unlike the other methods we’ve looked at so far, we’re responsible for closing the BufferedReader resource when we acquire a BufferedReader this way.

与我们迄今为止所看到的其他方法不同,当我们通过这种方式获得一个BufferedReader时,我们要负责关闭BufferedReader资源。

2.3. Reading with InputStreams

2.3.用InputStreams阅读

Similar to withReader and newReader, Groovy also provides methods for easily working with InputStreams. Although we can read text with InputStreams and Groovy even adds functionality for it, InputStreams are most commonly used for binary data.

withReadernewReader类似,Groovy也提供了轻松处理InputStreams的方法。虽然我们可以用InputStreams读取文本,Groovy甚至为它添加了功能,但InputStreams最常用于二进制数据。

Let’s use withInputStream to pass an InputStream to a closure and read in the bytes:

让我们使用withInputStream将一个InputStream传递给一个闭包并读入字节。

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

If we need to have the InputStream object, we can get one using newInputStream:

如果我们需要有InputStream对象,我们可以使用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()

As with the BufferedReader, we need to close our InputStream resource ourselves when we use newInputStream, but not when using withInputStream.

BufferedReader一样,当我们使用newInputStream时,我们需要自己关闭我们的InputStream资源,但在使用withInputStream时则不需要。

2.4. Reading Other Ways

2.4.其他方式的阅读

Let’s finish the subject of reading by looking at a few methods Groovy has for grabbing all the file data in one statement.

让我们通过看看Groovy在一条语句中抓取所有文件数据的几种方法来结束阅读这个主题。

If we want the lines of our file in a List, we can use collect with an iterator it passed to the closure:

如果我们想把文件的行数放在一个List中,我们可以使用collect,并把一个迭代器it传递给closure。

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

To get the lines of our file into an array of Strings, we can use as String[]:

为了把我们的文件行数变成一个字符串的数组,我们可以使用as String[]

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

For short files, we can get the entire contents in a String using text:

对于短文件,我们可以使用text获得String中的全部内容。

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

And when working with binary files, there’s the bytes method:

而在处理二进制文件时,有一个bytes方法。

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

3. Writing Files

3.写作文件

Before we start writing to files, let’s set up the text we’ll be outputting:

在我们开始写入文件之前,让我们设置我们要输出的文本。

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

3.1. Writing with Writer

3.1.用作家写作

As with reading a file, we can also easily get a BufferedWriter out of a File object.

与读取文件一样,我们也可以很容易地从文件对象中得到一个BufferedWriter

Let’s use withWriter to get a BufferedWriter and pass it to a closure:

让我们使用withWriter来获取一个BufferedWriter并将其传递给一个闭包。

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

Using withReader will close the resource should an exception occur.

如果发生异常,使用withReader将关闭该资源。

Groovy also has a method for getting the BufferedWriter object. Let’s get a BufferedWriter using newWriter:

Groovy也有一个方法来获取BufferedWriter对象。让我们使用newWriter获得一个BufferedWriter

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.

当我们使用newWriter时,我们负责冲刷和关闭我们的BufferedWriter对象。

3.2. Writing with Output Streams

3.2.用输出流进行写作

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

如果我们要写出二进制数据,我们可以使用withOutputStreamnewOutputStream获得一个OutputStream

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

让我们使用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:

让我们用newOutputStream得到一个OutputStream对象,用它来写一些字节。

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.

InputStreamBufferedReaderBufferedWriter类似,当我们使用newOutputStream时,我们要负责自己关闭OutputStream

3.3. Writing with the << Operator

3.3.用<<操作符书写

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

3.4.用字节写入二进制数据

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

我们在文章前面看到,我们可以通过访问bytes字段来获得二进制文件的所有字节。

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

4.遍历文件树

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.

Groovy还为我们提供了处理文件树的简单方法。在本节中,我们将使用eachFileeachDir及其变体和traverse方法进行处理。

4.1. Listing Files with eachFile

4.1.用eachFile列出文件

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

让我们使用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:

在处理文件时,另一个常见的情况是需要根据文件名来过滤文件。让我们使用eachFileMatch 和一个正则表达式,只列出以 “io “开头、以”.txt “结尾的文件。

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.

eachFileeachFileMatch方法只列出顶级目录的内容。Groovy还允许我们通过向方法传递FileType来限制eachFile方法的返回内容。这些选项包括ANYFILESDIRECTORIES

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

让我们使用eachFileRecurse递归地列出所有的文件,并为它提供FileTypeFILES

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.

如果我们向eachFile方法提供的是一个文件的路径而不是一个目录,那么这些方法会抛出一个IllegalArgumentException

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.

Groovy还提供了只处理目录的eachDir方法。我们可以使用eachDir及其变体来完成与使用eachFileFileTypeDIRECTORIES相同的事情。

Let’s recursively list directories with eachFileRecurse:

让我们用eachFileRecurse递归地列出目录。

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

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

现在,让我们对eachDirRecurse做同样的事情。

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

4.2. Listing Files with Traverse

4.2.用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.

对于更复杂的目录遍历用例,我们可以使用traverse方法。它的功能与eachFileRecurse类似,但提供了返回FileVisitResult对象的能力,以控制处理。

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

让我们在src/main目录上使用traverse,跳过处理groovy目录下的树。

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.与数据和对象一起工作

5.1. Serializing Primitives

5.1.序列化基元

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

在Java中,我们可以使用DataInputStreamDataOutputStream序列化原始数据域。Groovy在这里也增加了有用的扩展。

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:

现在,让我们使用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:

并使用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.

与其他with*方法类似,withDataOutputStreamwithDataInputStream将流传递给闭包,并确保它被正确关闭。

5.2. Serializing Objects

5.2.序列化对象

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

Groovy还建立在Java的ObjectInputStreamObjectOutputStream之上,使我们能够轻松地序列化实现Serializable的对象。

Let’s first define a class that implements Serializable:

让我们首先定义一个实现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 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:

有了我们的Task对象,让我们使用withObjectOutputStream将其序列化到一个文件。

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

Finally, let’s read our Task back in using withObjectInputStream:

最后,让我们使用withObjectInputStream读回我们的Task

Task taskRead

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

The methods we used, withObjectOutputStream and withObjectInputStream, pass the stream to a closure and handle closing the resources appropriately, just as with seen with the other with* methods.

我们使用的方法,withObjectOutputStreamwithObjectInputStream,将流传递给一个闭包,并适当处理关闭资源,就像在其他with*方法中看到的那样。

6. Conclusion

6.结语

In this article, we explored functionality that Groovy adds onto existing Java File I/O classes. We used this functionality to read and write files, work with directory structures, and serialize data and objects.

在这篇文章中,我们探讨了Groovy在现有的Java文件I/O类上增加的功能。我们用这些功能来读写文件,处理目录结构,以及序列化数据和对象。

We only touched on a few of the helper methods, so it’s worth digging into Groovy’s documentation to see what else it adds to Java’s I/O functionality.

我们只提到了一些辅助方法,所以值得深入研究Groovy的文档,看看它还为Java的I/O功能增加了什么。

The example code is available over on GitHub.

示例代码可在GitHub上获得over