用Java實現Stream流處理中的滑窗
簡單地說,滑窗演算法是一種移動固定大小的視窗(子列表)來遍歷資料結構的方法,主要是基於固定步驟的序列流資料。
如果我們想透過使用大小為3的視窗遍歷列表[1 2 3 4 5],我們透過視窗只能看到以下資料組:
下面談的是如何在這個流上使用滑窗演算法。
為了能夠建立自定義Stream,我們需要實現自定義Spliterator。
在我們的例子中,我們需要能夠迭代Stream <T>序列資料,因此我們需要實現Spliterator介面並指定泛型型別引數:
有一堆方法需要實現:
我們還需要一些欄位來儲存緩衝元素、視窗大小引數、源集合的迭代器以及預先計算的大小估計(稍後我們將需要):
在我們開始實現介面方法之前,我們需要能夠例項化我們的工具。
在這種情況下,我們將限制建構函式的可見性,並公開一個公共靜態工廠方法:
公開的靜態方法:
現在讓我們實現Spliterator方法中容易的部分。
實現 trySplit()時,我們預設使用文件中指定的值。幸運的是,計算大小很容易:
在characteristics()中,我們指定:
ORDERED - 因為順序很重要
NONNULL - 因為元素永遠不會為null(儘管可以包含空值)
SIZED -因為大小是可以預見的
現在實現tryAdvance,這裡是關鍵部分 - 負責實際分組和迭代的方法。
首先,如果視窗小於1,則沒有任何內容可以迭代,以便我們可以立即返回:
現在,要生成第一個子列表,我們需要開始迭代並填充緩衝區:
填充緩衝區後,我們可以排程整個組,並從緩衝區中丟棄最舊的元素。
這裡有一個關鍵部分,可能會試圖將buffer.stream()傳遞給accept()方法,這是一個巨大的錯誤 - Streams惰性地繫結到底層集合,這意味著如果源更改,Stream也會更改。
為了避免這個問題並將我們的組與內部緩衝區表示分離,我們需要在建立每個Stream例項之前對緩衝區的當前狀態進行快照。我們將使用陣列支援Stream例項,以使它們儘可能輕量級。
由於Java不支援通用陣列,我們需要做一些醜陋的轉換:
...瞧,我們準備好使用它:
滑窗程式碼編製成功,執行結果如下:
如果我們想透過使用大小為3的視窗遍歷列表[1 2 3 4 5],我們透過視窗只能看到以下資料組:
[1 2 3]
[2 3 4]
[3 4 5]
.如果我們想要使用比集合大小更大的視窗遍歷相同的列表,我們甚至不會得到一個元素。
Java 10提供了一種Stream實現,支援順序和並行聚合操作的一系列元素:
int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight()) .sum(); <p class="indent"> |
下面談的是如何在這個流上使用滑窗演算法。
為了能夠建立自定義Stream,我們需要實現自定義Spliterator。
在我們的例子中,我們需要能夠迭代Stream <T>序列資料,因此我們需要實現Spliterator介面並指定泛型型別引數:
public class SlidingWindowSpliterator<T> implements Spliterator<Stream<T>> { // ... } <p class="indent"> |
有一堆方法需要實現:
public class SlidingWindowSpliterator<T> implements Spliterator<Stream<T>> { //下面會實現 @Override public boolean tryAdvance(Consumer<? super Stream<T>> action) { return false; } //準備下面實現 @Override public Spliterator<Stream<T>> trySplit() { return null; } @Override public long estimateSize() { return 0; } //下面準備實現 @Override public int characteristics() { return 0; } } <p class="indent"> |
我們還需要一些欄位來儲存緩衝元素、視窗大小引數、源集合的迭代器以及預先計算的大小估計(稍後我們將需要):
private final Queue<T> buffer; private final Iterator<T> sourceIterator; private final int windowSize; private final int size; <p class="indent"> |
在我們開始實現介面方法之前,我們需要能夠例項化我們的工具。
在這種情況下,我們將限制建構函式的可見性,並公開一個公共靜態工廠方法:
private SlidingWindowSpliterator(Collection<T> source, int windowSize) { this.buffer = new ArrayDeque<>(windowSize); this.sourceIterator = Objects.requireNonNull(source).iterator(); this.windowSize = windowSize; this.size = calculateSize(source, windowSize); } <p class="indent"> |
公開的靜態方法:
static <T> Stream<Stream<T>> windowed(Collection<T> stream, int windowSize) { return StreamSupport.stream( new SlidingWindowSpliterator<>(stream, windowSize), false); } <p class="indent"> |
現在讓我們實現Spliterator方法中容易的部分。
實現 trySplit()時,我們預設使用文件中指定的值。幸運的是,計算大小很容易:
private static int calculateSize(Collection<?> source, int windowSize) { return source.size() < windowSize ? 0 : source.size() - windowSize + 1; } @Override public Spliterator<Stream<T>> trySplit() { return null; } @Override public long estimateSize() { return size; } <p class="indent"> |
在characteristics()中,我們指定:
ORDERED - 因為順序很重要
NONNULL - 因為元素永遠不會為null(儘管可以包含空值)
SIZED -因為大小是可以預見的
@Override public int characteristics() { return ORDERED | NONNULL | SIZED; } <p class="indent"> |
現在實現tryAdvance,這裡是關鍵部分 - 負責實際分組和迭代的方法。
首先,如果視窗小於1,則沒有任何內容可以迭代,以便我們可以立即返回:
@Override public boolean tryAdvance(Consumer<? super Stream<T>> action) { if (windowSize < 1) { return false; } // ... } <p class="indent"> |
現在,要生成第一個子列表,我們需要開始迭代並填充緩衝區:
while (sourceIterator.hasNext()) { buffer.add(sourceIterator.next()); // ... } <p class="indent"> |
填充緩衝區後,我們可以排程整個組,並從緩衝區中丟棄最舊的元素。
這裡有一個關鍵部分,可能會試圖將buffer.stream()傳遞給accept()方法,這是一個巨大的錯誤 - Streams惰性地繫結到底層集合,這意味著如果源更改,Stream也會更改。
為了避免這個問題並將我們的組與內部緩衝區表示分離,我們需要在建立每個Stream例項之前對緩衝區的當前狀態進行快照。我們將使用陣列支援Stream例項,以使它們儘可能輕量級。
由於Java不支援通用陣列,我們需要做一些醜陋的轉換:
if (buffer.size() == windowSize) { action.accept(Arrays.stream((T[]) buffer.toArray(new Object[0]))); buffer.poll(); return sourceIterator.hasNext(); } <p class="indent"> |
...瞧,我們準備好使用它:
windowed(List.of(1,2,3,4,5), 3) .map(group -> group.collect(toList())) .forEach(System.out::println); <p class="indent"> |
滑窗程式碼編製成功,執行結果如下:
// result <p class="indent">[1, 2, 3] <p class="indent">[2, 3, 4] <p class="indent">[3, 4, 5] <p class="indent"> |
相關文章
- Java 8中處理集合的優雅姿勢——StreamJava
- Java中stream流的filter機制理解JavaFilter
- Java中的並行流處理與效能提升Java並行
- java處理流 和節點流(在位元組流和字元流中,又分為處理流和節點流)Java字元
- 翻譯 | Java流中如何處理異常Java
- java-Stream流Java
- Java Stream流使用Java
- java的Stream流學習Java
- Java中實現流的分割槽Java
- node中的流(stream)
- 自己實現一個滑動視窗
- 滑動視窗最大值的golang實現Golang
- Java的簡單理解(22)---處理流Java
- Java8——Stream流Java
- java 8 特性——stream流Java
- [Java基礎]Stream流Java
- 【重學Java】Stream流Java
- Java中Stream的teeing()方法用於處理合並兩個Collector - foojayJava
- Java中的異常處理最佳實踐Java
- Kafka如何實現實時流處理 Part 1 - André MeloKafka
- Flink處理函式實戰之四:視窗處理函式
- Java8 Stream,簡潔快速處理集合(上)Java
- Java8 Stream,簡潔快速處理集合(下)Java
- Java8 Stream流的合併Java
- Java 8 Stream並行流Java並行
- mysql視窗函式中的滑動視窗MySql函式
- Go語言實現的Java Stream APIGoJavaAPI
- java大資料處理:如何使用Java技術實現高效的大資料處理Java大資料
- 淺析nodejs中的stream(流)NodeJS
- 瞭解nodeJs中的流(stream)NodeJS
- node.js中的流(stream)Node.js
- Node.js Stream 流的使用及實現總結Node.js
- Nodejs 實踐 -- Stream 流NodeJS
- Golang的滑動視窗計數器Redis限速實現GolangRedis
- 影像處理的實現與應用(Elixir 版)
- 影像處理的實現與應用(TypeScript 版)TypeScript
- 影像處理的實現與應用(PHP 版)PHP
- 影像處理的實現與應用(Ruby 版)