杰瑞科技汇

java excel 导入导出

核心工具:Apache POI 简介

Apache POI 是一个开源的 Java 库,由 Apache 软件基金会维护,它提供了 API,允许 Java 程序读取和写入 Microsoft Office 格式的文件,包括 Excel (.xls, .xlsx)、Word (.doc, .docx) 和 PowerPoint (.ppt, .pptx)。

java excel 导入导出-图1
(图片来源网络,侵删)

POI 的主要模块:

模块 描述 适用场景
POI-HSSF 处理 Excel 97-2003 格式 (.xls) 兼容旧版 Office,文件格式限制为 65536 行 x 256 列。
POI-XSSF 处理 Excel 2007+ 格式 (.xlsx) 目前最主流的格式,基于 OOXML 标准,支持大数据量。
POI-SXSSF 流式 API,用于处理大数据量的 .xlsx 文件。 导出时性能极高,可以避免内存溢出,它会把不活跃的数据写入临时文件,而不是全部保存在内存中。注意:SXSSF 不支持读取
POI-OOXML 一个通用模块,包含了 XSSF 和 SXSSF 的依赖。 在项目中引入这个模块即可,它会自动管理其他相关依赖。

推荐:在现代项目中,直接使用 poi-ooxml 是最方便的,它会自动引入处理 .xlsx 所需的依赖。


环境搭建(Maven 依赖)

在你的 pom.xml 文件中添加以下依赖,建议使用较新的稳定版本。

<dependencies>
    <!-- Apache POI 核心依赖,用于处理 .xlsx 文件 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.5</version> <!-- 请使用最新稳定版 -->
    </dependency>
    <!-- 如果需要处理 .xls 文件,需要额外引入 poi -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

Excel 导出

我们将分两种情况讲解导出:

  1. 基础导出:将数据写入一个简单的 Excel 文件。
  2. 大数据量导出:使用 SXSSF 避免内存溢出。

场景1:基础导出(.xlsx)

假设我们有一个用户列表,需要导出到 Excel。

java excel 导入导出-图2
(图片来源网络,侵删)

准备数据模型

public class User {
    private String name;
    private int age;
    private String email;
    // 构造器、Getter 和 Setter
    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

编写导出工具类

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class ExcelExporter {
    public static void exportUsersToExcel(List<User> users, String filePath) throws IOException {
        // 1. 创建一个新的 XSSFWorkbook 对象 (代表一个 .xlsx 文件)
        Workbook workbook = new XSSFWorkbook();
        // 2. 创建一个工作表
        Sheet sheet = workbook.createSheet("用户列表");
        // 3. 创建表头样式
        CellStyle headerStyle = workbook.createCellStyle();
        Font headerFont = workbook.createFont();
        headerFont.setBold(true);
        headerStyle.setFont(headerFont);
        headerStyle.setAlignment(HorizontalAlignment.CENTER); // 居中
        // 4. 创建数据行样式
        CellStyle dataStyle = workbook.createCellStyle();
        dataStyle.setAlignment(HorizontalAlignment.CENTER);
        // 5. 创建表头行
        Row headerRow = sheet.createRow(0);
        String[] headers = {"姓名", "年龄", "邮箱"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
            cell.setCellStyle(headerStyle);
        }
        // 6. 填充数据
        int rowNum = 1;
        for (User user : users) {
            Row row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(user.getName());
            row.createCell(1).setCellValue(user.getAge());
            row.createCell(2).setCellValue(user.getEmail());
            // 为数据行应用样式
            for(int i = 0; i < row.getLastCellNum(); i++) {
                row.getCell(i).setCellStyle(dataStyle);
            }
        }
        // 7. 自动调整列宽
        for (int i = 0; i < headers.length; i++) {
            sheet.autoSizeColumn(i);
        }
        // 8. 写入文件并关闭资源
        try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
            workbook.write(fileOut);
        }
        // 9. 关闭 Workbook
        workbook.close();
        System.out.println("Excel 文件已成功导出到: " + filePath);
    }
    public static void main(String[] args) {
        // 模拟数据
        List<User> users = List.of(
            new User("张三", 28, "zhangsan@example.com"),
            new User("李四", 32, "lisi@example.com"),
            new User("王五", 25, "wangwu@example.com")
        );
        String exportPath = "C:/temp/users_export.xlsx";
        try {
            exportUsersToExcel(users, exportPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

场景2:大数据量导出(使用 SXSSF)

当导出数据量非常大(例如几十万行)时,使用 XSSFWorkbook 会导致所有数据都加载到内存中,极易造成 OutOfMemoryErrorSXSSF (Streaming Usermodel) 是为此场景设计的。

核心区别

java excel 导入导出-图3
(图片来源网络,侵删)
  • XSSFWorkbook: 所有数据在内存中。
  • SXSSFWorkbook: 在内存中只保留一部分活动行(默认为100行),其余的行会临时写入磁盘。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class LargeDataExcelExporter {
    public static void exportLargeDataToExcel(List<User> users, String filePath) throws IOException {
        // 1. 创建一个 SXSSFWorkbook 对象
        // 参数 100 表示在内存中保留的行数,超过的行会写入磁盘
        Workbook workbook = new SXSSFWorkbook(100);
        // 2. 后续步骤与 XSSFWorkbook 基本相同
        Sheet sheet = workbook.createSheet("大数据用户列表");
        // ... (创建样式、表头等代码与上面示例相同) ...
        // 为了简洁,这里省略了样式创建代码,直接创建表头和数据
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("姓名");
        headerRow.createCell(1).setCellValue("年龄");
        headerRow.createCell(2).setCellValue("邮箱");
        int rowNum = 1;
        for (User user : users) {
            Row row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(user.getName());
            row.createCell(1).setCellValue(user.getAge());
            row.createCell(2).setCellValue(user.getEmail());
        }
        // 3. 写入文件
        try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
            workbook.write(fileOut);
        }
        // 4. **非常重要**:清理由 SXSSF 创建的临时文件
        ((SXSSFWorkbook) workbook).dispose();
        workbook.close();
        System.out.println("大数据 Excel 文件已成功导出到: " + filePath);
    }
    public static void main(String[] args) {
        // 模拟一个大数据量列表 (例如10万条)
        List<User> largeUsers = new ArrayList<>();
        for (int i = 0; i < 100_000; i++) {
            largeUsers.add(new User("用户" + i, 20 + (i % 30), "user" + i + "@example.com"));
        }
        String exportPath = "C:/temp/large_users_export.xlsx";
        try {
            exportLargeDataToExcel(largeUsers, exportPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Excel 导入

导入的核心是读取 Excel 文件,将每一行数据解析成我们的 Java 对象。

场景:从 .xlsx 文件导入用户数据

编写导入工具类

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ExcelImporter {
    public static List<User> importUsersFromExcel(String filePath) throws IOException {
        List<User> users = new ArrayList<>();
        // 1. 从文件输入流创建 Workbook 对象
        try (FileInputStream fis = new FileInputStream(new File(filePath));
             Workbook workbook = new XSSFWorkbook(fis)) {
            // 2. 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0); // 或者 workbook.getSheet("用户列表");
            // 3. 从第二行开始遍历(跳过表头)
            // sheet.getPhysicalNumberOfRows() 获取有数据的行数
            for (int i = 1; i <= sheet.getPhysicalNumberOfRows(); i++) {
                Row row = sheet.getRow(i);
                if (row == null) {
                    continue; // 跳过空行
                }
                // 4. 读取单元格数据
                // 注意:getCell(index) 可能返回 null,如果单元格为空
                // 使用 getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK) 可以避免空指针
                Cell nameCell = row.getCell(0, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
                Cell ageCell = row.getCell(1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
                Cell emailCell = row.getCell(2, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
                // 5. 处理不同类型的单元格数据
                String name = getCellValueAsString(nameCell);
                int age = (int) getCellValueAsNumeric(ageCell);
                String email = getCellValueAsString(emailCell);
                // 6. 创建 User 对象并添加到列表
                users.add(new User(name, age, email));
            }
        }
        return users;
    }
    /**
     * 将单元格的值转换为字符串
     */
    private static String getCellValueAsString(Cell cell) {
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue().trim();
            case NUMERIC:
                // 如果是数字类型,判断是否为日期
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue().toString();
                } else {
                    // 处理整数和浮点数
                    double numericValue = cell.getNumericCellValue();
                    // 如果是整数,则返回整数形式,否则返回原样
                    return (numericValue == (long) numericValue) ? String.valueOf((long) numericValue) : String.valueOf(numericValue);
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                // 如果是公式,则计算公式的值
                return cell.getCellFormula();
            default:
                return "";
        }
    }
    /**
     * 将单元格的值转换为数字 (int)
     */
    private static int getCellValueAsNumeric(Cell cell) {
        if (cell == null) {
            return 0;
        }
        switch (cell.getCellType()) {
            case NUMERIC:
                return (int) cell.getNumericCellValue();
            case STRING:
                try {
                    return Integer.parseInt(cell.getStringCellValue().trim());
                } catch (NumberFormatException e) {
                    return 0; // 或者抛出异常
                }
            default:
                return 0;
        }
    }
    public static void main(String[] args) {
        String importPath = "C:/temp/users_export.xlsx"; // 使用之前导出的文件
        try {
            List<User> importedUsers = importUsersFromExcel(importPath);
            System.out.println("成功导入 " + importedUsers.size() + " 个用户:");
            importedUsers.forEach(user -> 
                System.out.println("姓名: " + user.getName() + ", 年龄: " + user.getAge() + ", 邮箱: " + user.getEmail())
            );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

高级技巧与最佳实践

  1. 样式与格式化

    • 日期格式:使用 DataFormat 来设置日期单元格的显示格式。
      CellStyle dateStyle = workbook.createCellStyle();
      short dateFormat = workbook.createDataFormat().getFormat("yyyy-MM-dd");
      dateStyle.setDataFormat(dateFormat);
      cell.setCellStyle(dateStyle);
      cell.setCellValue(new Date()); // 设置日期值
    • 数字格式:同样可以使用 DataFormat 来设置货币、百分比等格式。
      CellStyle currencyStyle = workbook.createCellStyle();
      currencyStyle.setDataFormat(workbook.createDataFormat().getFormat("¥#,##0.00"));
      cell.setCellStyle(currencyStyle);
      cell.setCellValue(12345.67);
    • 边框和背景:通过 CellStylesetBorder...setFill... 系列方法可以轻松设置单元格的边框和背景色。
  2. 合并单元格

    // 合并从 (0,0) 到 (4,0) 的单元格
    sheet.addMergedRegion(new CellRangeAddress(0, 4, 0, 0));
  3. 下拉列表

    // 创建下拉列表数据
    String[] listItems = {"选项1", "选项2", "选项3"};
    DVConstraint constraint = DVConstraint.createExplicitListConstraint(listItems);
    // 设置下拉列表作用的区域
    CellRangeAddressList addressList = new CellRangeAddressList(0, 10, 0, 0); // 前11行的第1列
    // 创建数据有效性对象并应用到工作表
    DataValidation validation = new HSSFDataValidation(addressList, constraint);
    sheet.addValidationData(validation);
  4. 处理不同版本(.xls vs .xlsx) 可以通过文件后缀名来判断使用哪个 Workbook 实现。

    Workbook workbook;
    if (filePath.endsWith(".xlsx")) {
        workbook = new XSSFWorkbook(new FileInputStream(file));
    } else if (filePath.endsWith(".xls")) {
        workbook = new HSSFWorkbook(new FileInputStream(file));
    } else {
        throw new IllegalArgumentException("不支持的文件类型");
    }

其他优秀库推荐

除了 Apache POI,还有一些其他库在某些方面可能更优秀:

特点 适用场景
EasyExcel 阿里巴巴开源,基于 POI,但做了大量优化。最大特点是内存占用极低,读写的性能远超 POI,并且提供了非常简洁的 API(注解模式)。 强烈推荐,特别是对于 Web 应用中的导入导出,能显著降低服务器内存压力,代码也更简洁。
JExcelApi 轻量级,API 相对简单,功能没有 POI 强大。 对功能要求不高,追求轻量级的项目。
  • 如果追求极致性能和低内存占用,尤其是在高并发的 Web 环境下,优先选择 EasyExcel
  • 如果项目已经广泛使用 POI,或者需要 POI 提供的所有高级功能(如复杂图表、VBA 等),继续使用 POI 是一个稳妥的选择。

希望这份详细的指南能帮助你掌握 Java 中 Excel 的导入导出!

分享:
扫描分享到社交APP
上一篇
下一篇