将数据源转换为流
此页面由 Venkat Subramaniam 在 UPL 下贡献以流的方式思考
在本 教程系列 的前几篇文章中,我们研究了如何将以命令式风格编写的循环转换为函数式风格。在本文中,我们将研究如何从函数式角度看待数据源,将其视为数据流,并将迭代转换为使用 Streams API。
我们已经了解了如何使用 filter()
和 map()
函数分别选择和转换数据。我们可以在函数式管道中间执行这些操作。在前面文章中的示例中,我们使用了 range()
和 rangeClosed()
等函数来创建一定范围内的数字流。当我们想要遍历已知范围内的值时,这非常有效,但是,我们经常需要处理来自外部资源的数据,例如来自文件。如果我们能够将外部资源视为流,那么就可以轻松地应用函数式管道的操作。在本文中,我们将通过一个示例来说明这个想法。
从命令式风格到函数式风格
假设我们想要遍历一个文件,并统计包含一个或多个特定单词的行数。以下是一个非常常见的命令式风格代码,用于完成此任务
//Sample.java
import java.nio.file.*;
public class Sample {
public static void main(String[] args) {
try {
final var filePath = "./Sample.java";
final var wordOfInterest = "public";
try (var reader = Files.newBufferedReader(Path.of(filePath))) {
String line = "";
long count = 0;
while((line = reader.readLine()) != null) {
if(line.contains(wordOfInterest)) {
count++;
}
}
System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest));
}
} catch(Exception ex) {
System.out.println("ERROR: " + ex.getMessage());
}
}
}
为了便于处理此示例,我们将在与代码相同的源文件中查找包含单词“public”的行数。您可以根据需要更改 filePath
的值以指向不同的文件,或更改 wordOfInterest
的值以指向其他单词。
此示例主要包含两个部分。我们使用 newBufferedReader()
方法返回的 BufferedReader
来访问我们要查看的文件的内容。然后,在 while
循环中,我们检查每一行是否包含所需的单词,如果包含,则将 count
加 1,以指示我们找到了另一行包含该单词的行。让我们分别检查这两个部分,先从第二个部分开始。
仔细观察循环,从我们之前文章中的讨论中,我们可以认识到 if
的存在表明,如果我们可以将代码编写为函数式管道,那么我们可以使用 filter()
操作。一旦我们过滤掉或选择包含所需单词的行,就可以使用流的 count()
方法来统计行数。您可能非常好奇,迫不及待地想问:“但是,流在哪里?”为了回答这个问题,让我们看一下代码的第一部分。
数据,即文本行,来自路径存储在变量 filePath
中的文件。我们使用 BufferedReader
的 readLine()
方法和命令式风格来遍历每一行文本。为了使用函数式管道,以及 filter()
等操作,我们需要一个数据 Stream
。因此问题是,“是否可以为文件中的内容获取数据流?”
答案是肯定的。JDK 和 Java 语言的开发人员不仅引入了进行函数式编程的功能,还说“祝你好运”。他们煞费苦心地增强了 JDK,添加了函数,以便我们作为程序员能够充分利用 Java 的函数式功能来完成日常任务。
将文件内容转换为数据流的一种简单方法是使用 java.nio.file
包中 Files
类提供的 lines()
方法。让我们使用 lines()
方法(它为我们提供了文件内容的 Stream
)将之前的命令式风格代码重构为函数式风格,如下所示
//Sample.java
import java.nio.file.*;
public class Sample {
public static void main(String[] args) {
try {
final var filePath = "./Sample.java";
final var wordOfInterest = "public";
try(var stream = Files.lines(Path.of(filePath))) {
long count = stream.filter(line -> line.contains(wordOfInterest))
.count();
System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest));
}
} catch(Exception ex) {
System.out.println("ERROR: " + ex.getMessage());
}
}
}
lines()
方法不仅提供了文件内容的数据流,还消除了读取行周围的大量冗余代码。与我们一次获取一行文本的外部迭代器不同,流使我们能够使用内部迭代器,从而可以专注于对流管道中出现的每一行文本执行的操作。
映射
无论何时处理来自外部资源的数据集合,都要询问是否有方法获取该资源内容的数据流。您很有可能在 JDK 或第三方库中找到相应的函数。一旦我们获得了流,就可以使用 filter()
、map()
等高效的函数式运算符来流畅地遍历资源中包含的数据集合。
上次更新: 2024 年 4 月 23 日