Java-将一个大列表均分成多个小列表,每个小列表包含10个元素

要将一个大列表均分成多个小列表,每个小列表包含10个元素,可以使用多种方法。以下是几种常

见的方法:

方法一:使用 subList

这是你已经提到的方法,通过 subList 来获取子列表。

import java.util.ArrayList;
import java.util.List;

public class BatchProcessingExample {
    public static void main(String[] args) {
        List<Integer> largeList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            largeList.add(i);
        }

        int batchSize = 10;
        for (int i = 0; i < largeList.size(); i += batchSize) {
            int end = Math.min(i + batchSize, largeList.size());
            List<Integer> smallList = largeList.subList(i, end);
            processBatch(smallList);
        }
    }

    private static void processBatch(List<Integer> batch) {
    
<think>我们需要将Set集合拆分成多个固定小的子集合(也称为分页或分块)。由于Set是无序的,但通常我们关心的是元素本身,所以我们可以将Set转换为List来保持顺序(或者使用LinkedHashSet保持插入顺序),然后进行分块。 注意:Set本身没有顺序,但为了分块,我们可以先将其转换为List(这样就有了一个确定的顺序),然后按索引分块。 但是,如果原始Set非常,转换为List可能会占用较多内存,我们可以使用流式处理并利用迭代器来避免一次性加载所有数据(如果数据量极),但这里我们假设数据量在可接受范围内。 我们将使用Java Stream API来实现,但Stream API本身没有内置的分块操作,所以我们需要自己实现一个收集器,或者使用其他方法。 方法一:使用Guava库的Lists.partition(简单,但需要第三方库) 方法二:使用Java Stream API自定义收集器(纯Java) 这里我们提供两种纯Java实现方式: 1. 使用循环和subList(适用于List) 2. 使用Stream API自定义收集器(支持并行流) 由于问题要求使用Stream,我们将重点放在自定义收集器上。但首先,我们需要将Set转换为List(因为Set没有索引,无法直接分块,除非使用迭代器)。 步骤: a. 将Set转换为List(保持一个顺序,比如插入顺序,使用LinkedHashSet,或者自然顺序,使用ArrayList) b. 然后分块 但如果我们不想先转换为List,也可以直接使用迭代器。不过使用Stream时,我们通常希望利用流的特点。 这里我们提供两种方式: 方式1:基于列表的subList(简单,但不支持并行) 方式2:自定义收集器(支持并行,但复杂一些) 由于问题要求使用Stream,我们主要展示自定义收集器。 但是,注意:分块操作本质上是顺序的,因为每个元素的位置决定了它属于哪个块。并行处理分块可能比较困难,因为需要知道元素的总数和位置。所以,我们通常使用顺序流。 以下是一个使用Java 8 Stream API将集合拆分成固定小子集的方法: 方法1:使用循环(非Stream,简单直接) 方法2:使用Stream的limit和skip(效率较低,因为skip操作在顺序流中可能效率不高,特别是当块很时) 方法3:自定义收集器(高效且优雅) 这里我们展示方法3:自定义收集器。 自定义收集器实现: 我们将创建一个收集器,它将元素累积到一个列表中,当列表达到指定小时,就将其添加到结果中,并开始新的列表。 注意:最后一个块可能小于指定小。 代码实现: ```java import java.util.*; import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Stream; public class PartitionSetExample { public static void main(String[] args) { Set<Integer> originalSet = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); int chunkSize = 3; // 将Set转换为流,然后使用自定义收集器分块 List<List<Integer>> partitions = originalSet.stream() .collect(partitionToFixedSize(chunkSize)); System.out.println(partitions); } public static <T> Collector<T, ?, List<List<T>>> partitionToFixedSize(int chunkSize) { // 参数校验 if (chunkSize <= 0) { throw new IllegalArgumentException("chunkSize must be positive"); } // 供应器:初始化一个临时列表用于累积当前块,以及一个结果列表用于存储所有块 Supplier<List<List<T>>> supplier = () -> new ArrayList<List<T>>() { // 使用一个临时列表来累积当前块 private List<T> currentChunk = new ArrayList<>(chunkSize); // 重写add方法,这样当添加一个元素时,我们检查当前块是否已满 // 但注意:我们不会直接添加元素到这个结果列表,而是通过accumulator // 所以这里我们不需要重写add,而是使用一个独立的累加器 // 但是,在自定义收集器中,我们使用独立的累加器,所以这里我们不需要重写add。 }; // 累加器:将每个元素添加到当前块,如果当前块满了,就将其添加到结果并新建一个块 BiConsumer<List<List<T>>, T> accumulator = (result, element) -> { // 获取当前块(最后一个块) List<T> currentChunk; if (result.isEmpty()) { currentChunk = new ArrayList<>(chunkSize); result.add(currentChunk); } else { currentChunk = result.get(result.size() - 1); } // 如果当前块已满,则新建一个块 if (currentChunk.size() == chunkSize) { currentChunk = new ArrayList<>(chunkSize); result.add(currentChunk); } currentChunk.add(element); }; // 合并器:用于并行流,合并多个部分结果 BinaryOperator<List<List<T>>> combiner = (left, right) -> { // 合并两个列表 // 注意:每个列表的最后一个块可能未满 // 我们尝试将left的最后一个块和right的第一个块合并 if (left.isEmpty()) { return right; } if (right.isEmpty()) { return left; } // 获取left的最后一个List<T> lastLeftChunk = left.get(left.size() - 1); // 获取right的第一个List<T> firstRightChunk = right.get(0); // 如果最后一个块和第一个块的总小不超过chunkSize,则合并 if (lastLeftChunk.size() + firstRightChunk.size() <= chunkSize) { lastLeftChunk.addAll(firstRightChunk); right.remove(0); // 移除right的第一个块,因为已经合并到left的最后一个块 } // 将剩余的right块全部加入left left.addAll(right); return left; }; // 完器:不需要修改,直接返回结果 Function<List<List<T>>, List<List<T>>> finisher = Function.identity(); // 收集器特征 Set<Collector.Characteristics> characteristics = EnumSet.of(Collector.Characteristics.IDENTITY_FINISH); return Collector.of(supplier, accumulator, combiner, finisher, characteristics); } } ``` 但是,上面的自定义收集器在并行流中合并时可能不够高效(因为需要合并块),而且逻辑较复杂。 另一种更简单的方法(不自定义收集器)是先将Set转换为List,然后使用一个辅助方法分块: ```java public static <T> List<List<T>> partition(List<T> list, int chunkSize) { if (chunkSize <= 0) { throw new IllegalArgumentException("chunkSize must be positive"); } List<List<T>> partitions = new ArrayList<>(); for (int i = 0; i < list.size(); i += chunkSize) { partitions.add(list.subList(i, Math.min(i + chunkSize, list.size()))); } return partitions; } ``` 然后在流中这样使用: ```java List<List<Integer>> partitions = partition(new ArrayList<>(originalSet), chunkSize); ``` 由于Set无序,转换为List后顺序是随机的(除非使用LinkedHashSet)。如果顺序不重要,那么直接使用ArrayList即可。 如果一定要使用Stream且不依赖先转换为List,我们可以使用以下方法(基于迭代器): ```java public static <T> List<List<T>> partitionSet(Set<T> set, int chunkSize) { List<T> list = new ArrayList<>(set); return IntStream.range(0, (list.size() + chunkSize - 1) / chunkSize) .mapToObj(i -> list.subList(i * chunkSize, Math.min((i + 1) * chunkSize, list.size()))) .collect(Collectors.toList()); } ``` 总结:在Java中,由于Set的无序性,我们通常先转换为List,然后进行分块。如果一定要用Stream,我们可以先转换为List,然后使用IntStream生索引来分块。 考虑到自定义收集器的复杂性,并且并行分块并不常见(因为分块通常用于顺序处理),我们推荐先将Set转换为List,然后使用简单循环分块。 但是,如果希望使用纯Stream操作(不先转换为List)且不依赖迭代器,我们可以使用以下自定义收集器(更简洁的版本,但仍然是顺序的): ```java public static <T> Collector<T, ?, List<List<T>>> toFixedSizeChunks(int chunkSize) { return Collector.of( () -> new ArrayList<List<T>>(), (list, value) -> { List<T> chunk; if (list.isEmpty() || list.get(list.size()-1).size() == chunkSize) { chunk = new ArrayList<>(chunkSize); list.add(chunk); } else { chunk = list.get(list.size()-1); } chunk.add(value); }, (list1, list2) -> { // 合并两个列表,将list2的第一个块合并到list1的最后一个块(如果可能) if (list1.isEmpty()) { return list2; } if (list2.isEmpty()) { return list1; } List<T> lastChunk = list1.get(list1.size()-1); List<T> firstChunk = list2.get(0); if (lastChunk.size() + firstChunk.size() <= chunkSize) { lastChunk.addAll(firstChunk); list2.remove(0); } list1.addAll(list2); return list1; } ); } ``` 使用: ```java List<List<Integer>> partitions = originalSet.stream().collect(toFixedSizeChunks(3)); ``` 注意:这个收集器在并行流中可能工作,但效率不高,且要求流是顺序的(因为顺序会影响分块)。对于Set,由于流是无序的,分块的顺序也是任意的。 因此,根据需求,如果顺序不重要,那么可以使用上述自定义收集器。如果顺序重要(比如按照插入顺序),那么应该使用LinkedHashSet,然后转换为List再分块。 综上所述,我们提供了多种方法,根据实际场景选择。 在回答中,我们给出两种主要方法: 1. 先将Set转换为List,然后使用循环分块(简单高效,推荐)。 2. 自定义收集器(支持流,但并行合并复杂)。 我们给出自定义收集器的代码,并解释。 由于问题要求使用Stream,我们展示自定义收集器的实现。 但请注意:并行流中合并操作的性能可能不是最优的,特别是当块的小很小时,合并操作会比较频繁。 因此,我们建议在顺序流中使用自定义收集器,或者使用先转换为List的方法。 下面我们给出完整的自定义收集器实现(顺序流中工作正常,并行流也可以工作但效率不高)的示例代码: ```java import java.util.*; import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Stream; public class FixedSizeChunkCollector { public static <T> Collector<T, List<List<T>>, List<List<T>>> toFixedSizeChunks(int chunkSize) { // 参数校验 if (chunkSize <= 0) { throw new IllegalArgumentException("chunkSize must be greater than 0"); } return Collector.of( () -> new ArrayList<>(), // 供应器:创建一个存放块的列表 // 累加器:将元素添加到当前最后一个块,如果最后一个块满了则新建一个块 (chunks, element) -> { List<T> currentChunk; if (chunks.isEmpty() || chunks.get(chunks.size() - 1).size() == chunkSize) { currentChunk = new ArrayList<>(chunkSize); chunks.add(currentChunk); } else { currentChunk = chunks.get(chunks.size() - 1); } currentChunk.add(element); }, // 合并器:用于并行流,合并两个部分结果 (chunks1, chunks2) -> { if (chunks1.isEmpty()) { return chunks2; } if (chunks2.isEmpty()) { return chunks1; } // 获取chunks1的最后一个List<T> lastChunk = chunks1.get(chunks1.size() - 1); // 获取chunks2的第一个List<T> firstChunk = chunks2.get(0); // 如果两个块可以合并(总小不超过chunkSize),则合并 if (lastChunk.size() + firstChunk.size() <= chunkSize) { lastChunk.addAll(firstChunk); // 移除chunks2的第一个块,并将chunks1与chunks2的剩余部分合并 chunks2.remove(0); } chunks1.addAll(chunks2); return chunks1; }, // 完器:直接返回结果 Collector.Characteristics.IDENTITY_FINISH ); } public static void main(String[] args) { Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); int chunkSize = 3; // 使用顺序流 List<List<Integer>> chunks = set.stream().collect(toFixedSizeChunks(chunkSize)); System.out.println("顺序流分块: " + chunks); // 使用并行流(结果可能不一致,因为Set的无序性,且并行流会改变元素处理顺序) List<List<Integer>> parallelChunks = set.parallelStream().collect(toFixedSizeChunks(chunkSize)); System.out.println("并行流分块: " + parallelChunks); } } ``` 注意:在并行流中,由于元素处理的顺序不确定,分块的顺序也会不确定,并且最后一个块和第一个块的合并可能不会发生(因为并行处理的分割点不一定在块边界),所以合并逻辑可能不会如预期那样完美。因此,并行流分块的结果可能会产生比预期更多的块(因为合并条件不满足)。 所以,如果对块的数量和小有严格要求,建议在顺序流中使用,或者使用非流式方法(先转换为List再分块)。 最后,我们推荐使用先将Set转换为List,然后分块的方法,因为简单可靠: ```java public static <T> List<List<T>> partitionSet(Set<T> set, int chunkSize) { List<T> list = new ArrayList<>(set); List<List<T>> partitions = new ArrayList<>(); for (int i = 0; i < list.size(); i += chunkSize) { partitions.add(list.subList(i, Math.min(i + chunkSize, list.size()))); } return partitions; } ``` 以上代码简单高效,易于理解。 因此,根据问题要求,我们给出了使用Stream的自定义收集器方法,同时也提供了更简单的方法。 相关问题:</think>### 使用 Java Stream 将 Set 集合分成固定小的多个子集 在 Java 中,将 Set 集合拆分成固定小的多个子集(分块/分页)可以通过以下三种方法实现,每种方法都有其适用场景: #### 方法 1:使用 Java 8 Stream API 和 `Collectors.groupingBy()` ```java import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; public class SetPartitioner { public static <T> List<Set<T>> partitionSet(Set<T> originalSet, int chunkSize) { // 将 Set 转换为带索引的列表 List<T> list = new ArrayList<>(originalSet); int totalSize = list.size(); // 使用索引创建分块 return IntStream.range(0, (totalSize + chunkSize - 1) / chunkSize) .mapToObj(i -> list.subList(i * chunkSize, Math.min((i + 1) * chunkSize, totalSize)) .map(HashSet::new) // 转换回 Set .collect(Collectors.toList()); } public static void main(String[] args) { Set<Integer> numbers = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); int chunkSize = 3; List<Set<Integer>> partitions = partitionSet(numbers, chunkSize); // 输出: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]] partitions.forEach(System.out::println); } } ``` #### 方法 2:使用迭代器和 Stream 生(适合集合) ```java public static <T> List<Set<T>> partitionSetIterator(Set<T> originalSet, int chunkSize) { List<Set<T>> partitions = new ArrayList<>(); Iterator<T> iterator = originalSet.iterator(); while (iterator.hasNext()) { Set<T> chunk = new HashSet<>(); for (int i = 0; i < chunkSize && iterator.hasNext(); i++) { chunk.add(iterator.next()); } partitions.add(chunk); } return partitions; } ``` #### 方法 3:使用第三方库 Guava(最简洁) ```java // 添加 Maven 依赖: com.google.guava:guava:31.1-jre import com.google.common.collect.Lists; import java.util.*; public static <T> List<Set<T>> partitionWithGuava(Set<T> originalSet, int chunkSize) { return Lists.partition(new ArrayList<>(originalSet), chunkSize) .stream() .map(HashSet::new) .collect(Collectors.toList()); } ``` ### 关键解释与注意事项 1. **核心机制**: - 先将 `Set` 转换为有序的 `List`(保持固定顺序) - 通过索引计算分块范围(`startIndex` 到 `endIndex`) - 使用 `subList()` 截取子集并转换为新 Set 2. **分块小计算**: ```java // 计算分块数量 int chunks = (int) Math.ceil((double) originalSet.size() / chunkSize); ``` 3. **保留顺序**: - 使用 `LinkedHashSet` 替代 `HashSet` 可保持插入顺序: ```java .map(LinkedHashSet::new) ``` 4. **性能考虑**: - **时间复杂度**:O(n) - 所有元素只遍历一次 - **空间复杂度**:O(n) - 需要额外空间存储分块 - 集合推荐使用方法 2(迭代器),避免创建完整列表 5. **空集合处理**: ```java if (originalSet.isEmpty()) { return Collections.emptyList(); } ``` 6. **并行流优化**: ```java // 并行处理(需确保线程安全) return IntStream.range(0, chunks).parallel() .mapToObj(...) .collect(Collectors.toList()); ``` ### 使用示例 ```java Set<String> names = Set.of("Alice", "Bob", "Charlie", "David", "Eva"); List<Set<String>> nameChunks = partitionSet(names, 2); // 输出: [[Alice, Bob], [Charlie, David], [Eva]] nameChunks.forEach(System.out::println); ``` ### 推荐方案选择 1. **标准 Java 方案**:方法 1(Stream + 索引) 2. **超集合**:方法 2(迭代器) 3. **项目含 Guava**:方法 3(最简洁) ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZHOU_VIP

您的鼓励将是我创作最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值