[英]Java 8 streams - incremental collection/partial reduce/intermittent map/…what is this even called?
我正在处理遵循该模式的潜在无限数据元素流:
E1 <start mark> E2 foo E3 bah ... En-1 bar En <end mark>
也就是说,<String>的流,在我将它们映射到对象模型之前必须在缓冲区中累积。
目标 :将Stream<String>
聚合到Stream<ObjectDefinedByStrings>
而无需收集无限流。
在英语中,代码类似于“一旦你看到一个开始标记,开始缓冲。缓冲直到你看到一个结束标记,然后准备好返回旧的缓冲区,并准备一个新的缓冲区。返回旧的缓冲区。”
我目前的实现形式如下:
Data<String>.stream()
.map(functionReturningAnOptionalPresentOnlyIfObjectIsComplete)
.filter(Optional::isPresent)
我有几个问题:
这个操作适当地叫什么? (即我可以通过Google获取更多示例吗?我发现的每个讨论.map()
都会讨论1:1的映射。每次讨论.reduce)都会讨论n:1的减少。 每次讨论.collect()
谈到累积作为终端操作......)
这在很多方面都很糟糕。 有没有更好的方法来实现这个? (形式的候选人.collectUntilConditionThenApplyFinisher(Collector,Condition,Finisher)
......?)
谢谢!
为了避免你的污染,你可以在映射之前过滤。
Data<String>.stream()
.filter(text -> canBeConvertedToObject(text))
.map(text -> convertToObject(text))
这在无限流上非常有效,只能构造需要构造的对象。 它还避免了创建不必要的Optional对象的开销。
不幸的是,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
,该groupRuns
应用于两个相邻的流元素,如果必须对这些元素进行分组,则返回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个用于此部分缩减的用例:
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解析器可用于解析大多数编程语言的语法。
解析器是具有堆栈的有限自动机。 在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那样无条件批处理批处理 )。
对于这种特定情况,可以使用StreamEx
部分减少操作,因为如果BiPredicate
测试的两个相邻元素都是DML语句,则它们应该被包括在批处理中。 因此,我们不需要知道以前的批次收集历史。
但是我们可以增加任务的复杂性,并说批量应该受到大小的限制。 比如说,一批中不超过100个DML语句。 在这种情况下,我们不能忽略以前的批次收集历史记录和使用BiPredicate
来确定是否应该继续或停止批次收集是不够的。
虽然我们可以在StreamEx
部分缩减后添加flatMap,将长批次拆分成部分。 但这会延迟特定的100元素批处理执行,直到所有DML语句都被收集到无限批处理中。 毋庸置疑,这是针对管道意识形态的:我们希望最大限度地减少缓冲,以最大化输入和输出之间的速度。 此外,在非常长的DML语句列表的情况下,无限制的批量收集可能导致OutOfMemoryError
,而在它们之间没有任何查询(例如,由于数据库导出而导致数百万的INSERT
),这是不可容忍的。
因此,对于具有上限的复杂条件批处理集合,我们还需要像先前用例中描述的LR(0)解析器一样强大的东西。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.