我正在处理遵循该模式的潜在无限数据元素流:
E1 <start mark>
E2 foo
E3 bah
...
En-1 bar
En <end mark>
即&lt; String&gt;的流,在我将它们映射到对象模型之前必须在缓冲区中累积。
目标:将Stream<String>
聚合到Stream<ObjectDefinedByStrings>
中,而无需收集无限流的费用。
在英语中,代码类似于&#34;一旦看到开始标记,就开始缓冲。缓冲,直到您看到结束标记,然后准备好返回旧缓冲区,并准备一个新的缓冲区。返回旧缓冲区。&#34;
我目前的实施形式为:
Data<String>.stream()
.map(functionReturningAnOptionalPresentOnlyIfObjectIsComplete)
.filter(Optional::isPresent)
我有几个问题:
这个操作适当地叫什么? (即我可以通过Google获取更多示例?我发现.map()
的每次讨论都谈到1:1映射。每次讨论.reduce)都会谈到n:1减少。关于.collect()
的每次讨论都谈到了作为终端操作积累......)
这在很多方面都很糟糕。有没有更好的方法来实现这个? (.collectUntilConditionThenApplyFinisher(Collector,Condition,Finisher)
......的形式的候选人?)
谢谢!
答案 0 :(得分:2)
为了避免您的污染,您可以在映射之前进行过滤。
Data<String>.stream()
.filter(text -> canBeConvertedToObject(text))
.map(text -> convertToObject(text))
在无限流上运行良好,只构造需要构造的对象。它还避免了创建不必要的Optional对象的开销。
答案 1 :(得分:1)
不幸的是,Java 8 Stream API中没有部分减少操作。但是,此类操作在我的StreamEx库中实现,该库增强了标准Java 8 Streams。所以你的任务可以这样解决:
Stream<ObjectDefinedByStrings> result =
StreamEx.of(strings)
.groupRuns((a, b) -> !b.contains("<start mark>"))
.map(stringList -> constructObjectDefinedByStrings());
strings
是普通的Java-8流或其他来源,如数组Collection
,Spliterator
等。适用于无限或并行流。 groupRuns
方法采用BiPredicate
,它应用于两个相邻的流元素,如果必须对这些元素进行分组,则返回true。这里我们说元素应该被分组,除非第二个元素包含"<start mark>"
(这是新元素的开头)。之后,您将获得List<String>
元素流。
如果收集到中间列表不适合您,您可以使用collapse(BiPredicate, Collector)
方法并指定自定义收集器以执行部分缩减。例如,您可能希望将所有字符串连接在一起:
Stream<ObjectDefinedByStrings> result =
StreamEx.of(strings)
.collapse((a, b) -> !b.contains("<start mark>"), Collectors.joining())
.map(joinedString -> constructObjectDefinedByStrings());
答案 2 :(得分:0)
我提出了另外两个用例来进行部分缩减:
SQL语句的标准分隔符是分号(;
)。它将正常的SQL语句彼此分开。但是如果你有PL / SQL语句,则分号将语句中的运算符彼此分开,而不仅仅是语句。
解析包含普通SQL和PL / SQL语句的脚本文件的方法之一是首先用分号分割它们然后如果特定语句以特定关键字(DECLARE
,BEGIN
等开头。)按照PL / SQL语法规则将此语句与下一个语句结合起来。
顺便说一句,这不能通过使用StreamEx
部分减少操作来完成,因为它们只测试两个相邻的元素。因为您需要了解从初始PL / SQL关键字元素开始的先前流元素,以确定是否要将当前元素包括在部分缩减或部分缩减中。在这种情况下,可变部分缩减可用于收集器保存已收集元素的信息,并且一些Predicate
测试或者仅收集器本身(如果应该完成部分缩减)或者BiPredicate
测试收集器和当前流元素
理论上,我们正在谈论使用Stream管道意识形态实现LR(0)或LR(1)解析器(参见https://en.wikipedia.org/wiki/LR_parser)。 LR解析器可用于解析大多数编程语言的语法。
Parser是一个有限自动机堆栈。在LR(0)自动机的情况下,其转换仅取决于堆栈。在LR(1)自动机的情况下,它取决于堆栈和来自流的下一个元素(理论上可以有LR(2),LR(3)等自动偷看2,3等下一个元素来确定转换但是在实践中,所有编程语言都是语法上的LR(1)语言。)
要实现解析器,应该有一个Collector
包含有限自动机堆栈和谓词测试是否达到此自动机的最终状态(因此我们可以停止减少)。在LR(0)的情况下,它应该Predicate
测试Collector
本身。在LR(1)的情况下,它应该BiPredicate
测试流中的Collector
和下一个元素(因为转换依赖于堆栈和下一个符号)。
因此要实现LR(0)解析器,我们需要类似下面的内容(T
是流元素类型,A
是包含有限自动机堆栈和结果的累加器,R
是结果每个解析器工作形成输出流):
<R,A> Stream<R> Stream<T>.parse(
Collector<T,A,R> automataCollector,
Predicate<A> isFinalState)
(我删除了? super T
而不是T
的复杂性以获得紧凑性 - 结果API应包含这些内容)
要实现LR(1)解析器,我们需要以下内容:
<R,A> Stream<R> Stream<T>.parse(
BiPredicate<A, T> isFinalState
Collector<T,A,R> automataCollector)
注意:在这种情况下,BiPredicate
应该在之前测试元素,它将由累加器使用。记住LR(1)解析器正在查看下一个元素以确定转换。因此,如果空累加器拒绝接受下一个元素,则可能存在潜在的异常(BiPredicate
返回true,表示部分减少已经结束,在由Supplier
和下一个流元素创建的空累加器上。)
当我们执行SQL statemens时,我们希望将相邻的数据修改(DML)语句合并到一个批处理中(请参阅JDBC API)以提高整体性能。但我们不想批量查询。所以我们需要条件批处理(而不是像Java 8 Stream with batch processing中的无条件批处理。)
对于这种特定情况StreamEx
可以使用部分减少操作,因为如果BiPredicate
测试的两个相邻元素都是DML语句,则它们应该被包含在批处理中。因此,我们不需要知道以前的批次收集历史。
但我们可以增加任务的复杂性,并说批量应该受到大小的限制。比如说,一批中不超过100个DML语句。在这种情况下,我们不能忽略以前的批次收集历史记录,并使用BiPredicate
来确定是否应该继续或停止批次收集是不够的。
虽然我们可以在StreamEx
部分缩减后添加flatMap,将长批次分成几部分。但这会延迟特定的100元素批处理执行,直到所有DML语句都被收集到无限批处理中。毋庸置疑,这是针对管道意识形态的:我们希望最大限度地减少缓冲,以最大化输入和输出之间的速度。此外,如果DML语句列表很长而且两者之间没有任何查询(例如,由于数据库导出而导致的OutOfMemoryError
数百万),则无限制批量收集可能会导致INSERT
,这是不可容忍的。 / p>
因此,对于具有上限的复杂条件批处理集合,我们还需要像先前用例中描述的LR(0)解析器一样强大的功能。