基于POI实现Excel文件导入导出

1. 引言

Apache POI(Poor Obfuscation Implementation)是Apache软件基金会提供的开源Java API,用于处理Microsoft Office格式文件。其中HSSF处理Excel 97-2003(.xls)格式,XSSF处理Excel 2007+(.xlsx)格式,SXSSF则用于处理大数据量的流式写入。

Excel操作在企业开发中的核心应用场景:

  • 数据报表导出(销售数据、财务报表、用户列表等)
  • 批量数据导入(商品信息、员工档案、订单数据等)
  • 数据统计分析后的结果呈现
  • 系统间的数据交换与备份

相比EasyExcel、Hutool等封装框架,原始POI提供了最底层的控制能力,适合需要高度定制化的场景。

2. 环境准备

2.1 Maven依赖配置

xml 复制代码
<dependencies>
    <!-- POI核心依赖 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.3</version>
    </dependency>
    
    <!-- 处理.xlsx格式 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version>
    </dependency>
    
    <!-- 可选:处理日期工具 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.4</version>
    </dependency>
</dependencies>

版本说明:

  • 建议使用5.x版本,4.x版本存在内存泄漏问题
  • 如需支持.xls格式,poi依赖必不可少
  • poi-ooxml包含.xmlbeans和ooxml-security等传递依赖

2.2 环境要求

组件 最低版本 推荐版本
JDK 1.8 11+
POI 3.17 5.2.3+
Maven 3.6+ 3.8+

3. Excel导出实现

3.1 创建工作簿结构

java 复制代码
/**
 * 创建Excel工作簿基础结构
 */
public static Workbook createWorkbook() {
    // 创建.xlsx格式工作簿
    Workbook workbook = new XSSFWorkbook();
    return workbook;
}

/**
 * 创建工作表并设置标题
 */
public static Sheet createSheet(Workbook workbook, String sheetName) {
    Sheet sheet = workbook.createSheet(sheetName);
    // 设置列宽(以字符宽度为单位,256为一个字符宽度)
    sheet.setColumnWidth(0, 20 * 256);  // A列宽度为20个字符
    sheet.setColumnWidth(1, 15 * 256);  // B列宽度为15个字符
    return sheet;
}

/**
 * 创建标题行
 */
public static Row createHeaderRow(Sheet sheet, String[] headers) {
    Row row = sheet.createRow(0);
    for (int i = 0; i < headers.length; i++) {
        Cell cell = row.createCell(i);
        cell.setCellValue(headers[i]);
    }
    return row;
}

3.2 单元格样式设置

java 复制代码
/**
 * 创建标题样式(居中、加粗、背景色)
 */
public static CellStyle createHeaderStyle(Workbook workbook) {
    CellStyle style = workbook.createCellStyle();
    
    // 水平居中
    style.setAlignment(HorizontalAlignment.CENTER);
    // 垂直居中
    style.setVerticalAlignment(VerticalAlignment.CENTER);
    
    // 设置字体
    Font font = workbook.createFont();
    font.setFontName("微软雅黑");
    font.setFontHeightInPoints((short) 12);
    font.setBold(true);
    style.setFont(font);
    
    // 设置背景色(淡蓝色)
    style.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
    style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
    
    // 设置边框
    style.setBorderTop(BorderStyle.THIN);
    style.setBorderBottom(BorderStyle.THIN);
    style.setBorderLeft(BorderStyle.THIN);
    style.setBorderRight(BorderStyle.THIN);
    
    return style;
}

/**
 * 创建数据样式(左对齐、自动换行)
 */
public static CellStyle createDataStyle(Workbook workbook) {
    CellStyle style = workbook.createCellStyle();
    
    style.setAlignment(HorizontalAlignment.LEFT);
    style.setVerticalAlignment(VerticalAlignment.CENTER);
    style.setWrapText(true);  // 自动换行
    
    // 设置边框
    style.setBorderTop(BorderStyle.THIN);
    style.setBorderBottom(BorderStyle.THIN);
    style.setBorderLeft(BorderStyle.THIN);
    style.setBorderRight(BorderStyle.THIN);
    
    return style;
}

/**
 * 创建数字格式样式(保留两位小数)
 */
public static CellStyle createNumberStyle(Workbook workbook) {
    CellStyle style = workbook.createCellStyle();
    style.setAlignment(HorizontalAlignment.RIGHT);
    
    // 创建数字格式
    DataFormat format = workbook.createDataFormat();
    style.setDataFormat(format.getFormat("#,##0.00"));
    
    style.setBorderTop(BorderStyle.THIN);
    style.setBorderBottom(BorderStyle.THIN);
    style.setBorderLeft(BorderStyle.THIN);
    style.setBorderRight(BorderStyle.THIN);
    
    return style;
}

3.3 处理不同数据类型

java 复制代码
/**
 * 设置单元格值(支持多种数据类型)
 */
public static void setCellValue(Cell cell, Object value) {
    if (value == null) {
        cell.setCellValue("");
        return;
    }
    
    if (value instanceof String) {
        cell.setCellValue((String) value);
    } else if (value instanceof Number) {
        cell.setCellValue(((Number) value).doubleValue());
    } else if (value instanceof Date) {
        cell.setCellValue((Date) value);
    } else if (value instanceof Boolean) {
        cell.setCellValue((Boolean) value);
    } else if (value instanceof Calendar) {
        cell.setCellValue((Calendar) value);
    } else {
        cell.setCellValue(value.toString());
    }
}

/**
 * 设置日期格式单元格
 */
public static void setDateCellValue(Cell cell, Date date, Workbook workbook) {
    CellStyle dateStyle = workbook.createCellStyle();
    DataFormat format = workbook.createDataFormat();
    dateStyle.setDataFormat(format.getFormat("yyyy年mm月dd日"));
    
    cell.setCellStyle(dateStyle);
    cell.setCellValue(date);
}

3.4 响应输出流处理

java 复制代码
/**
 * 导出Excel到HTTP响应
 * @param response HttpServletResponse对象
 * @param workbook Excel工作簿
 * @param fileName 导出文件名
 */
public static void exportExcel(HttpServletResponse response, 
                               Workbook workbook, 
                               String fileName) throws IOException {
    // 设置响应头
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("UTF-8");
    
    // 处理文件名编码(防止中文乱码)
    String encodedFileName = URLEncoder.encode(fileName, "UTF-8")
            .replaceAll("\\+", "%20");
    response.setHeader("Content-Disposition", 
                      "attachment;filename*=UTF-8''" + encodedFileName + ".xlsx");
    
    // 写入输出流
    try (OutputStream out = response.getOutputStream()) {
        workbook.write(out);
        out.flush();
    } finally {
        // 关闭工作簿释放资源
        if (workbook != null) {
            workbook.close();
        }
    }
}

4. Excel导入实现

4.1 文件上传与输入流处理

java 复制代码
/**
 * 从MultipartFile读取Excel文件
 * @param file Spring MVC上传的文件
 * @return Excel工作簿对象
 */
public static Workbook readExcel(MultipartFile file) throws IOException {
    String fileName = file.getOriginalFilename();
    InputStream inputStream = file.getInputStream();
    
    // 根据文件后缀判断版本
    if (fileName.endsWith(".xlsx")) {
        return new XSSFWorkbook(inputStream);
    } else if (fileName.endsWith(".xls")) {
        return new HSSFWorkbook(inputStream);
    } else {
        throw new IllegalArgumentException("不支持的文件格式,仅支持.xlsx和.xls");
    }
}

4.2 工作表读取与数据解析

java 复制代码
/**
 * 读取工作表数据(默认读取第一个工作表)
 */
public static List<List<String>> readSheetData(Sheet sheet) {
    List<List<String>> dataList = new ArrayList<>();
    
    // 从第一行开始读取(跳过标题行可从1开始)
    for (int rowIndex = 0; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
        Row row = sheet.getRow(rowIndex);
        if (row == null) continue;
        
        List<String> rowData = new ArrayList<>();
        // 遍历该行的所有单元格
        for (int cellIndex = 0; cellIndex < row.getLastCellNum(); cellIndex++) {
            Cell cell = row.getCell(cellIndex);
            String cellValue = getCellValueAsString(cell);
            rowData.add(cellValue);
        }
        dataList.add(rowData);
    }
    
    return dataList;
}

4.3 单元格数据类型转换

java 复制代码
/**
 * 获取单元格字符串值(处理各种数据类型)
 */
public static String getCellValueAsString(Cell cell) {
    if (cell == null) {
        return "";
    }
    
    // 根据单元格类型进行转换
    switch (cell.getCellType()) {
        case STRING:
            return cell.getStringCellValue().trim();
            
        case NUMERIC:
            // 判断是否为日期类型
            if (DateUtil.isCellDateFormatted(cell)) {
                Date date = cell.getDateCellValue();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                return sdf.format(date);
            } else {
                // 避免科学计数法和精度丢失
                DecimalFormat df = new DecimalFormat("0");
                return df.format(cell.getNumericCellValue());
            }
            
        case BOOLEAN:
            return String.valueOf(cell.getBooleanCellValue());
            
        case FORMULA:
            // 处理公式单元格
            try {
                return String.valueOf(cell.getNumericCellValue());
            } catch (IllegalStateException e) {
                return cell.getStringCellValue();
            }
            
        case BLANK:
            return "";
            
        default:
            return "";
    }
}

/**
 * 获取指定类型的单元格值
 */
public static <T> T getCellValue(Cell cell, Class<T> clazz) {
    if (cell == null) return null;
    
    if (clazz == String.class) {
        return clazz.cast(getCellValueAsString(cell));
    } else if (clazz == Integer.class || clazz == int.class) {
        return clazz.cast((int) cell.getNumericCellValue());
    } else if (clazz == Long.class || clazz == long.class) {
        return clazz.cast((long) cell.getNumericCellValue());
    } else if (clazz == Double.class || clazz == double.class) {
        return clazz.cast(cell.getNumericCellValue());
    } else if (clazz == Boolean.class || clazz == boolean.class) {
        return clazz.cast(cell.getBooleanCellValue());
    } else if (clazz == Date.class) {
        return clazz.cast(cell.getDateCellValue());
    }
    
    return null;
}

4.4 异常处理与数据验证

java 复制代码
/**
 * 导入时进行数据验证
 */
public static class ValidationResult {
    private boolean valid;
    private String errorMessage;
    private int rowIndex;
    private int columnIndex;
    
    // 构造方法、getter和setter省略...
}

/**
 * 验证导入数据
 */
public static ValidationResult validateCell(Cell cell, String fieldName, 
                                           boolean required, 
                                           int max_length) {
    ValidationResult result = new ValidationResult();
    result.setValid(true);
    
    String value = getCellValueAsString(cell);
    
    // 必填验证
    if (required && (value == null || value.isEmpty())) {
        result.setValid(false);
        result.setErrorMessage(fieldName + "不能为空");
        return result;
    }
    
    // 长度验证
    if (max_length > 0 && value.length() > max_length) {
        result.setValid(false);
        result.setErrorMessage(fieldName + "长度不能超过" + max_length + "个字符");
        return result;
    }
    
    // 其他业务验证逻辑...
    
    return result;
}

/**
 * 导入Excel并验证数据
 */
public static List<ValidationResult> importExcelWithValidation(
        MultipartFile file, 
        boolean skipHeader) throws IOException {
    
    Workbook workbook = readExcel(file);
    Sheet sheet = workbook.getSheetAt(0);
    List<ValidationResult> errors = new ArrayList<>();
    
    int startRow = skipHeader ? 1 : 0;
    for (int i = startRow; i <= sheet.getLastRowNum(); i++) {
        Row row = sheet.getRow(i);
        if (row == null) continue;
        
        // 示例:验证第一列(姓名)
        Cell nameCell = row.getCell(0);
        ValidationResult result = validateCell(nameCell, "姓名", true, 50);
        if (!result.isValid()) {
            result.setRowIndex(i + 1);
            result.setColumnIndex(1);
            errors.add(result);
        }
        
        // 验证第二列(年龄)
        Cell ageCell = row.getCell(1);
        if (ageCell != null) {
            try {
                int age = (int) ageCell.getNumericCellValue();
                if (age < 0 || age > 150) {
                    ValidationResult ageError = new ValidationResult();
                    ageError.setValid(false);
                    ageError.setErrorMessage("年龄必须在0-150之间");
                    ageError.setRowIndex(i + 1);
                    ageError.setColumnIndex(2);
                    errors.add(ageError);
                }
            } catch (Exception e) {
                ValidationResult ageError = new ValidationResult();
                ageError.setValid(false);
                ageError.setErrorMessage("年龄必须是数字");
                ageError.setRowIndex(i + 1);
                ageError.setColumnIndex(2);
                errors.add(ageError);
            }
        }
    }
    
    workbook.close();
    return errors;
}

5. 完整示例代码

5.1 Excel导出工具类

java 复制代码
package com.example.excel.util;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;

/**
 * Excel导出工具类
 */
public class ExcelExportUtil {
    
    /**
     * 导出数据到Excel
     * @param headers 表头数组
     * @param dataList 数据列表(每行数据为List<Object>)
     * @param fileName 文件名
     * @param sheetName 工作表名
     */
    public static void exportData(HttpServletResponse response,
                                   String[] headers,
                                   List<List<Object>> dataList,
                                   String fileName,
                                   String sheetName) throws IOException {
        
        // 创建工作簿
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet(sheetName);
        
        // 创建样式
        CellStyle headerStyle = createHeaderStyle(workbook);
        CellStyle dataStyle = createDataStyle(workbook);
        CellStyle numberStyle = createNumberStyle(workbook);
        
        // 创建标题行
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
            cell.setCellStyle(headerStyle);
        }
        
        // 填充数据
        for (int i = 0; i < dataList.size(); i++) {
            Row row = sheet.createRow(i + 1);
            List<Object> rowData = dataList.get(i);
            
            for (int j = 0; j < rowData.size(); j++) {
                Cell cell = row.createCell(j);
                Object value = rowData.get(j);
                
                // 根据数据类型设置样式和值
                if (value == null) {
                    cell.setCellValue("");
                    cell.setCellStyle(dataStyle);
                } else if (value instanceof Number) {
                    cell.setCellValue(((Number) value).doubleValue());
                    cell.setCellStyle(numberStyle);
                } else if (value instanceof Date) {
                    CellStyle dateStyle = workbook.createCellStyle();
                    DataFormat format = workbook.createDataFormat();
                    dateStyle.setDataFormat(format.getFormat("yyyy-MM-dd"));
                    dateStyle.setAlignment(HorizontalAlignment.CENTER);
                    dateStyle.setBorderTop(BorderStyle.THIN);
                    dateStyle.setBorderBottom(BorderStyle.THIN);
                    dateStyle.setBorderLeft(BorderStyle.THIN);
                    dateStyle.setBorderRight(BorderStyle.THIN);
                    cell.setCellStyle(dateStyle);
                    cell.setCellValue((Date) value);
                } else {
                    cell.setCellValue(value.toString());
                    cell.setCellStyle(dataStyle);
                }
            }
        }
        
        // 自动调整列宽
        autoSizeColumns(sheet, headers.length);
        
        // 导出文件
        exportExcel(response, workbook, fileName);
    }
    
    /**
     * 自动调整列宽
     */
    private static void autoSizeColumns(Sheet sheet, int columnCount) {
        for (int i = 0; i < columnCount; i++) {
            sheet.autoSizeColumn(i);
            // 设置最小列宽,防止过窄
            if (sheet.getColumnWidth(i) < 15 * 256) {
                sheet.setColumnWidth(i, 15 * 256);
            }
            // 设置最大列宽,防止过宽
            if (sheet.getColumnWidth(i) > 50 * 256) {
                sheet.setColumnWidth(i, 50 * 256);
            }
        }
    }
    
    /**
     * 创建标题样式
     */
    public static CellStyle createHeaderStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        
        Font font = workbook.createFont();
        font.setFontName("微软雅黑");
        font.setFontHeightInPoints((short) 12);
        font.setBold(true);
        style.setFont(font);
        
        style.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        
        return style;
    }
    
    /**
     * 创建数据样式
     */
    public static CellStyle createDataStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.LEFT);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setWrapText(true);
        
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        
        return style;
    }
    
    /**
     * 创建数字格式样式
     */
    public static CellStyle createNumberStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.RIGHT);
        
        DataFormat format = workbook.createDataFormat();
        style.setDataFormat(format.getFormat("#,##0.00"));
        
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        
        return style;
    }
    
    /**
     * 导出Excel到HTTP响应
     */
    public static void exportExcel(HttpServletResponse response, 
                                   Workbook workbook, String fileName) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        
        String encodedFileName = URLEncoder.encode(fileName, "UTF-8")
                .replaceAll("\\+", "%20");
        response.setHeader("Content-Disposition", 
                          "attachment;filename*=UTF-8''" + encodedFileName + ".xlsx");
        
        try (OutputStream out = response.getOutputStream()) {
            workbook.write(out);
            out.flush();
        } finally {
            if (workbook != null) {
                workbook.close();
            }
        }
    }
}

5.2 Excel导入工具类

java 复制代码
package com.example.excel.util;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Excel导入工具类
 */
public class ExcelImportUtil {
    
    /**
     * 导入Excel文件并返回数据
     * @param file 上传的Excel文件
     * @param skipHeader 是否跳过标题行
     * @return 数据列表(每行数据为List<String>)
     */
    public static List<List<String>> importExcel(MultipartFile file, boolean skipHeader) 
            throws IOException {
        
        Workbook workbook = readExcel(file);
        Sheet sheet = workbook.getSheetAt(0);
        List<List<String>> dataList = new ArrayList<>();
        
        int startRow = skipHeader ? 1 : 0;
        for (int i = startRow; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            if (row == null) continue;
            
            List<String> rowData = new ArrayList<>();
            for (int j = 0; j < row.getLastCellNum(); j++) {
                Cell cell = row.getCell(j);
                String value = getCellValueAsString(cell);
                rowData.add(value);
            }
            dataList.add(rowData);
        }
        
        workbook.close();
        return dataList;
    }
    
    /**
     * 读取Excel文件
     */
    public static Workbook readExcel(MultipartFile file) throws IOException {
        String fileName = file.getOriginalFilename();
        InputStream inputStream = file.getInputStream();
        
        if (fileName.endsWith(".xlsx")) {
            return new XSSFWorkbook(inputStream);
        } else if (fileName.endsWith(".xls")) {
            return new HSSFWorkbook(inputStream);
        } else {
            throw new IllegalArgumentException("不支持的文件格式");
        }
    }
    
    /**
     * 获取单元格字符串值
     */
    public static String getCellValueAsString(Cell cell) {
        if (cell == null) return "";
        
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue().trim();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date date = cell.getDateCellValue();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    return sdf.format(date);
                } else {
                    java.text.DecimalFormat df = new java.text.DecimalFormat("0");
                    return df.format(cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                try {
                    return String.valueOf(cell.getNumericCellValue());
                } catch (IllegalStateException e) {
                    return cell.getStringCellValue();
                }
            case BLANK:
                return "";
            default:
                return "";
        }
    }
}

5.3 Controller使用示例

java 复制代码
package com.example.excel.controller;

import com.example.excel.util.ExcelExportUtil;
import com.example.excel.util.ExcelImportUtil;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Excel操作Controller示例
 */
@RestController
@RequestMapping("/excel")
public class ExcelController {
    
    /**
     * 导出示例
     */
    @GetMapping("/export")
    public void exportUserList(HttpServletResponse response) throws IOException {
        // 准备表头
        String[] headers = {"用户ID", "用户名", "年龄", "注册日期", "状态"};
        
        // 准备数据
        List<List<Object>> dataList = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            List<Object> row = new ArrayList<>();
            row.add(i);
            row.add("用户" + i);
            row.add(20 + i);
            row.add(new java.util.Date());
            row.add(i % 2 == 0 ? "启用" : "禁用");
            dataList.add(row);
        }
        
        // 导出
        ExcelExportUtil.exportData(response, headers, dataList, 
                                   "用户列表", "用户数据");
    }
    
    /**
     * 导入示例
     */
    @PostMapping("/import")
    public String importUserList(@RequestParam("file") MultipartFile file) {
        try {
            List<List<String>> dataList = ExcelImportUtil.importExcel(file, true);
            
            // 处理导入数据
            for (List<String> row : dataList) {
                System.out.println("导入数据:" + row);
                // 数据库操作...
            }
            
            return "导入成功,共导入" + dataList.size() + "条数据";
        } catch (IOException e) {
            e.printStackTrace();
            return "导入失败:" + e.getMessage();
        }
    }
}

6. 常见问题与解决方案

6.1 大文件内存溢出问题

问题 解决方案 实现方式
大文件OOM 使用SXSSF流式写入 new SXSSFWorkbook(rowAccessWindowSize)
大文件读取 使用事件模型 XSSF and SAX (Event API)
样式过多 复用样式对象 避免为每个单元格创建新样式

SXSSF流式写入示例:

java 复制代码
// 设置内存中保留100行,超过后写入临时文件
SXSSFWorkbook workbook = new SXSSFWorkbook(100);
Sheet sheet = workbook.createSheet("大数据导出");

// 写入数据...
for (int i = 0; i < 100000; i++) {
    Row row = sheet.createRow(i);
    // ...
}

// 清理临时文件
workbook.dispose();

6.2 数字精度丢失问题

java 复制代码
// 错误写法:可能丢失精度
double value = cell.getNumericCellValue();

// 正确写法:使用BigDecimal
BigDecimal decimal = new BigDecimal(cell.getNumericCellValue())
    .setScale(2, RoundingMode.HALF_UP);

6.3 日期格式问题

java 复制代码
// 判断单元格是否为日期格式
if (DateUtil.isCellDateFormatted(cell)) {
    Date date = cell.getDateCellValue();
    // 处理日期...
} else {
    // 处理数字...
}

6.4 文件名乱码问题

java 复制代码
// 完整的文件名编码处理
String encodedFileName = URLEncoder.encode(fileName, "UTF-8")
        .replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", 
                   "attachment;filename*=UTF-8''" + encodedFileName + ".xlsx");

6.5 版本兼容性

java 复制代码
// 动态判断文件版本
Workbook workbook = null;
String fileName = file.getOriginalFilename();

if (fileName.endsWith(".xlsx")) {
    workbook = new XSSFWorkbook(inputStream);
} else if (fileName.endsWith(".xls")) {
    workbook = new HSSFWorkbook(inputStream);
} else {
    throw new IllegalArgumentException("不支持的文件格式");
}

7. 总结与扩展

7.1 框架对比

特性 Apache POI EasyExcel Hutool
学习成本
性能 一般 优秀 一般
内存占用 较高 较高
功能完整性 丰富 基础 较全
定制化能力 中等

选择建议:

  • 需要高度定制化样式和功能 → Apache POI
  • 处理百万级大数据量 → EasyExcel
  • 快速开发、简单场景 → Hutool

7.2 性能优化要点

  1. 样式复用:避免为每个单元格创建新样式,全局创建并复用
  2. 流式处理:大数据场景使用SXSSF
  3. 批量操作:减少频繁的IO操作
  4. 及时释放:使用try-with-resources确保资源释放

7.3 实际应用场景

  • 报表系统:销售报表、财务报表、数据分析结果导出
  • 数据迁移:系统间数据交换、历史数据导入
  • 后台管理:用户列表、商品管理、订单导出
  • 数据分析:业务数据统计、趋势分析报告

7.4 扩展建议

  1. 结合注解:基于注解实现自动映射(如Excel导入直接转为实体对象)
  2. 异步处理:大文件导出采用异步+通知机制
  3. 模板导出:基于预先设计好的Excel模板进行数据填充
  4. 多Sheet处理:复杂报表使用多Sheet组织数据

总结:Apache POI提供了完整的Excel处理能力,虽然API相对复杂,但在需要精细控制和高度定制化的场景下仍是不二之选。通过合理封装和优化,可以构建稳定高效的Excel处理工具。

相关推荐
AI_56787 小时前
Excel数据透视表提速:Power Query预处理百万数据
数据库·excel
颜挺锐10 小时前
EXCEL中A列连接B列中间加| 怎么写公式
excel
Texous20 小时前
Java解析Excel图片
java·excel·wps·office·excel图片处理·excel图片解析·excel图片提取
chatexcel2 天前
从Excel到PPT:如何利用自动化工具重构数据汇报流程
自动化·powerpoint·excel
wei10192 天前
【Excel VBA基础编程】边玩边学:可视化程序开发
excel·vba·自动化工具
科技块儿2 天前
如何利用Excel宏和离线数据库自动化IP归属地查询?
服务器·数据库·物联网·tcp/ip·自动化·excel
请为小H留灯3 天前
Excel 常用公式大全(带详细步骤):文本→日期→判断→查找→统计→求和
excel·职场·函数·公式·办公常用
骇客野人3 天前
Java使用MultipartFile上传下载excel后端处理
java·spring·excel
CodeKwang4 天前
Qt实战:简易Excel表格 | 附完整源码
qt·excel·qtabwidget·qt控件