Groovy开发套件 第一部分
1 I/O 的使用
Groovy提供了丰富的方法来操作IO流。当然你也可以使用标准的Java代码来进行这些操作。但是Groovy提供了更多方便的方式来操作文件,流…
你可以先看看下面列举的一些方法:
- the io.File class : http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html
- the io.InputStream class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html
- the io.OutputStream class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.html
- the io.Reader class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.html
- the io.Writer class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.html
- the nio.file.Path class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html
下面的一些小节将提供一些示例来演示如何使用这些类,如果你想查看所有方法的详细用法,请阅读GDK的接口文档。
1.1 读文件
作为开篇的第一个示例,我们来看看如何使用Groovy来读文件并且打印读到的所有行:
new File(baseDir, 'haiku.txt').eachLine { line ->
println line
}
Groovy的eachLine方法是File类自动加载并且可有有许多的变体,比如如果你想知道行号,你可以使用以下的变体:
new File(baseDir, 'haiku.txt').eachLine { line, nb ->
println "Line $nb: $line"
}
在eachLine方法体里,抛出任何异常后该方法都可以确保正常将文件流关闭。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')
}
}
}
如果你想将一个文本文件中的所有行放到一个list里,你可以这样写:
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
使用IO并不仅仅是操作文件,事实上,更多的时候你需要操作输入/输出流,这就是为什么Groovy提供了非常丰富的方法来实现这一需求,你可以参见这个文档:InputStream
举个例子,你可以非常容易地从一个文件中获取一个输入流:
def is = new File(baseDir,'haiku.txt').newInputStream()
// do something ...
is.close()
但是这个方式需要你手动关闭输入流,事实上Groovy还提供了一种更加通用和快捷的方式,那就是使用withInputStream来操作:
new File(baseDir,'haiku.txt').withInputStream { stream ->
// do something ...
}
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()
// do something ...
os.close()
但是这个例子要求你手动关闭输出流。一种更好的做法是使用withOutputStream,任何时候只要抛出了异常它都会关闭流:
new File(baseDir,'data.bin').withOutputStream { stream ->
// do something ...
}
1.3 遍历文件树
在脚本上下文里,一种很常见的场景是遍历文件树来找到特定的文件进行特定的处理。Groovy提供了多种方法来做这个事情。比如说可以操作某个目录下的全部文件:
dir.eachFile { file ->
println file.name
} //(1)
dir.eachFileMatch(~/.*\.txt/) { file ->
println file.name
} //(2)
- 列举给定目录下的每个文件
- 在给定目录下查找匹配格式的文件
你经常需要处理更深层次的目录,可以使用 eachFileRecurse:
dir.eachFileRecurse { file ->
println file.name
} //(1)
dir.eachFileRecurse(FileType.FILES) { file ->
println file.name
} //(2)
- 递归列举所有文件和目录
- 仅仅递归列举文件
需要更加复杂的遍历技术,可以使用 traverse方法,需要你设置一个特定的递归标识来终止递归:
dir.traverse { file ->
if (file.directory && file.name=='bin') {
FileVisitResult.TERMINATE //(1)
} else {
println file.name
FileVisitResult.CONTINUE //(2)
}
}
- 如果当前文件是一个目录并且名字是bin,停止遍历
- 打印文件名并继续
1.4 数据和对象
在Java里,使用java.io.DataOutputStream 和 java.io.DataInputStream类来序列化和反序列化数据是非常常见的。Groovy里,这步操作将变得更加容易,比如,你可以序列化数据到一个文件然后使用下面的代码反序列:
boolean b = true
String message = 'Hello from Groovy'
// Serialize data into a file
file.withDataOutputStream { out ->
out.writeBoolean(b)
out.writeUTF(message)
}
// ...
// Then read it back
file.withDataInputStream { input ->
assert input.readBoolean() == b
assert input.readUTF() == message
}
类似地,如果你想要序列化的数据实现了Serializable接口,你可以使用一个对象输出流来处理,如下面的示例所示:
Person p = new Person(name:'Bob', age:76)
// Serialize data into a file
file.withObjectOutputStream { out ->
out.writeObject(p)
}
// ...
// Then read it back
file.withObjectInputStream { input ->
def p2 = input.readObject()
assert p2.name == p.name
assert p2.age == p.age
}
1.5 执行外部进程
前面的章节描述了使用Groovy来处理文件,Readers或流是一件很简单的是。但是在一些领域向系统管理员或开发经常需要和外部进程进行交互。
Groovy提供了一种简单的方式来执行命令行进程。仅仅需要将命令行写成字符串然后调用execute方法。举个例子,子啊一个*nix机器上(或者一台安装了*nix命令执行环境的windows机器上)你可以执行下面的代码:
def process = "ls -l".execute() (1)
println "Found text ${process.text}" (2)
- 在外部进程执行ls命令
- 处理输出并且返回文本
Execute方法返回一个java.lang.Process实例,可以使用in/out/err流来处理,通过返回值可以查看处理情况。
eg:这里有一个和上面命令类似但是现在是每次处理一个结果流:
def process = "ls -l".execute() (1)
process.in.eachLine { line -> (2)
println line (3)
}
- 在外部进程执行ls命令
- 对每个输入流进行处理
- 打印line的内容
in 相当于标准输出命令中的输入流, out指代你发送到进程(标准输入流)中的数据的流。
记住,对于内置的shell命令需要有特殊的处理,因此如果你想在一台windows机器上列一个某个目录的所有文件可以这样写:
def process = "dir".execute()
println "${process.text}"
当出现 Cannot run program “dir”: CreateProcess error=2, The system cannot find the file specified. 时你将收到一个IOException
这是因为dir命令是windows shell(cmd.exe)内置的命令。不能仅仅是执行dir,你应该这样写:
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等等,完整的例子可以参考GDK API for java.lang.Process
除此之外,也有一个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"