Статьи Утилиты Telegram YouTube Отзывы

Чтение данных из Excel

Исходники

30 января 2020

Тэги: Apache, Excel, Java, maven.

Для чтения данных из файла Excel можно использовать библиотеку Apache POI. Эта библиотека позволяет читать как «старый» формат Excel 97-2003 (расширение файла .xls), так и «новый» (.xlsx). В качестве примера вы можете использовать проект на github.

Добавим в наш maven-проект необходимые зависимости.

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.1</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.1</version>
</dependency>

Теперь напишем класс ExcelReader, состоящий из одного публичного метода read() и нескольких приватных.

public void read(String filename) throws IOException {
    Workbook workbook = loadWorkbook(filename);
    var sheetIterator = workbook.sheetIterator();
    while (sheetIterator.hasNext()) {
        Sheet sheet = sheetIterator.next();
        processSheet(sheet);
        System.out.println();
    }
}

В качестве параметра метод принимает полный путь до файла Excel. Затем мы вызываем метод loadWorkbook(), который возвращает интерфейс Workbook. Этот интерфейс имеет несколько реализаций в зависимости от формата файла (xls, xlsx и т.п.). В одном файле (книге) Excel имеется несколько страниц (листов). Эти листы мы обходим с помощью итератора, который возвращает метод sheetIterator(). Обработка каждого листа происходит в методе processSheet().

private Workbook loadWorkbook(String filename) throws IOException {
    var extension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
    var file = new FileInputStream(new File(filename));
    switch (extension) {
        case "xls":
            // old format
            return new HSSFWorkbook(file);
        case "xlsx":
            // new format
            return new XSSFWorkbook(file);
        default:
            throw new RuntimeException("Unknown Excel file extension: " + extension);
    }
}

Метод loadWorkbook() анализирует расширение файла и в зависимости от него определяет формат файла. Сначала мы создаём FileInputStream, связанный с исходным файлом, затем создаём на основе этого потока нужную реализацию интерфейса Workbook. Если расширение отличается от тех, которые мы ожидаем, то кидаем исключение.

private void processSheet(Sheet sheet) {
    System.out.println("Sheet: " + sheet.getSheetName());
    var data = new HashMap<Integer, List<Object>>();
    var iterator = sheet.rowIterator();
    for (var rowIndex = 0; iterator.hasNext(); rowIndex++) {
        var row = iterator.next();
        processRow(data, rowIndex, row);
    }
    System.out.println("Sheet data:");
    System.out.println(data);
}

Метод processSheet() обрабатывает один лист из Excel. Метод getSheetName() возвращает имя листа, которое обычно в Excel пишется внизу. Далее создаём мапу data, куда будем складывать построчно данные из ячеек таблицы. Ключ такой мапы – это номер строки, а значение – все ячейки данной таблицы. Обход всех строк листа выполняем также через итератор.

private void processRow(HashMap<Integer, List<Object>> data, int rowIndex, Row row) {
    data.put(rowIndex, new ArrayList<>());
    for (var cell : row) {
        processCell(cell, data.get(rowIndex));
    }
}

Метод processRow() просто вызывает в цикле метод processCell() для каждой ячейки.

private void processCell(Cell cell, List<Object> dataRow) {
    switch (cell.getCellType()) {
        case STRING:
            dataRow.add(cell.getStringCellValue());
            break;
        case NUMERIC:
            if (DateUtil.isCellDateFormatted(cell)) {
                dataRow.add(cell.getLocalDateTimeCellValue());
            } else {
                dataRow.add(NumberToTextConverter.toText(cell.getNumericCellValue()));
            }
            break;
        case BOOLEAN:
            dataRow.add(cell.getBooleanCellValue());
            break;
        case FORMULA:
            dataRow.add(cell.getCellFormula());
            break;
        default:
            dataRow.add(" ");
    }
}

В методе processCell() для каждой ячейки мы вначале смотрим формат ячейки с помощью метода getCellType(). Всего есть 4 значимых типа для формата ячейки: это строка, число, булевый (логический) тип и формула, в которой значение ячейки вычисляется динамически на основании других ячеек.

Значение каждого типа мы получаем с помощью соответствующего метода. Для текстовых значений мы используем getStringCellValue(). Для числового типа мы сначала проверяем формат с помощью метода DateUtil.isCellDateFormatted() на предмет наличия в ней даты. Если метод возвращает true, то интерпретируем значение ячейки как дату с помощью метода getLocalDateTimeCellValue(), иначе берём значение как число с помощью getNumericCellValue(). При этом используем NumberToTextConverter, который преобразует числа в текст. Если его не использовать, то даже целые числа будут иметь один десятичный знак после запятой. Формула, возвращаемая методом getCellFormula() содержит буквенно-числовые имена ячеек и выглядит примерно так: «A2+C2*2».

Таким образом, строка за строкой, лист за листом, мы обходим весь Excel-файл и в конце выводим всё, что нам удалось из него извлечь.


Облако тэгов

Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.

Последние статьи


Комментарии

26.10.2023 18:49 KirStranger

Спасибо за статью :)

Добавить комментарий

×

devmark.ru