基于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处理工具。

相关推荐
冰心孤城2 小时前
Excel: xls与xlsx格式转换排坑指南
java·前端·excel
金豆呀5 小时前
WPS批量提取Word文档内容生成固定格式Excel表格
word·excel·wps
Access开发易登软件7 小时前
Access 怎么做前后端分离?用 Web API 读写 SQL Server
前端·数据库·人工智能·microsoft·excel·access
红莲凪是1 天前
使用二次封装的Excel COM 组件操作Excel\WPS ET IExcelRange 高级应用
python·excel·wps
用户298698530141 天前
如何使用免费工具在线将 Excel 转换为 XML 格式
人工智能·后端·excel
开开心心就好1 天前
内存清理工具定时自动清理开机自启动
java·开发语言·elasticsearch·ocr·excel·音视频·big data
开开心心就好1 天前
电脑内存优化工具一键释放内存超简单
java·开发语言·macos·arcgis·ocr·excel·音视频
随手工具-Excel, pdf, SQL1 天前
没有 SSMS 导入向导?Excel/CSV 一键生成 SQL Server INSERT 脚本
excel·sql server·在线工具·insert·测试数据·ssms·t-sql
SunnyDays10112 天前
Python 将 Excel(XLS/XLSX)转换为 JSON:导出工作簿、工作表、单元格区域与自定义 JSON 结构
python·json·excel·excel 转 json·导出 excel 到 json·xlsx 转 json·xls 转 json
chatexcel2 天前
ChatExcel AI Word 升级:多模态读资料、按模板生成、智能排版配图,还能批注修订和联动Excel 复核
人工智能·ai·word·excel·ai写作