1. 处理 IO 操作
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
下面,使用上面提供的辅助函数来介绍一些简单而又符合 Groovy 语言习惯的构建范例,更多的有关方法可参看 GDK API 。
1.1 读取文件
作为第一个范例,我们来看一看如何将文本文件的所有行都打印出来:
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 的自动资源管理。在下面这个例子中,即使发生异常,Reader 也会关闭。
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()
// 省略的逻辑语句......
is.close()
但是,从上面的代码我们也可以看到,你还需要手动关闭这个输入流的。在 Groovy 中,一般最好使用 withInputStream
习语,它可以替你处理这个关闭操作:
new File(baseDir,'haiku.txt').withInputStream { stream ->
// 省略的逻辑语句......
}
1.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()
// 省略的逻辑语句......
os.close()
但我们再次看到,需要手动编写关闭输出流的语句,与输入流的情况相同,一般在 Groovy 中最好使用 withOutputStream
习语,因为它既可以自动处理异常,也能在任何情况下关闭输出流。
new File(baseDir,'data.bin').withOutputStream { stream ->
// 省略的逻辑语句......
}
1.3 遍历文件树
在编写上下文脚本时,常见的一个任务就是遍历文件树查找某些特定文件,然后对它们进行一定的处理。Groovy 为此提供了多种方法。例如,你可以对某个目录的所有文件执行一些操作:
dir.eachFile { file -> // 1⃣️
println file.name
}
dir.eachFileMatch(~/.*\.txt/) { file -> // 2⃣️
println file.name
}
1⃣️ 对目录中已找到的所有文件执行闭包代码。
2⃣️ 对匹配特定模式的目录中的文件执行闭包代码。
另外,我们往往必须处理较深层次的文件,这种情况下可以使用 eachFileRecurse
:
dir.eachFileRecurse { file -> // 1⃣️
println file.name
}
dir.eachFileRecurse(FileType.FILES) { file -> // 2⃣️
println file.name
}
1⃣️ 对目录中已找到的所有文件或目录递归地执行闭包代码。
2⃣️ 只在文件上执行闭包代码,但并不递归。
要想使用更复杂的遍历技术,你可以使用 traverse
方法,它需要你设置特定的标志来确定遍历的行为:
dir.traverse { file ->
if (file.directory && file.name=='bin') {
FileVisitResult.TERMINATE // 1⃣️
} else {
println file.name
FileVisitResult.CONTINUE // 2⃣️
}
}
1⃣️ 如果当前文件是目录或者它的名称为 bin
,则停止遍历。
2⃣️ 如果不满足 1⃣️ 条件,则打印文件名并继续遍历。
1.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
}
1.5 执行外部进程
前面几节介绍了在 Groovy 中处理文件、Reader 或流的便利性,但在系统管理或开发运维领域中,我们经常还需要与外部进程进行通信。
Groovy 执行命令行进程的方式非常简单,只需把命令行写成字符串的形式,然后调用 execute()
方法即可。比如,在 nix 系统的机器上(或者是 Windows 机器上安装了合适的 nix 命令),可以执行类似下面的命令:
def process = "ls -l".execute() // 1⃣️
println "Found text ${process.text}" // 2⃣️
1⃣️ 在外部进程中执行 ls
命令。
2⃣️ 利用命令输出获取文本。
execute()
方法返回一个 java.lang.Process
实例,该实例随后能允许执行 in/out/err 流,检查处理得到的结束值,等等。
比如,下例中的命令与上例相同,但这次我们每次只处理一行结果流:
def process = "ls -l".execute() // 1⃣️
process.in.eachLine { line -> // 2⃣️
println line // 3⃣️
}
1⃣️ 在外部进程中执行 ls
命令。
2⃣️ 对于进程的输入流的每一行......
3⃣️ 打印改行。
上例中,值得我们注意的是, 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”:CreateProcess error=2,系统无法找到指定目录。)
这是因为 dir
是 Windows shell( cmd.exe
)的内建命令,无法以一个简单的可执行文件的形式运行。你需要这样写:
def process = "cmd /c dir".execute()
println "${process.text}"
另外,由于该功能当前其实秘密使用了 java.lang.Process
这个类,所以就必须要提防该类的一些不足和缺点。特别要注意的是,在 java 文档中对该类有如下这番说明:
> 由于一些原生平台上所提供的输出和输入流的缓冲区十分有限,所以如果未能及时地写入子进程的输入流或读取子进程的输出流,可能会导致子进程的阻塞甚至死锁。
正是由于这个特点,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"
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论