private List<CompactedObjectBuilder> compactObjects(List<StreamDataBlock> streamDataBlocks) {
        List<CompactedObjectBuilder> compactedObjectBuilders = new ArrayList<>();
        CompactedObjectBuilder builder = new CompactedObjectBuilder();
        for (StreamDataBlock streamDataBlock : streamDataBlocks) {
            if (builder.lastStreamId() == -1L) {
                // init state
                builder.addStreamDataBlock(streamDataBlock);
            } else if (builder.lastStreamId() == streamDataBlock.getStreamId()) {
                // data range from same stream
                if (streamDataBlock.getStartOffset() > builder.lastOffset()) {
                    // data range is not continuous, split current object as StreamObject
                    builder = splitObject(builder, compactedObjectBuilders);
                    builder.addStreamDataBlock(streamDataBlock);
                } else if (streamDataBlock.getStartOffset() == builder.lastOffset()) {
                    builder.addStreamDataBlock(streamDataBlock);
                } else {
                    // should not go there
                    logger.error("FATAL ERROR: illegal stream range position, last offset: {}, curr: {}",
                        builder.lastOffset(), streamDataBlock);
                    return new ArrayList<>();
                }
            } else {
                builder = splitAndAddBlock(builder, streamDataBlock, compactedObjectBuilders);
            }
        }
        if (builder.currStreamBlockSize() > streamSplitSize) {
            splitObject(builder, compactedObjectBuilders);
        } else {
            compactedObjectBuilders.add(builder);
        }
        return compactedObjectBuilders;
    }
Iterate through stream data blocks and group them into different {@link CompactionType}.

@param streamDataBlocks stream data blocks
@return list of {@link CompactedObjectBuilder}
s3stream/src/main/java/com/automq/stream/s3/compact/CompactionAnalyzer.java