常見場景:大量資料庫批次寫入、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);
}
}
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 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);
// 按欄位分組
Map> byUser = orders.stream()
.collect(Collectors.groupingBy(Order::getUserId));
// 多級分組:用戶 -> 狀態
Map>> byUserStatus = orders.stream()
.collect(Collectors.groupingBy(
Order::getUserId,
Collectors.groupingBy(Order::getStatus)
));
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);
// 注意: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.partition | O(1) 視圖 | 最快 | 只讀遍歷、記憶體敏感 |
| Stream API | O(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, ...));
}
}