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 = 3new 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 pondA frog jumpsWater’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.nameFileVisitResult.CONTINUE}}
4.数据和对象
在 Java 中,分别使用java.io.DataOutputStream和 java.io.DataInputStream类来序列化和反序列化数据并不少见。Groovy 将使处理它们变得更加容易。例如,您可以将数据序列化为文件并使用以下代码对其进行反序列化:
boolean b = trueString message = 'Hello from Groovy'// 序列化数据到文件中file.withDataOutputStream { out ->out.writeBoolean(b)out.writeUTF(message)}// ...// 然后再读取一编file.withDataInputStream { input ->assert input.readBoolean() == bassert 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.nameassert 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 | proc4proc4.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"
