Groovy中的I/O 简介
1. 简介
尽管在Groovy 中我们可以像在 Java 中一样使用 I/O,但Groovy 使用许多辅助方法扩展了 Java 的 I/O 功能。
在本教程中,我们将了解如何通过 Groovy 的文件扩展方法读取和写入文件、遍历文件系统以及序列化数据和对象。
在适用的情况下,我们将链接到我们的相关 Java 文章,以便与 Java 等效文章进行比较。
2. 读取文件
Groovy以eachLine方法、获取BufferedReader和InputStream的方法以及通过一行代码获取所有文件数据的方法的形式添加了读取文件 的便捷功能。
Java 7 和 Java 8 对在 Java 中读取文件 具有类似的支持。
2.1. eachLine读取
在处理文本文件时,我们经常需要读取每一行并进行处理。Groovy使用eachLine方法为java.io.File提供了一个方便的扩展:
def lines = []
new File('src/main/resources/ioInput.txt').eachLine { line ->
lines.add(line)
}
提供给eachLine的闭包也有一个有用的可选行号。让我们使用行号从文件中仅获取特定行:
def lineNoRange = 2..4
def lines = []
new File('src/main/resources/ioInput.txt').eachLine { line, lineNo ->
if (lineNoRange.contains(lineNo)) {
lines.add(line)
}
}
**默认情况下,行号从一开始。**我们可以通过将它作为第一个参数传递给eachLine方法来提供一个用作第一行号的值。
让我们从零开始我们的行号:
new File('src/main/resources/ioInput.txt').eachLine(0, { line, lineNo ->
if (lineNoRange.contains(lineNo)) {
lines.add(line)
}
})
如果在eachLine中抛出异常, Groovy 会确保文件资源被关闭。很像 Java 中的try-with-resources或try-finally。
2.2. 与读者一起阅读
我们还可以轻松地从 Groovy File对象中获取BufferedReader。我们可以使用withReader获取文件对象的BufferedReader并将其传递给闭包:
def actualCount = 0
new File('src/main/resources/ioInput.txt').withReader { reader ->
while(reader.readLine()) {
actualCount++
}
}
与eachLine一样,withReader方法会在抛出异常时自动关闭资源。
有时,我们可能希望BufferedReader对象可用。例如,我们可能计划调用一个将 1 作为参数的方法。我们可以为此使用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()
与到目前为止我们看到的其他方法不同,当我们以这种方式获取BufferedReader时,我们负责关闭BufferedReader资源。
2.3. 使用InputStream读取
与withReader和newReader类似,Groovy 也提供了轻松使用InputStream的方法。尽管我们可以使用InputStream读取文本,Groovy 甚至为其添加了功能,但InputStream最常用于二进制数据。
让我们使用 withInputStream将InputStream传递给闭包并读取字节:
byte[] data = []
new File("src/main/resources/binaryExample.jpg").withInputStream { stream ->
data = stream.getBytes()
}
如果我们需要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()
与BufferedReader一样,我们需要在使用 newInputStream 时自行关闭InputStream资源,而在使用withInputStream时则不需要。
2.4. 阅读其他方式
让我们通过查看 Groovy 用于在一条语句中获取所有文件数据的几种方法来完成阅读主题。
如果我们希望文件的行在List中,我们可以将collect与传递给闭包的迭代器一起使用:
def actualList = new File('src/main/resources/ioInput.txt').collect {it}
要将文件的行放入Strings数组中,我们可以使用as String[]:
def actualArray = new File('src/main/resources/ioInput.txt') as String[]
对于短文件,我们可以使用text获取**String中的全部内容:
def actualString = new File('src/main/resources/ioInput.txt').text
在处理二进制文件时,有bytes方法:
def contents = new File('src/main/resources/binaryExample.jpg').bytes
3. 写文件
在我们开始写入文件 之前,让我们设置我们将要输出的文本:
def outputLines = [
'Line one of output example',
'Line two of output example',
'Line three of output example'
]
3.1. withWriter写
与读取文件一样,我们也可以轻松地从File对象中获取BufferedWriter。
让我们使用withWriter获取BufferedWriter并将其传递给闭包:
def outputFileName = 'src/main/resources/ioOutput.txt'
new File(outputFileName).withWriter { writer ->
outputLines.each { line ->
writer.writeLine line
}
}
如果发生异常,使用withReader将关闭资源。
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()
当我们使用newWriter时,我们负责刷新和关闭我们的BufferedWriter对象。
3.2. 使用输出流编写
如果我们正在写出二进制数据,我们可以使用withOutputStream或newOutputStream获得一个OutputStream。
让我们使用withOutputStream将一些字节写入文件:
byte[] outBytes = [44, 88, 22]
new File(outputFileName).withOutputStream { stream ->
stream.write(outBytes)
}
让我们用newOutputStream获取一个OutputStream对象并用它来写入一些字节:
byte[] outBytes = [44, 88, 22]
def os = new File(outputFileName).newOutputStream()
os.write(outBytes)
os.close()
与InputStream、BufferedReader和BufferedWriter类似,当我们使用newOutputStream时,我们负责自己关闭OutputStream。
3.3. 使用 « 运算符编写
由于将文本写入文件是如此普遍,因此*«运算符直接提供了此功能。 让我们使用«*运算符来编写一些简单的文本行:
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. 用字节写入二进制数据
我们在文章前面看到,我们只需访问byte字段即可从二进制文件中获取所有字节。
让我们以相同的方式写入二进制数据:
def outputFileName = 'src/main/resources/ioBinaryOutput.bin'
def outputFile = new File(outputFileName)
byte[] outBytes = [44, 88, 22]
outputFile.bytes = outBytes
4. 遍历文件树
**Groovy 还为我们提供了使用文件树的简单方法。**在本节中,我们将使用eachFile、eachDir及其变体以及traverse方法来实现。
4.1. 使用eachFile列出文件
让我们使用eachFile列出目录中的所有文件和目录:
new File('src/main/resources').eachFile { file ->
println file.name
}
处理文件时的另一个常见场景是需要根据文件名过滤文件。让我们使用eachFileMatch和正则表达式仅列出以“io”开头并以“.txt”结尾的文件:
new File('src/main/resources').eachFileMatch(~/io.*\.txt/) { file ->
println file.name
}
**eachFile和eachFileMatch方法只列出根目录的内容。**Groovy 还允许我们通过将FileType传递给方法来限制eachFile方法返回的内容。选项是ANY、FILES和DIRECTORIES。
让我们使用eachFileRecurse递归列出所有文件,并为其提供FileType的FILES:
new File('src/main').eachFileRecurse(FileType.FILES) { file ->
println "$file.parent $file.name"
}
如果我们为eachFile方法提供文件而不是目录的路径,则会引发IllegalArgumentException 。
**Groovy 还提供了eachDir方法来仅处理目录。**我们可以使用eachDir及其变体来完成与使用带有DIRECTORIES文件类型的eachFile相同的事情。
让我们使用eachFileRecurse递归地列出目录:
new File('src/main').eachFileRecurse(FileType.DIRECTORIES) { file ->
println "$file.parent $file.name"
}
现在,让我们对eachDirRecurse做同样的事情:
new File('src/main').eachDirRecurse { dir ->
println "$dir.parent $dir.name"
}
4.2. 使用 Traverse 列出文件
**对于更复杂的目录遍历 用例,我们可以使用traverse方法。**它的功能与eachFileRecurse类似,但提供了返回FileVisitResult对象以控制处理的能力。
让我们在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. 使用数据和对象
5.1. 序列化基元
在 Java 中,我们可以使用DataInputStream和DataOutputStream来序列化原始数据字段 。Groovy 也在这里添加了有用的扩展。
让我们设置一些原始数据:
String message = 'This is a serialized string'
int length = message.length()
boolean valid = true
现在,让我们使用withDataOutputStream将我们的数据序列化到一个文件中:
new File('src/main/resources/ioData.txt').withDataOutputStream { out ->
out.writeUTF(message)
out.writeInt(length)
out.writeBoolean(valid)
}
并使用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()
}
与其他 with* 方法类似,withDataOutputStream和withDataInputStream将流传递给闭包并确保其正确关闭。
5.2. 序列化对象
Groovy 还建立在 Java 的ObjectInputStream和ObjectOutputStream之上,允许我们轻松地序列化实现Serializable的对象。
让我们首先定义一个实现Serializable的类:
class Task implements Serializable {
String description
Date startDate
Date dueDate
int status
}
现在让我们创建一个可以序列化到文件的Task实例:
Task task = new Task(description:'Take out the trash', startDate:new Date(), status:0)
有了我们的Task对象,让我们使用withObjectOutputStream将它序列化为一个文件:
new File('src/main/resources/ioSerializedObject.txt').withObjectOutputStream { out ->
out.writeObject(task)
}
最后,让我们使用withObjectInputStream重新读取我们的Task:
Task taskRead
new File('src/main/resources/ioSerializedObject.txt').withObjectInputStream { is ->
taskRead = is.readObject()
}
我们使用的方法withObjectOutputStream和withObjectInputStream将流传递给闭包并适当地处理关闭资源,就像在其他 with* 方法中看到的一样。