Java 从一个长流创建一个流
我想根据Java 从一个长流创建一个流,java,java-8,java-stream,Java,Java 8,Java Stream,我想根据流的内容将单个流拆分为流的流。生成的流应包含原始流的部分数据 我的实际应用程序更复杂(它是对时间间隔列表中的日志行进行分组),但我的问题是如何处理流,因此这里我询问一个简化的示例 示例问题 我希望能够根据重复的相同数字将流拆分为流,只留下奇数流 例如,以下流包含: {1,1,1,2,2,3,6,7,7,1,1} 需要产生包含以下内容的流: {{1,1,1},{3},{7,7},{1,1} 通过使用过滤器开始(或结束)可以省去偶数: Stream<Integer> input
流
的内容将单个流
拆分为流
的流
。生成的流
应包含原始流的部分数据
我的实际应用程序更复杂(它是对时间间隔列表中的日志行进行分组),但我的问题是如何处理流,因此这里我询问一个简化的示例
示例问题
我希望能够根据重复的相同数字将流
拆分为流
,只留下奇数流
例如,以下流包含:
{1,1,1,2,2,3,6,7,7,1,1}
需要产生包含以下内容的流:
{{1,1,1},{3},{7,7},{1,1}
通过使用过滤器开始(或结束)可以省去偶数:
Stream<Integer> input = ...;
Straem<Stream<Integer>> output = input.filter(this::isOdd).someOtherOperation();
流输入=。。。;
stream output=input.filter(this::isOdd).someOtherOperation();
这是不希望的,因为这将意味着对每个输入值进行两次评估,这是可以接受的,但我更希望避免这种情况
解决办法的想法
我当前的解决方案是迭代流的内容,创建一个列表
,并将其转换为流
。但是,这意味着完整的结果将保留在内存中(这对于我的应用程序来说是不需要的)
我还认为我可以通过编写自己的从流中读取的迭代器来实现这一点,但我不确定这将如何工作
问题:
如何基于原始流
的内容将流
转换为流
的流
,而不首先将完整结果存储为列表
的列表 恐怕这是不可行的,至少在一个好的方面是不行的。即使您将元素映射到流中并减少它们,这些内部流也必须知道它们包含哪些元素,以便它们必须存储某些内容
最简单的解决方案是只使用groupingBy
,但它会将所有结果存储在地图中:
List<Integer> input = asList(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1);
Map<Integer, List<Integer>> grouped = input.stream().collect(groupingBy(i -> i));
Stream<Stream<Integer>> streamOfStreams = grouped.values().stream().map(list -> list.stream());
但是请注意,它具有时间复杂度O(n^2)
编辑:
此解决方案将只包含本地元素组。它只存储当前本地组
public static void main(String[] args) {
Stream<Integer> input = Stream.of(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1);
Iterator<Integer> iterator = input.iterator();
int first;
int second = iterator.next();
List<Integer> buffer = new ArrayList<>();
buffer.add(second);
do {
first = second;
second = iterator.next();
if (Objects.equals(first, second)) {
buffer.add(second);
} else {
doSomethingWithTheGroup(buffer);
buffer = new ArrayList<>(); // let GC remove the previous buffer
buffer.add(second);
}
} while (iterator.hasNext());
doSomethingWithTheGroup(buffer);
}
private static void doSomethingWithTheGroup(List<Integer> buffer) {
System.out.println(buffer);
}
private static boolean isOdd(Integer i) {
return (i & 1) == 1;
}
像@Jaroslaw一样,我也使用Map来保存不同的流。但是,映射将保存从输入构建的流,并且不预先收集,这是可行的。使用Stream.concat
和Stream.of
可以向流中添加一个元素:
Map<Integer, Stream<Integer>> streamMap = new HashMap<>();
int[] arr = {1,1,1,2,2,2,3,6,7,7,1,1};
Arrays.stream(arr)
.filter(this::isOdd)
.forEach(i -> {
Stream<Integer> st = streamMap.get(i);
if (st == null) st = Stream.of(i);
else st = Stream.concat(st, Stream.of(i));
streamMap.put(i, st);
});
streamMap.entrySet().stream().forEach(e -> {
System.out.print(e.getKey() + "={");
e.getValue().forEach(System.out::print);
System.out.println("}");
});
您可能希望实现自己的方法来实现这一点。库中已经有类似的东西(第一个链接重定向到质子包中实现的链接)
请注意,您将获得一个流
(您可以尝试修改实现以直接获得流
,但始终需要缓冲少量元素;这取决于窗口的大小;以测试是否应创建新窗口)。例如:
StreamUtils.aggregate(Stream.of(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1),
Objects::equals)
.forEach(System.out::println);
产出:
[1, 1, 1]
[2, 2, 2]
[3]
[6]
[7, 7]
[1, 1]
你可以用我的图书馆。它具有以下功能:
List<Integer> input = Arrays.asList(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1);
Stream<Stream<Integer>> streams = StreamEx.of(input).filter(this::isOdd)
.groupRuns(Integer::equals)
.map(List::stream);
输出:
1,1,1
3
7,7
1,1
与protonpack库类似,内部有一个自定义拆分器,但使用StreamEx可以利用并行处理(protonpack根本不拆分)
在顺序处理中,一次最多有一个中间列表驻留在内存中(其他列表符合GC条件)。如果您仍然担心内存消耗(例如,您有很长的组),则有另一种解决此任务的方法,因为StreamEx 0.3.3:
Stream<Stream<Integer>> streams = StreamEx.of(input).filter(this::isOdd)
.runLengths()
.mapKeyValue(StreamEx::constant);
Stream-streams=streamx.of(input).filter(this::isOdd)
.runLength()
.mapKeyValue(StreamX::常量);
该方法返回条目流,其中key是元素,value是相邻重复元素的数量。然后使用StreamEx.constant
,这是Stream.generate(()->value.limit(length)
的快捷方式。因此,即使对于很长的组,也会有恒定的中间内存消耗。当然,这个版本也是并行友好的
更新:StreamEx 0.3.3已发布,因此第二个解决方案现在也符合条件。这相当于按分组并将所有内容存储在列表中Stream.of
只存储您传入的值,这样您的映射将使用O(n)
内存,这正是@Thirler想要避免的。Stream.of
加载一个元素。使用我的解决方案,在forEach
中,您可以访问地图和所有流,因为它们被填充(=原始流被消耗)。使用您的解决方案,一旦原始流耗尽并且所有内容都已收集完毕,地图就可以访问,但一旦使用整个流,地图将包含该流中的所有元素。在提到我的解决方案之前,我建议您完全阅读我的答案,因为它包含两个解决方案……在我看来,您的第二个解决方案不回答OP,因为它不提供流。它提供了一次加载整个子流的选项。注意,这不会产生与我的示例相同的结果。在打印地图之前,它还会将完整结果存储在地图中。第一个forEach遍历完整的输入并存储结果,这与存储列表非常相似。同样,拥有列表流意味着这些包含所有元素的列表存储在内存中,这是不需要的。@JaroslawPawlak据我所知,每个列表都是在管道需要下一段数据时生成的(使用tryAdvance
方法)。完成后,currentSlide
列表将使用一个新的引用(currentSlide=new ArrayList();
)重新初始化,因此我想前一个将符合GC的条件,但老实说,我
List<Integer> input = Arrays.asList(1, 1, 1, 2, 2, 2, 3, 6, 7, 7, 1, 1);
Stream<Stream<Integer>> streams = StreamEx.of(input).filter(this::isOdd)
.groupRuns(Integer::equals)
.map(List::stream);
streams.map(s -> StreamEx.of(s).joining(",")).forEach(System.out::println);
1,1,1
3
7,7
1,1
Stream<Stream<Integer>> streams = StreamEx.of(input).filter(this::isOdd)
.runLengths()
.mapKeyValue(StreamEx::constant);