
Apache Groovy:轻松写入文件
在 Apache Groovy 中写入文件非常简单。本指南探讨了 Groovy 的内置方法,这些方法使文件处理简洁高效。(如果您尚未安装 Groovy,请阅读本系列的简介。)
几年前,我写了一篇关于使用 Groovy 读取和写入文件的文章。这是一个非常简单的演示,说明了 Groovy 在处理文本文件方面可以与 Java 有何不同——并且可以说更好。在我本系列的上一篇教程中,我重新审视了读取文件的主题。
这一次,我将更深入地探讨在 Groovy 中写入文件的主题。快速提醒:计算机文件可以分为两大类:文本文件和二进制文件。计算机文件有两种类型:文本文件(例如您可以使用简单编辑器读取的数字文档)和二进制文件(存储计算机可以理解但对我们来说显得杂乱无章的信息,例如图像或视频)。
Java(和 Groovy)根据类型以不同的方式处理文件:文本(可读)或二进制(图像、程序)。这会影响您处理文件内容的方式。
请记住,在 Java(和 Groovy)中,文本文件将数据存储为人类可读的字符。
在 Java SE API 文档中,Unicode 代码点 用于 U+0000 和 U+10FFFF 范围内的字符值,Unicode 代码单元 用于 16 位
char
值,这些值是 UTF-16 编码的代码单元(请参阅 Java 语言文档中 Character 类的官方定义)。
Java(和 Groovy)定义了一个类层次结构,从 java.io.Writer
(请参阅此文档)开始,用于写入字符流——即文本。Writer
是一个抽象类,它被继承并进一步由以下类开发:
BufferedWriter
CharArrayWriter
FilterWriter
OutputStreamWriter
PipedWriter
StringWriter
根据您要将字符流发送到何处,您可以获得这些各种 writer 的专门版本。在本例中,想要写入文本文件,我对 java.io.FileWriter
类(请参阅此文档)最感兴趣,它是 OutputStreamWriter
的子类。
FileWriter
定义了一个方便的构造函数 FileWriter(String fileName)
,可让您直接从文件名转到 writer,从而可以访问该字符流。
FileWriter
的问题在于它没有定义方便的 writeLine()
方法。但是,与 reader 的方法不同,Java 的 BufferedWriter
也没有!相反,Java 的 BufferedWriter
将使用 write()
写入文本与使用 newLine()
写入行尾分隔开来。
虽然这种差异不是世界末日,但它确实给代码增加了一些不必要的臃肿。Groovy 的 BufferedWriter
确实提供了一个 writeLine()
方法。因此,您可以将 FileWriter
包装在 BufferedWriter
中。
坚持主要使用 Java 式方法
1 if (args.length != 1) {
2 System.err.println "Usage: groovy Groovy20a.groovy output-file"
3 System.exit(0)
4}
5 def writer = new BufferedWriter(new FileWriter(args[0]))
6 writer.writeLine("Hello world")
7 writer.writeLine("how's the weather?")
8 writer.close()
第 1-4 行处理用法。
第 5 行定义了您需要的 reader,它是一个 BufferedWriter
实例,包装了一个 FileWriter
实例,该实例附加到命令行上作为第一个参数提供的文件名。
第 6-7 行将几行写入文件。
第 9 行关闭 writer。
让我们运行它
$ groovy Groovy20a.groovy /tmp/junk
$ cat /tmp/junk
Hello world
how's the weather?
$
这是我在早期的 Java 时代编写数据到文件时使用的方法。当 Java 1.7 出现时,它带来了 java.nio.file.Files
类,该类通过提供 newBufferedWriter()
工厂方法,消除了将 FileWriter
与 BufferedWriter
包装在一起的需要。我不能说我立即跳到这个方法,因为它一方面简化了,另一方面又增加了整个新类的复杂性。但是,当我越来越熟悉 Files
类时,我可以看到它将一大堆相关实用程序整合到一个地方,这使其值得学习。例如,Files
提供了 write()
方法,该方法将文件的 java.nio.file.Path
作为参数,通过消除 writer 变量、BufferedWriter()
和 FileWriter()
,接受迭代器,并最终执行自己的 close()
,从而显着简化了 Java 代码。让我们看一下
1 import java.nio.file.Files
2 import java.nio.file.Path
3 if (args.length != 1) {
4 System.err.println "Usage: groovy Groovy20b.groovy input-file"
5 System.exit(0)
6 }
7 def outLines = ["foo", "bar"]
8 Files.write(Path.of(args[0]), outLines)
事实证明,它也简化了 Groovy 代码。
运行它
$ groovy Groovy20b.groovy /tmp/junk
$ cat /tmp/junk
foo
bar
$
查看上面的第 8 行,您可以看到 write()
方法特别适用于您构建要在应用程序中写入的数据列表,然后在完成后写入数据的情况。例如,当您想读取包含某种详细信息的文件并累积,然后写出该类详细信息的摘要时,可能会发生这种情况。或者,当然,只是为了写出您的程序以某种方式累积的信息包,可能是通过用户界面。
在您可能同时进行读取和写入的情况下,Groovy File
类提供了一个 withWriter()
方法,该方法调用一个闭包,并将 BufferedWriter
实例传递给它。您可以使用此功能来管理围绕循环的文件 writer,该循环从文件中读取数据
1 if (args.length != 2) {
2 System.err.println "Usage: groovy Groovy20c.groovy input-file output-file"
3 System.exit(0)
4 }
5 new File(args[1]).withWriter { writer ->
6 new File(args[0]).eachLine { line -> writer.writeLine(line) }
7}
再次强调,第 1-4 行检查用法。
第 5 行假定参数 1 指定输出文件的名称。它使用 File
的 withWriter()
方法打开此文件,并将 BufferedWriter
的实例附加到该文件,最后将该 writer 传递给第 5-7 行中定义的闭包。
第 6 行假定参数 0 指定输入文件的名称。它使用 BufferedReader
的(隐藏)实例打开此文件,使用该 reader 循环遍历文件的行,使用 File
的 eachLine()
方法将读取的每一行传递给第 6 行中定义的闭包。闭包调用 writer 实例的 writeLine()
方法来写出 reader 读取的每一行。当输入文件中的所有行都被读取后,它会自动关闭。
第 7 行结束 writer 闭包,并导致 writer 被关闭。
您可以按如下方式运行它
$ groovy Groovy20c.groovy /etc/group _etc_group
$ head _etc_group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:syslog
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
news:x:9:
$
我在这里不打算介绍写入二进制文件,因为您需要知道如何处理二进制信息以对其进行结构化和解释。无论如何,我通常更喜欢尽可能使用文本文件。这是因为当文本文件用于在步骤之间进行通信时,更容易解耦处理步骤并查看中间结果。
我也不打算介绍写入其他来源(如 URL),尽管 Java 和 Groovy 提供了与上面描述的非常相似的功能来写入这些目标。
这里值得一提的是,java.nio
包定义了一个有趣的高性能 I/O 模型,值得在需要处理大量数据或需要非阻塞 I/O 的应用程序中进行探索(例如,与远程系统一起工作)。Groovy 就在那里提供帮助。
结论
虽然 Java 早期对 Writers 的采用似乎有点笨重,需要将 FileWriter 包装在 BufferedWriter 中,使用循环迭代文件中的行,并记住在最后关闭整个文件。但从那时起,Java 和 Groovy 都得到了改进。现在,写入文件既简短又直接,读写循环也是如此。
再次,您会看到 Groovy 的方法是通过向您已经知道的类(如 File)添加新行为来使其更有用。这比现代 Java 方法更简洁,现代 Java 方法是添加新的类层次结构,这些层次结构添加新行为,同时整合旧行为,从而带来更陡峭的学习曲线。
2 条评论
评论已关闭。