首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 批量分組處理演算法:固定大小分批、動態分組與 Stream API 實現

     編輯:Java 程式設計入門

核心需求:將大集合拆分為固定大小的批次處理

常見場景:大量資料庫批次寫入、API 批量呼叫、訊息佇列分批發送、大檔案分片上傳。核心邏輯:List> batches = partition(list, batchSize)

基礎實現:傳統迴圈版


import java.util.*;

public class BatchPartitioner {

    /**
     * 將列表按固定大小分批
     * @param list 來源列表
     * @param batchSize 批次大小 (>0)
     * @return 分批後的列表列表 (原列表順序保持)
     */
    public static  List> partition(List list, int batchSize) {
        if (list == null || list.isEmpty() || batchSize <= 0) {
            return Collections.emptyList();
        }

        List> batches = new ArrayList<>();
        int total = list.size();
        int fullBatches = total / batchSize;

        // 完整批次
        for (int i = 0; i < fullBatches; i++) {
            int from = i * batchSize;
            int to = from + batchSize;
            batches.add(new ArrayList<>(list.subList(from, to)));
        }

        // 餘數批次
        int remainder = total % batchSize;
        if (remainder > 0) {
            int from = fullBatches * batchSize;
            batches.add(new ArrayList<>(list.subList(from, total)));
        }

        return batches;
    }

    // 就地分批處理 (不建立中間列表,記憶體效率高)
    public static  void processInBatches(List list, int batchSize, BatchProcessor processor) {
        if (list == null || list.isEmpty() || batchSize <= 0) return;
        
        int total = list.size();
        for (int i = 0; i < total; i += batchSize) {
            int end = Math.min(i + batchSize, total);
            List batch = list.subList(i, end); // 視圖,無複製開銷
            processor.process(batch);
        }
    }

    @FunctionalInterface
    public interface BatchProcessor {
        void process(List batch);
    }
}

Java 8 Stream API 實現 (函數式風格)


import java.util.*;
import java.util.stream.*;

public class StreamBatchPartitioner {

    // 方法一:IntStream 索引分組 (最直觀)
    public static  List> partitionStream(List list, int batchSize) {
        if (list == null || list.isEmpty() || batchSize <= 0) return Collections.emptyList();
        
        return IntStream.iterate(0, i -> i + batchSize)
                .limit((list.size() + batchSize - 1) / batchSize)
                .mapToObj(i -> list.subList(i, Math.min(i + batchSize, list.size())))
                .map(ArrayList::new) // 複製為獨立列表 (可選)
                .collect(Collectors.toList());
    }

    // 方法二:Collector 自定義 (可直接流式處理,無中間列表)
    public static  List batchProcess(Stream stream, int batchSize, 
            Function, R> batchProcessor) {
        return stream.collect(Collectors.collectingAndThen(
            Collectors.groupingBy(
                // 分組鍵:索引 / batchSize
                new java.util.function.Function() {
                    long counter = -1;
                    @Override public Long apply(T t) { return ++counter / batchSize; }
                },
                // 下游收集器:收集每批並立即處理
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    batchProcessor
                )
            ),
            map -> new ArrayList<>(map.values())
        ));
    }

    // 使用範例
    public static void main(String[] args) {
        List data = IntStream.rangeClosed(1, 100).boxed().collect(Collectors.toList());
        
        // 1. 簡單分批
        List> batches = partitionStream(data, 7);
        System.out.println("批次數: " + batches.size()); // 15
        System.out.println("最後批次: " + batches.get(batches.size()-1)); // [99, 100]

        // 2. 流式批次處理 (如批次寫入 DB)
        List result = batchProcess(data.stream(), 10, batch -> {
            // 模擬批次處理:求和
            return batch.stream().mapToInt(Integer::intValue).sum();
        });
        System.out.println("各批次和: " + result); // [55, 155, 255, ...]
    }
}

Guava/Apache Commons 現成工具


// Guava Lists.partition (返回視圖,極高效)
import com.google.common.collect.Lists;

List> batches = Lists.partition(sourceList, batchSize);
// 注意:返回的子列表是原列表視圖,修改子列表會影響原列表
// 需獨立副本時:Lists.partition(new ArrayList<>(source), batchSize)

// Apache Commons Collections
import org.apache.commons.collections4.ListUtils;

List> batches = ListUtils.partition(sourceList, batchSize);

進階場景:動態分組策略

1. 按條件分組 (如相同用戶、相同訂單狀態)


// 按欄位分組
Map> byUser = orders.stream()
    .collect(Collectors.groupingBy(Order::getUserId));

// 多級分組:用戶 -> 狀態
Map>> byUserStatus = orders.stream()
    .collect(Collectors.groupingBy(
        Order::getUserId,
        Collectors.groupingBy(Order::getStatus)
    ));

2. 按大小動態分組 (如檔案分片、訊息分包)


public static  List> partitionBySize(List items, 
        ToLongFunction sizeMapper, long maxBatchSize) {
    
    List> batches = new ArrayList<>();
    List currentBatch = new ArrayList<>();
    long currentSize = 0;

    for (T item : items) {
        long itemSize = sizeMapper.applyAsLong(item);
        // 單項超過限制:單獨成批 (或拋異常/分割)
        if (itemSize > maxBatchSize) {
            if (!currentBatch.isEmpty()) {
                batches.add(currentBatch);
                currentBatch = new ArrayList<>();
                currentSize = 0;
            }
            batches.add(Collections.singletonList(item));
            continue;
        }
        
        // 當前批次加入該項會超限:切分
        if (currentSize + itemSize > maxBatchSize) {
            batches.add(currentBatch);
            currentBatch = new ArrayList<>();
            currentSize = 0;
        }
        
        currentBatch.add(item);
        currentSize += itemSize;
    }
    
    if (!currentBatch.isEmpty()) batches.add(currentBatch);
    return batches;
}

// 使用:按字串長度分組,每批總長度不超過 1000
List> batches = partitionBySize(strings, String::length, 1000);

3. 平行串流批次處理 (CPU 密集型)


// 注意:parallel stream 順序不保證,需無序場景
List results = hugeList.parallelStream()
    .collect(Collectors.groupingBy(
        i -> i / BATCH_SIZE, // 分組鍵
        Collectors.collectingAndThen(
            Collectors.toList(),
            batch -> processBatch(batch) // 並行處理各批
        )
    ))
    .values().stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());

效能與記憶體考量

方案記憶體速度適用場景
subList 視圖 + 就地處理O(1) 額外最快單執行緒、順序處理、不需保留分批結果
new ArrayList<>(subList)O(batchSize)快需獨立修改批次、多執行緒共享
Guava Lists.partitionO(1) 視圖最快只讀遍歷、記憶體敏感
Stream APIO(n) 中間中等函數式鏈式操作、並行處理

資料庫批次寫入最佳實踐


@Repository
public class BatchInsertRepository {

    @Autowired JdbcTemplate jdbcTemplate;
    
    public void batchInsert(List entities, int batchSize) {
        String sql = "INSERT INTO t (a,b,c) VALUES (?,?,?)";
        
        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override public void setValues(PreparedStatement ps, int i) throws SQLException {
                Entity e = entities.get(i);
                ps.setString(1, e.getA());
                ps.setInt(2, e.getB());
                ps.setTimestamp(3, Timestamp.valueOf(e.getC()));
            }
            @Override public int getBatchSize() { return entities.size(); }
        });
        // 或分批調用:
        // List> batches = Lists.partition(entities, batchSize);
        // batches.forEach(batch -> jdbcTemplate.batchUpdate(sql, ...));
    }
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved