Groovy 提供了许多使用 I/O 的 辅助方法。虽然您可以在 Groovy 中使用标准 Java 代码来处理这些问题,但 Groovy 提供了更方便的方法来处理文件、流、阅读器……
特别是,您应该查看添加到的方法:
java.io.File
课程:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.htmljava.io.InputStream
课程:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.htmljava.io.OutputStream
课程: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.htmljava.io.Reader
课程:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.htmljava.io.Writer
课程:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.htmljava.nio.file.Path
课程:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html
以下部分重点介绍使用上述可用辅助方法的示例惯用构造,但并不意味着对所有可用方法进行完整描述。为此,请阅读GDK API。
1.读取文件
作为第一个示例,让我们看看如何在 Groovy 中打印文本文件的所有行:
new File(baseDir, 'haiku.txt').eachLine { line ->
println line
}
该eachLine
方法是Groovy 自动添加到File
类中的一个方法,有很多变体,例如如果你需要知道行号,你可以使用这个变体:
new File(baseDir, 'haiku.txt').eachLine { line, nb ->
println "Line $nb: $line"
}
如果由于某种原因在eachLine
主体中引发异常,该方法会确保资源已正确关闭。这适用于 Groovy 添加的所有 I/O 资源方法。
例如,在某些情况下,您会更喜欢使用Reader
.,但仍然可以从 Groovy 的自动资源管理中受益。在下一个示例中,即使发生异常,阅读器也会关闭:
def count = 0, MAXSIZE = 3
new File(baseDir,"haiku.txt").withReader { reader ->
while (reader.readLine()) {
if (++count > MAXSIZE) {
throw new RuntimeException('Haiku should only have 3 verses')
}
}
}
如果您需要将文本文件的行收集到列表中,您可以执行以下操作:
def list = new File(baseDir, 'haiku.txt').collect {it}
或者您甚至可以利用as
运算符将文件的内容放入行数组中:
def array = new File(baseDir, 'haiku.txt') as String[]
您需要将文件内容放入byte[]
中多少次,它需要多少代码?Groovy实际上非常简单:
byte[] contents = file.bytes
使用 I/O 不仅限于处理文件。事实上,很多操作都依赖于输入/输出流,因此 Groovy 为它们添加了很多支持方法,正如您在 文档中看到的那样。
例如,您可以很容易地从一个File
中获得一个InputStream
:
def is = new File(baseDir,'haiku.txt').newInputStream()
// do something ...
is.close()
但是您可以看到它需要您处理关闭输入流。在 Groovy 中,通常使用withInputStream可以为您解决这个问题:
new File(baseDir,'haiku.txt').withInputStream { stream ->
// do something ...
}
2.写入文件
当然,在某些情况下,您不想读取而是写入文件。一种选择是使用Writer
:
new File(baseDir,'haiku.txt').withWriter('utf-8') { writer ->
writer.writeLine 'Into the ancient pond'
writer.writeLine 'A frog jumps'
writer.writeLine 'Water’s sound!'
}
但是对于这样一个简单的示例,使用<<运算符就足够了:
new File(baseDir,'haiku.txt') << '''Into the ancient pond
A frog jumps
Water’s sound!'''
当然,我们并不总是处理文本内容,因此您可以使用Writer或直接写入字节,如下例所示:
file.bytes = [66,22,11]
当然你也可以直接处理输出流。例如,以下是创建输出流以写入文件的方法:
def os = new File(baseDir,'data.bin').newOutputStream()
// do something ...
os.close()
但是您可以看到它需要您处理关闭输出流。同样,withOutputStream在任何情况下,使用将处理异常并关闭流的习语通常是一个更好的主意:
new File(baseDir,'data.bin').withOutputStream { stream ->
// do something ...
}
3.遍历文件树
在脚本上下文中,遍历文件树以查找某些特定文件并对其进行处理是一项常见任务。Groovy 提供了多种方法来执行此操作。例如,您可以对目录的所有文件执行某些操作:
// 对目录中找到的每个文件执行关闭代码
dir.eachFile { file ->
println file.name
}
// 对与指定模式匹配的目录中的文件执行关闭代码
dir.eachFileMatch(~/.*\.txt/) { file ->
println file.name
}
通常,您必须处理更深层次的文件,在这种情况下,您可以使用eachFileRecurse:
// 递归地对目录中找到的每个文件或目录执行闭包代码
dir.eachFileRecurse { file ->
println file.name
}
// 仅在文件上执行闭包代码,但递归地执行
dir.eachFileRecurse(FileType.FILES) { file ->
println file.name
}
对于更复杂的遍历技术,您可以使用该traverse
方法,该方法需要您设置一个特殊标志来指示如何处理遍历:
dir.traverse { file ->
if (file.directory && file.name=='bin') {
// 如果当前文件是一个目录并且它的名字是bin,停止遍历
FileVisitResult.TERMINATE
} else {
// 否则打印文件名并继续
println file.name
FileVisitResult.CONTINUE
}
}
4.数据和对象
在 Java 中,分别使用java.io.DataOutputStream
和 java.io.DataInputStream
类来序列化和反序列化数据并不少见。Groovy 将使处理它们变得更加容易。例如,您可以将数据序列化为文件并使用以下代码对其进行反序列化:
boolean b = true
String message = 'Hello from Groovy'
// 序列化数据到文件中
file.withDataOutputStream { out ->
out.writeBoolean(b)
out.writeUTF(message)
}
// ...
// 然后再读取一编
file.withDataInputStream { input ->
assert input.readBoolean() == b
assert input.readUTF() == message
}
同样,如果您要序列化的数据实现了Serializable
接口,您可以继续使用对象输出流,如下所示:
Person p = new Person(name:'Bob', age:76)
// 序列化数据到文件中
file.withObjectOutputStream { out ->
out.writeObject(p)
}
// ...
// 然后再读取一编
file.withObjectInputStream { input ->
def p2 = input.readObject()
assert p2.name == p.name
assert p2.age == p.age
}
5.执行外部进程
上一节描述了在 Groovy 中处理文件、阅读器或流是多么容易。然而,在系统管理或 devops 等领域,通常需要与外部进程进行通信。
Groovy 提供了一种执行命令行进程的简单方法。只需将命令行编写为字符串并调用该execute()
方法。例如,在 nix 机器(或安装了适当 nix 命令的 Windows 机器)上,您可以执行以下命令:
// 在外部进程中执行ls命令
def process = "ls -l".execute()
// 使用命令的输出并检索文本
println "Found text ${process.text}"
该execute()
方法返回一个java.lang.Process
实例,该实例随后将允许处理输入/输出/错误流以及检查进程的退出值等。
例如,这里是与上面相同的命令,但我们现在将一次处理一行结果流:
// 在外部进程中执行ls命令
def process = "ls -l".execute()
// 对于进程的输入流的每一行
process.in.eachLine { line ->
// 打印
println line
}
值得注意的是,in
对应于命令标准输出的输入流。out
是指一个流,您可以在其中向流程发送数据(其标准输入)。
请记住,许多命令是 shell 内置的,需要特殊处理。因此,如果您想要 Windows 机器上目录中的文件列表并编写:
def process = "dir".execute()
println "${process.text}"
你会收到一个IOException
:Cannot run program “dir”: CreateProcess error=2, The system cannot find the file specified。
这是因为dir
它内置在 Windows shell ( cmd.exe
) 中,不能作为简单的可执行文件运行。相反,您将需要编写:
def process = "cmd /c dir".execute()
println "${process.text}"
此外,由于此功能当前使用 java.lang.Process实现,因此必须考虑该类的缺陷。特别是,这个类的 javadoc :
由于部分原生平台只为标准输入输出流提供有限的缓冲区大小,未能及时写入子进程的输入流或读取输出流可能导致子进程阻塞,甚至死锁
正因为如此,Groovy 提供了一些额外的帮助方法,使流程的流处理更容易。
以下是如何从您的进程中吞噬所有输出(包括错误流输出):
def p = "rm -f foo.tmp".execute([], tmpDir)
p.consumeProcessOutput()
p.waitFor()
consumeProcessOutput也有不同的版本,使用StringBuffer、InputStream、OutputStream等…请阅读 java.lang.Process 的 GDK API
此外,这些是一个pipeTo
命令(映射到|
允许重载),它允许将一个进程的输出流馈送到另一个进程的输入流。
以下是一些使用示例:
管道在行动
proc1 = 'ls'.execute()
proc2 = 'tr -d o'.execute()
proc3 = 'tr -d e'.execute()
proc4 = 'tr -d i'.execute()
proc1 | proc2 | proc3 | proc4
proc4.waitFor()
if (proc4.exitValue()) {
println proc4.err.text
} else {
println proc4.text
}
消费错误
def sout = new StringBuilder()
def serr = new StringBuilder()
proc2 = 'tr -d o'.execute()
proc3 = 'tr -d e'.execute()
proc4 = 'tr -d i'.execute()
proc4.consumeProcessOutput(sout, serr)
proc2 | proc3 | proc4
[proc2, proc3].each { it.consumeProcessErrorStream(serr) }
proc2.withWriter { writer ->
writer << 'testfile.groovy'
}
proc4.waitForOrKill(1000)
println "Standard output: $sout"
println "Standard error: $serr"