
Apache Groovy: 强大的文本文件处理
本文深入探讨了使用 Groovy 读取文本文件,探索了其相对于 Java 的独特优势。我们将回顾文件类型,并深入研究 Groovy 处理从文本文件读取行的不同方式。
在 之前的 文章, 我复习了一些 Groovy 的基础知识。如果您想解锁 Groovy 的强大功能,本系列将指导您了解是什么使 Groovy 成为对开发人员如此有价值的语言。 (如果您尚未安装 Groovy,请 阅读本系列的介绍 系列。)
几年前,我写了一篇关于使用 Groovy 读取和写入文件的文章。这是一个非常简单的演示,说明 Groovy 在处理文本文件方面可以与 Java 有何不同,并且可以说,做得更好。
现在我将更深入地探讨在 Groovy 中读取文件的主题。但首先,这里有一些关于 Groovy(和 Java)对文件内容的看法的背景知识。
在您的 Linux 桌面电脑上,文件通常分为两类:包含人类可读文本的文件和包含二进制数据的文件。二进制数据对于人类来说很难直接理解。
我使用 LibreOffice Writer 编写了这篇文章,它以 ODF 文本 格式存储在我的电脑上。所以,文本格式,一定是你可以阅读和理解的东西,对吧?嗯,不是的。如果我使用 more 命令来显示文件,我可以看到它只是一条很长的行,开头是这样的
P��V^�2^L'mimetypeapplication/vnd.oasis.opendocument.textP��VConf
这没什么用。事实证明,ODF 文本格式是二进制数据,无法在终端中呈现。嗯。但是,如果您深入挖掘,您会发现 ODF 文本格式可以以压缩 zip 格式存储。您可以使用 unzip 命令在 .odt 文件上解压出它的所有组件,其中许多是文本文件。
当我解压这篇文章时,我得到了
unzip ../groo*19.odt
Archive: ../groovy-advent-19.odt
extracting: mimetype
creating: Configurations2/images/Bitmaps/
creating: Configurations2/accelerator/
creating: Configurations2/statusbar/
creating: Configurations2/menubar/
creating: Configurations2/popupmenu/
creating: Configurations2/floater/
creating: Configurations2/progressbar/
creating: Configurations2/toolbar/
creating: Configurations2/toolpanel/
inflating: manifest.rdf
inflating: meta.xml
inflating: settings.xml
extracting: Thumbnails/thumbnail.png
inflating: styles.xml
inflating: content.xml
inflating: META-INF/manifest.xml
我可以使用 more
命令查看文章文本,它以 XML 格式保存在 content.xml
中。它是可读的,并且确实有意义,因为我了解 XML 是关于什么的
$ more content.xml
<?xml version="1.0" encoding="UTF-8"?>
<office:document-content xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl=…
$
简而言之,您会看到 .odt 文件是一个二进制文件。您可以使用 unzip 实用程序通过将其转换为主要为文本的组件来帮助您理解此文件。但在更深层次上,所有文件都是二进制的。旨在供人类阅读的文件被结构化为文本。旨在供 unzip 或其他实用程序程序读取的文件通常被结构化为其他内容。

在 Java 中,因此在 Groovy 中,根据文件是否旨在被解释为文本或某种结构化二进制文件,有不同的处理文件的方式。
专注于文本路径,这种结构化的第一部分是认识到文件中的位旨在被读取为 Unicode 字符或 Unicode 代码点的实例
在 Java SE API 文档中,Unicode 代码点 用于 U+0000 和 U+10FFFF 范围内的字符值,而 Unicode 代码单元 用于 16 位
char
值,这些值是 UTF-16 编码的代码单元(请参阅 Java 语言文档中 Character 类的官方定义)。
Java,因此 Groovy,定义了一个以 java.io.Reader
开头的类层次结构(请参阅 此文档),这些类用于读取字符流,即文本。Reader
是一个抽象类,它被继承并进一步发展为
BufferedReader
CharArrayReader
FilterReader
InputStreamReader
PipedReader
StringReader
根据您要从哪里获取字符流,您可以获得这些各种读取器的专门版本。如果您想从文本文件读取,您应该关注 java.io.FileReader
类(请参阅 此文档),它是 InputStreamReader
的子类。
FileReader
定义了一个方便的构造函数 FileReader(String fileName)
,它允许您直接从文件名转到准备好让您访问该字符流的读取器。
FileReader
的问题在于它没有定义方便的 readLine()
方法。要将字符流视为具有面向行的结构,您必须自己注意行尾。这是危险的,因为行尾结构往往是操作系统相关的。这需要您缓冲字符流,然后搜索行尾。
FileReader 的另一个问题是您必须将 FileReader
包装在 BufferedReader
中。这提供了一个 readLine()
方法,以及一个 lines()
方法,该方法返回一个 java.util.Stream<String>
实例。
从接近 Java 的方式开始,这是它的样子
1 if (args.length != 1) {
2 System.err.println "Usage: groovy Groovy19a.groovy input-file"
3 System.exit(0)
4 }
5 def reader = new BufferedReader(new FileReader(args[0]))
6 def line
7 while ((line = reader.readLine()) != null) {
8 println "line = $line"
9 }
10 reader.close()
第一到第四行处理用法。
第五行定义了您需要的读取器,它是一个 BufferedReader
实例,包装了一个 FileReader
实例,该实例附加到命令行上作为第一个参数提供的文件名。
第六行定义了字符串行,您将在其中读取文件的每一行。
第七到第九行循环遍历文件的行,方法是在读取器上调用 readLine() 方法,并将返回的值放入 line 变量中,直到遇到文件末尾。文件末尾用 null 行表示。读取的每一行都会打印出来,前缀为字符串“line = “。
第十行关闭读取器。
让我们运行它,使用文本/面向行的文件 /etc/group
作为输入
$ groovy Groovy19a.groovy /etc/group
line = root:x:0:
line = daemon:x:1:
line = bin:x:2:
line = sys:x:3:
…
$
这曾经是我早期 Java 时代读取文件的首选方法。当 Java 1.7 出现时,它带来了 java.nio.file. Files
类,它消除了用 BufferedReader
包装 FileReader
的需要。它通过提供 newBufferedReader()
工厂方法来实现这一点。我不能说我立即接受了这一点,因为它通过增加一个全新的类的复杂性来简化了操作。但是,随着我对 Files
类越来越熟悉,我可以看到它将一大堆相关实用程序整合到一个地方,这使其值得学习。例如,Files
提供了 lines()
方法,该方法将文件的 java.nio.file.Path
作为参数。消除 reader
变量、BufferedReader()
和 FileReader()
大大简化了 Java 代码。while {}
命令、测试和 line
变量也被消除了。最后的润色是让它完成自己的 close()
。然而,本着给予的同时也拿走的精神,它也需要学习整个 Streams
的东西……无论如何,这是一个很好的实践
1 import java.nio.file.Files
2 import java.nio.file.Path
3 if (args.length != 1) {
4 System.err.println "Usage: groovy Groovy19b.groovy input-file"
5 System.exit(0)
6 }
7 Files.lines(Path.of(args[0])).each { line -> println "line = $line" }
事实证明,它也简化了 Groovy 代码。
运行它
$ groovy Groovy19b.groovy /etc/group
line = root:x:0:
line = daemon:x:1:
line = bin:x:2:
line = sys:x:3:
…
$
解决此问题的另一种方法是将 Groovy 增强功能应用于 File
类
1 if (args.length != 1) {
2 System.err.println "Usage: groovy Groovy19b.groovy input-file"
3 System.exit(0)
4 }
5 new File(args[0]).eachLine { line -> println "line = $line" }
这可能是我最喜欢的,因为它不涉及学习几个新的类层次结构,而且它既简洁又紧凑。
当您运行它时,您会看到
$ groovy Groovy19c.groovy /etc/group
line = root:x:0:
line = daemon:x:1:
line = bin:x:2:
line = sys:x:3:
…
Groovy File
类还具有一个 withReader()
方法,该方法调用一个闭包,并将 BufferedReader
实例传递给它。我发现这不如使用上面描述的 eachLine()
方法方便。但是,当我从一个文件读取并写入另一个文件时,我肯定会使用 withWriter()
方法,以便在我的 BufferedReader
处理闭包中可以使用 writer。
但我会留到下次再说。
我在这里不打算介绍读取二进制文件,因为您需要知道如何处理二进制信息才能对其进行结构化和解释。在任何情况下,我通常更喜欢尽可能使用文本文件。当文本文件用于在步骤之间进行通信时,解耦处理步骤和查看中间结果要容易得多。
结论
虽然 Java 早期对 Readers
的看法似乎有点笨拙,需要将 FileReader
包装在 BufferedReader
中,使用循环来迭代文件中的行,并记住在最后关闭整个东西,但情况已经有所改善。现在从文件读取可以是一个单行程序,而该行不是 1,000 个字符长。
再次,您会看到 Groovy 的方法 是通过向您已经知道的类(如 File
)添加新行为来使其更有用。将此与现代 Java 方法进行比较,后者是添加新的类层次结构,这些层次结构在整合旧行为的同时添加新行为,从而带来了更陡峭的学习曲线。