private void addObjectsToRemove(CompactionType compactionType, List<CompactedObjectBuilder> compactedObjectBuilders,
        CompactionStats stats, Set<Long> objectsToRemove) {
        List<CompactedObjectBuilder> sortedCompactedObjectIndexList = new ArrayList<>();
        for (CompactedObjectBuilder compactedObjectBuilder : compactedObjectBuilders) {
            // find all compacted objects of the same type
            if (compactedObjectBuilder.type() == compactionType) {
                sortedCompactedObjectIndexList.add(compactedObjectBuilder);
            }
        }
        if (compactionType == CompactionType.SPLIT) {
            // try to find out one stream object to remove
            sortedCompactedObjectIndexList.sort(new StreamObjectComparator(stats.getS3ObjectToCompactedObjectNumMap()));
            // remove compacted object with the highest priority
            CompactedObjectBuilder compactedObjectToRemove = sortedCompactedObjectIndexList.get(0);
            // add all objects in the compacted object
            objectsToRemove.addAll(compactedObjectToRemove.streamDataBlocks().stream()
                .map(StreamDataBlock::getObjectId)
                .collect(Collectors.toSet()));
        } else {
            // try to find out one stream to remove
            // key: stream id, value: id of all objects that contains the stream
            Map<Long, Set<Long>> streamObjectIdsMap = new HashMap<>();
            // key: object id, value: id of all streams from the object, used to describe the dispersion of streams in the object
            Map<Long, Set<Long>> objectStreamIdsMap = new HashMap<>();
            for (CompactedObjectBuilder compactedObjectBuilder : sortedCompactedObjectIndexList) {
                for (StreamDataBlock streamDataBlock : compactedObjectBuilder.streamDataBlocks()) {
                    Set<Long> objectIds = streamObjectIdsMap.computeIfAbsent(streamDataBlock.getStreamId(), k -> new HashSet<>());
                    objectIds.add(streamDataBlock.getObjectId());
                    Set<Long> streamIds = objectStreamIdsMap.computeIfAbsent(streamDataBlock.getObjectId(), k -> new HashSet<>());
                    streamIds.add(streamDataBlock.getStreamId());
                }
            }
            List<Pair<Long, Integer>> sortedStreamObjectStatsList = new ArrayList<>();
            for (Map.Entry<Long, Set<Long>> entry : streamObjectIdsMap.entrySet()) {
                long streamId = entry.getKey();
                Set<Long> objectIds = entry.getValue();
                int objectStreamNum = 0;
                for (long objectId : objectIds) {
                    objectStreamNum += objectStreamIdsMap.get(objectId).size();
                }
                sortedStreamObjectStatsList.add(new ImmutablePair<>(streamId, objectStreamNum));
            }
            sortedStreamObjectStatsList.sort(Comparator.comparingInt(Pair::getRight));
            // remove stream with minimum object dispersion
            objectsToRemove.addAll(streamObjectIdsMap.get(sortedStreamObjectStatsList.get(0).getKey()));
        }
    }
Find objects to exclude from compaction.

@param compactionType          {@link CompactionType#COMPACT} means to exclude objects to reduce stream number in stream set object;
                               {@link CompactionType#SPLIT} means to exclude objects to reduce stream object number
@param compactedObjectBuilders all compacted object builders
@param stats                   compaction stats
@param objectsToRemove         objects to remove
s3stream/src/main/java/com/automq/stream/s3/compact/CompactionAnalyzer.java