Java中实现流的分区

Java Streams 是处理集合的强大抽象。通常,我们需要将流分成更小的块以进行进一步的操作。本文探讨了基于固定最大大小对 Java 8 Stream 进行分区的各种技术。

1. 用列表分区
处理 List 时,使用 Java 8 Streams 基于固定最大大小进行分区可以相对简单。它的工作原理如下:

  • 计算分区数量:根据列表的大小和所需的分区大小确定所需的分区数量。
  • 创建分区:迭代分区索引范围并提取原始列表的子列表。
  • 收集分区:将子列表收集到列表列表中。

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ListPartitioning {

    public static <T> List<List<T>> partitionList(List<T> list, int partitionSize) {
        return IntStream.range(0, (list.size() + partitionSize - 1) / partitionSize)
                .mapToObj(i -> list.subList(i * partitionSize, Math.min(list.size(), (i + 1) * partitionSize)))
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        List<Integer> numbers = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());
        List<List<Integer>> partitions = partitionList(numbers, 5);
        partitions.forEach(System.out::println);
    }
}

要测试此方法:

  • 我们使用IntStream.range创建表示分区索引的整数流。
  • 对于每个索引,它使用 提取原始列表的子列表list.subList。
  • 然后使用该collect方法将子列表收集到列表列表中。

2.使用 Guava 分割数据流
使用 Guava 可以高效地在 Java 中对流进行分区。我们可以利用 Guava 的 Lists.partition 方法将流分割成固定大小的块。首先,请确保已将 Guava 库作为依赖项添加到项目中。

导入 Guava
如果您使用的是 Maven,可以通过在 pom.xml 文件中添加以下依赖关系来包含 Guava:

<dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>33.0.0-jre</version> <!-- Replace with the latest version -->
        </dependency>
    </dependencies>

Guava 的 Lists.partition 方法允许我们将列表分割成固定大小的子列表。该方法可直接应用于流,以实现流分割。
build.gradle:
implementation 'com.google.guava:guava:33.0.0-jre' // Replace with the latest version

import com.google.common.collect.Lists;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
 
public class GuavaPartitioning {
 
    public static void main(String[] args) {
        List<Integer> numbers = IntStream.rangeClosed(1, 10)
                                         .boxed()
                                         .collect(Collectors.toList());
 
        //定义分区大小
        int partitionSize = 3;
 
       
//使用 Guava 对数据流进行分区
        List<List<Integer>> partitions = Lists.partition(numbers, partitionSize);
 
       
// Print each partition
        partitions.forEach(System.out::println);
    }
     
}

这段代码的输出显示了分割后的子列表,每个子列表根据分割大小包含指定数量的元素。在这个例子中,我们将一个从 1 到 10 的整数列表进行了分区,分区大小为 3,输出结果如下:

[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]

3. 使用 Guava 的 Iterables.partition 对流进行分区
在 Java 中使用 Guava 的 Iterables.partition 方法对流进行分区,也是将流或序列分区/划分为固定大小块的一种高效、便捷的方法。下面是我们如何使用它:

import com.google.common.collect.Iterables;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
 
public class GuavaPartitionExample {
 
    public static void main(String[] args) {
        List<Integer> numbers = IntStream.rangeClosed(1, 10)
                                         .boxed()
                                         .collect(Collectors.toList());
 
        // Define the partition size
        int partitionSize = 3;
 
       
// 使用 Iterables.partition 对数据流进行分区
        Iterable<List<Integer>> partitions = Iterables.partition(numbers, partitionSize);
 
       
// Print each partition
        partitions.forEach(System.out::println);
    }
}

这种方法与上述使用 List.partition 方法的方法类似。唯一的区别在于它们如何处理输入数据结构和由此产生的分区输出。在本例中

  • 我们使用 Iterables.partition(numbers, partitionSize) 将数字列表分割成大小为 partitionSize 的子列表。
  • 结果是一个 Iterable<List<Integer>> 包含原始列表的分区。

使用 Guava 对流进行分区的要点

  • 简单:Guava 的 Lists.partition 和 Iterables.partition 方法简化了将流划分为固定大小块的任务。
  • 灵活性:这种方法用途广泛,能很好地处理各种类型的数据和分区大小。
  • 可读性:通过利用 Guava 实用程序,代码的可读性和简洁性均优于手动分区实现。
  • 性能:Guava 经过精心优化,即使在处理大数据流时也能高效处理分区。

Iterables.partition 与 List.partition 之间的区别
使用 Guava 的 Iterables.partition 和 List.partition 之间的区别在于它们如何处理输入数据结构和分区输出结果。

Iterables.partition

  • 输入:Iterables.partition 可对任何 Iterable 类型进行操作,而不仅限于 List。这意味着它可以处理任何实现 Iterable 接口的集合,如 List、Set、Queue 等。
  • 输出:Iterables.partition 的输出是一个 Iterable<List<T>>,其中每个 List<T> 代表原始序列的一个分区。这适用于懒散地分割和处理大型或无限序列,而无需将整个数据加载到内存中。
  • 使用方法:Iterables.partition 用途更广,可应用于更多数据结构,因此适用于需要处理不同类型集合或流的场景。

List.partition

  • 输入List.partition 专用于 List 集合。它将 list 分割成固定大小的子 list。
  • 输出List.partition 的输出是 List<List<T>>,其中每个 List<T> 代表原始列表的一个分区。当你有一个 List 并想将它分割成较小的块时,通常需要随机访问每个分区内的元素,这种方法就非常有用。
  • 使用方法:当你有一个 List,并希望根据固定的分区大小将其划分为更小的列表时,List.partition 是理想的选择。对于只处理 List 集合的用例,List.partition 非常简单。

在两者之间做出选择

  • 当您需要对任何 Iterable 序列(不限于 List)进行懒散分区或处理各种类型的集合时,请使用 Iterables.partition。
  • 如果您特别拥有一个 List 并希望将其划分为更小的子列表以便随机访问,或者您只喜欢处理 List 集合,请使用 List.partition。