Объединить ячейки при экспорте таблицы с помощью EasyExcel
Объединить ячейки при экспорте таблицы с помощью EasyExcel

фон

Теперь нам нужно изменить функцию экспорта данных списка в таблицу Excel, чтобы те же данные в указанном столбце можно было автоматически объединять в ячейки.

Как показано на рисунке выше, укажите два столбца A и B для автоматического объединения. Как показано на рисунке (6, 7), (8, 9), (13, 14, 15), ячейки должны быть автоматически объединены. слились.

Введение в EasyExcel

EasyExcel — это быстрый и лаконичный инструмент обработки Excel на основе Java, который устраняет переполнение памяти больших файлов. Он позволяет быстро выполнять чтение, запись и другие функции Excel без учета таких факторов, как производительность и память.

EasyExcel имеет лучший алгоритм управления потреблением памяти, чем другие платформы синтаксического анализа Excel (Apache poi и jxl). Специально для версии Excel 07 EasyExcel переписывает базовую логику синтаксического анализа. Для синтаксического анализа 3M Excel требуется всего несколько МБ памяти, но для синтаксического анализа poi может потребоваться около 100 МБ памяти. EasyExcel имеет улучшенную производительность чтения. Он может прочитать 75 МБ Excel за 20 секунд с памятью 64 МБ. Существует также более быстрый режим экстремальной скорости, но он потребляет больше памяти.

EasyExcel поддерживает пользовательские стратегии объединения ячеек, которые позволяют быстро и легко заполнять данные в шаблонах. Он имеет активную поддержку китайского сообщества и полные тестовые примеры, которые могут охватывать большинство бизнес-сценариев.

Практический пример слияния ячеек

Пример кода для экспорта Excel с помощью EasyExcel:

Язык кода:javascript
копировать
	@Test
    public void testWrite() throws IOException {
        List<DemoMergeData> resultList = new ArrayList<>();
        resultList.add(DemoMergeData.builder().id(1).sub("Чжан Шэннань").date("12").build());        resultList.add(DemoMergeData.builder().id(1).sub("Джон Доу").date("224").build());        resultList.add(DemoMergeData.builder().id(3).sub("Ван Ву").date("224").build());        resultList.add(DemoMergeData.builder().id(4).sub("Чжао Лю").date("224").build());        resultList.add(DemoMergeData.builder().id(5).sub("Чжао Лю").date("224").build());        resultList.add(DemoMergeData.builder().id(5).sub("Чжао Лю").date("224").build());        resultList.add(DemoMergeData.builder().id(8).sub("Чжао Лю").date("224").build());        resultList.add(DemoMergeData.builder().id(8).sub("Чжао Лю").date("224").build());        resultList.add(DemoMergeData.builder().id(9).sub("Чэнь Ци").date("224").build());        resultList.add(DemoMergeData.builder().id(10).sub("нуб").date("241").build());        resultList.add(DemoMergeData.builder().id(11).sub("Сяохэй").date("241").build());        resultList.add(DemoMergeData.builder().id(12).sub("Сяохэй").date("241").build());        resultList.add(DemoMergeData.builder().id(12).sub("Сяохэй").date("241").build());        resultList.add(DemoMergeData.builder().id(12).sub("Сяохэй").date("241").build());        resultList.add(DemoMergeData.builder().id(13).sub("Сяохэй").date("241").build());        //  Установить имя файла
        String fileName = "C:\\Users\\Administrator\\Downloads\\test\\t1.xlsx";
        File file = new File(fileName);
        if (!file.exists()) {
            file.createNewFile();
        }

        //  имя листа
        EasyExcel.write(fileName, DemoMergeData.class)
                .autoCloseStream(Boolean.TRUE)
                .sheet("Экспорт теста").doWrite(resultList);
    }

Экспортировать стиль таблицы:

Пользовательская стратегия 1: объединить ячейки с одинаковыми данными в верхней и нижней строках.

Пример кода собственной стратегии:

Язык кода:javascript
копировать
public class ExcelFillCellMergeStrategy implements CellWriteHandler {

    private int[] mergeColumnIndex;
    private int mergeRowIndex;
    
    public ExcelFillCellMergeStrategy() {
    }

    public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeColumnIndex = mergeColumnIndex;
    }


    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        //Текущая строка
        int curRowIndex = cell.getRowIndex();
        //Текущий столбец
        int curColIndex = cell.getColumnIndex();

        if (curRowIndex > mergeRowIndex) {
            for (int columnIndex : mergeColumnIndex) {
                if (curColIndex == columnIndex) {
                    mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
                    break;
                }
            }
        }
    }



    /**
     * Объединить текущие ячейки вверх
     *
     * @param cell             текущая ячейка
     * @param curRowIndex      текущая строка
     * @param curColIndex      текущий столбец
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        //получатьтекущая строкаизтекущий столбецизданные и предыдущая строкаизтекущий данные столбца столбец,Объединение на основе того, совпадают ли данные в предыдущей строке.
        Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
        Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();

        // Сравниватьтекущая строкаизпервый столбециз Ячейка такая же, как предыдущая строка?,То же слияние текущая ячейка с предыдущей строкой
        if (curData.equals(preData)) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress cellRangeAddr = mergeRegions.get(i);
                // Если предыдущая ячейка была объединена, сначала удалите исходную объединенную единицу, а затем снова добавьте объединенную единицу.
                if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                    sheet.removeMergedRegion(i);
                    cellRangeAddr.setLastRow(curRowIndex);
                    sheet.addMergedRegion(cellRangeAddr);
                    isMerged = true;
                }
            }
            // Если предыдущая ячейка не была объединена, добавьте новую объединенную ячейку.
            if (!isMerged) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }
}

Пример тестового кода:

Язык кода:javascript
копировать
@Test
    public void testWrite() throws IOException {
        int[] mergeColumnIndex = {0,1};
	    // С какой строки нужно начинать слияние?
        int mergeRowIndex = 1;
        // Данные не инициализируются.
        List<DemoMergeData> resultList = new ArrayList<>();
        // Установить имя файла
        String fileName = "C:\\Users\\Administrator\\Downloads\\test\\t1.xlsx";
        File file = new File(fileName);
        if (!file.exists()) {
            file.createNewFile();
        }

        //  имя листа
        EasyExcel.write(fileName, DemoMergeData.class)
                .autoCloseStream(Boolean.TRUE)
                .registerWriteHandler(new ExcelFillCellMergeStrategy(mergeRowIndex,mergeColumnIndex))
                .sheet("Экспорт теста").doWrite(resultList);
    }

Стиль экспорта:

Пользовательская стратегия 2: объединять ячейки только в том случае, если данные в указанных столбцах совпадают.

Пример кода пользовательской стратегии 2:

Язык кода:javascript
копировать
public class MultiColumnMergeStrategy extends AbstractMergeStrategy {

    // Номер объединенного столбца, начиная с 0, заданного индекса или пронумерован в порядке полей.
    private Integer startCellIndex = 0;
    private Integer endCellIndex = 0;

    // Размер набора данных, используемый для различения конечной позиции строки.
    private Integer maxRow = 0;

    // Операторы без параметров запрещены.
    private MultiColumnMergeStrategy() {
    }


    public MultiColumnMergeStrategy(Integer maxRow, Integer startCellIndex, Integer endCellIndex) {
        this.startCellIndex = startCellIndex;
        this.endCellIndex = endCellIndex;
        this.maxRow = maxRow;
    }

    // Запишите информацию о последнем слиянии
    private final List<List<String>> dataList = new ArrayList<>();

    /**
     * Будут введены каждая строка и столбец. Обратите внимание на условные ограничения в цикле.
     */
    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) {
        int currentCellIndex = cell.getColumnIndex();
        int currentRowIndex = cell.getRowIndex();

        // Определите, нужно ли объединить столбец
        if (currentCellIndex < startCellIndex || currentCellIndex > endCellIndex) {
            return;
        }

        Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        String currentCellValue = curData.toString();

        List<String> rowList;
        if (dataList.size() > currentRowIndex - 1) {
            rowList = dataList.get(currentRowIndex - 1);
        } else {
            rowList = new ArrayList<>();
            dataList.add(rowList);
        }
        rowList.add(currentCellValue);

        // Конечная позиция запускает последнее незавершенное слияние.
        if (relativeRowIndex == (maxRow - 1) && currentCellIndex == endCellIndex) {
            System.out.println(JSONObject.toJSONString(dataList));
            List<String> tempList = null;
            Integer tempIndex = null;
            for (int i = 0; i < dataList.size(); i++) {
                if (tempList == null) {
                    tempList = dataList.get(i);
                    tempIndex = i;
                    continue;
                }
                List<String> currList = dataList.get(i);
                if (tempList.equals(currList)) {
                    if (i >= dataList.size() - 1) {
                        // Конечная позиция запускает последнее незавершенное слияние.
                        for (int j = 0; j < tempList.size(); j++) {
                            sheet.addMergedRegionUnsafe(new CellRangeAddress(tempIndex + 1, i + 1, startCellIndex + j, startCellIndex + j));
                        }
                    }
                    continue;
                }

                // текущая Слияние запускается, когда данные строки отличаются от предыдущей строки данных и выше имеется несколько строк одинаковых данных.
                if (i - tempIndex > 1) {
                    for (int j = 0; j < tempList.size(); j++) {
                        sheet.addMergedRegionUnsafe(new CellRangeAddress(tempIndex + 1, i, startCellIndex + j, startCellIndex + j));
                    }
                }


                tempIndex = i;
                tempList = currList;


            }

        }
    }
}

Пример тестового кода:

Язык кода:javascript
копировать
@Test
    public void testWrite() throws IOException {
        // Данные не инициализируются.
        List<DemoMergeData> resultList = new ArrayList<>();
        // Установить имя файла
        String fileName = "C:\\Users\\Administrator\\Downloads\\test\\t1.xlsx";
        File file = new File(fileName);
        if (!file.exists()) {
            file.createNewFile();
        }

        //  имя листа
        EasyExcel.write(fileName, DemoMergeData.class)
                .autoCloseStream(Boolean.TRUE)
                .registerWriteHandler(new MultiColumnMergeStrategy(resultList.size(),0,1))
                .sheet("Экспорт теста").doWrite(resultList);
    }

Стиль экспорта:

Подвести итог

EasyExcel — это гибкий и мощный инструмент. Вы можете настроить стиль в соответствии со своими бизнес-сценариями, а также использовать функцию заполнения шаблонов для выполнения сложных функций, таких как экспорт международных языков.

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose