Статьи Генератор паролей UUID MD5 Unix-время URL-encode Base64 Форматирование XML Ваш внешний IP Число прописью


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

Вернуться назад Исходники

30 января 2020

Тэги: Apache Java 10 maven

Для чтения данных из файла Excel можно использовать библиотеку org.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-файл и в конце выводим всё, что нам удалось из него извлечь.