以下是一个基于EasyExcel封装的Excel工具类,支持高效导出和读取操作:
java">import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;/*** Excel工具类(基于EasyExcel)*/
public class ExcelUtil {/*** 导出Excel到输出流(单个Sheet)* @param outputStream 输出流* @param dataList 数据列表* @param templateClass 数据模型类* @param sheetName 工作表名称* @param <T> 数据类型*/public static <T> void exportToStream(OutputStream outputStream, List<T> dataList,Class<T> templateClass,String sheetName) {ExcelWriter excelWriter = null;try {// 设置表头样式WriteCellStyle headStyle = new WriteCellStyle();headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);// 设置正文样式WriteCellStyle contentStyle = new WriteCellStyle();contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);// 构建样式策略HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(headStyle, contentStyle);excelWriter = EasyExcel.write(outputStream, templateClass).registerWriteHandler(styleStrategy).build();WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();excelWriter.write(dataList, writeSheet);} finally {if (excelWriter != null) {excelWriter.finish();}}}/*** 导出大数据量Excel(分Sheet写入)* @param outputStream 输出流* @param dataSupplier 数据提供函数(分页查询)* @param templateClass 数据模型类* @param sheetSize 每个Sheet最大行数* @param <T> 数据类型*/public static <T> void exportBigData(OutputStream outputStream,PageDataSupplier<T> dataSupplier,Class<T> templateClass,int sheetSize) {ExcelWriter excelWriter = null;try {excelWriter = EasyExcel.write(outputStream, templateClass).build();int sheetIndex = 1;int page = 1;List<T> dataChunk;while (!(dataChunk = dataSupplier.getPageData(page, sheetSize)).isEmpty()) {WriteSheet writeSheet = EasyExcel.writerSheet("Sheet" + sheetIndex).build();excelWriter.write(dataChunk, writeSheet);if (page % (sheetSize / 5000) == 0) { // 每5000行一个SheetsheetIndex++;}page++;}} finally {if (excelWriter != null) {excelWriter.finish();}}}/*** 从输入流读取Excel数据* @param inputStream 输入流* @param templateClass 数据模型类* @param dataConsumer 数据消费接口* @param <T> 数据类型*/public static <T> void readFromStream(InputStream inputStream,Class<T> templateClass,DataConsumer<T> dataConsumer) {EasyExcel.read(inputStream, templateClass, new AnalysisEventListener<T>() {private static final int BATCH_SIZE = 2000;private List<T> cachedList = new ArrayList<>(BATCH_SIZE);@Overridepublic void invoke(T data, AnalysisContext context) {cachedList.add(data);if (cachedList.size() >= BATCH_SIZE) {dataConsumer.consume(cachedList);cachedList = new ArrayList<>(BATCH_SIZE);}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {if (!cachedList.isEmpty()) {dataConsumer.consume(cachedList);}}}).sheet().doRead();}/*** 导出Web响应(自动设置Header)* @param response HttpServletResponse* @param fileName 文件名(不带后缀)* @param dataList 数据列表* @param templateClass 数据模型类* @param <T> 数据类型*/public static <T> void exportWebResponse(HttpServletResponse response,String fileName,List<T> dataList,Class<T> templateClass) 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");exportToStream(response.getOutputStream(), dataList, templateClass, "Sheet1");}// 自定义转换器注册public static void registerConverter(Converter<?> converter) {EasyExcel.registerConverter(converter);}/*** 分页数据提供接口*/public interface PageDataSupplier<T> {List<T> getPageData(int pageNumber, int pageSize);}/*** 数据消费接口*/public interface DataConsumer<T> {void consume(List<T> dataBatch);}// 示例数据模型public static class UserExportModel {@ExcelProperty("用户ID")private Long userId;@ExcelProperty(value = "用户状态", converter = StatusConverter.class)private Integer status;// 其他字段...}// 示例转换器public static class StatusConverter implements Converter<Integer> {private static final Map<Integer, String> STATUS_MAP = Map.of(1, "正常",2, "冻结",3, "注销");@Overridepublic String convertToExcelData(Integer value, ExcelContentProperty property, GlobalConfiguration globalConfig) {return STATUS_MAP.getOrDefault(value, "未知状态");}}
}
使用示例
1. 简单导出到文件:
java">try (FileOutputStream fos = new FileOutputStream("export.xlsx")) {List<User> userList = userService.getAllUsers();ExcelUtil.exportToStream(fos, userList, User.class, "用户数据");
}
2. Web响应导出:
java">@GetMapping("/export")
public void exportUsers(HttpServletResponse response) throws IOException {List<User> userList = userService.getAllUsers();ExcelUtil.exportWebResponse(response, "用户列表", userList, User.class);
}
3. 大数据量分页导出:
java">ExcelUtil.exportBigData(response.getOutputStream(), (page, size) -> userService.getUsersByPage(page, size),User.class,100_0000); // 每个Sheet存储100万数据
4. 读取Excel数据:
java">InputStream inputStream = new FileInputStream("import.xlsx");
ExcelUtil.readFromStream(inputStream, UserImportModel.class, batch -> {userService.batchProcess(batch);
});
主要特性说明
-
高性能导出:
- 使用分Sheet写入策略,每个Sheet最多存储指定行数
- 自动注册样式策略(表头居中,内容左对齐)
- 支持流式导出,避免内存溢出
-
灵活读取:
- 批量处理机制(默认每2000条处理一次)
- 自动内存管理,适合大文件读取
-
扩展功能:
- 支持自定义转换器(通过
registerConverter
方法) - 支持Web响应自动配置(自动设置Content-Type和文件名)
- 提供分页数据获取接口,适合数据库分页查询
- 支持自定义转换器(通过
-
内存安全:
- 导出时自动分批次写入
- 读取时使用事件驱动模型,不保留完整数据在内存
- 默认开启SXSSF模式(流式写入)
-
样式配置:
- 预定义表头和内容样式
- 可通过覆盖
registerWriteHandler
方法自定义样式
最佳实践建议
-
大文件导出:
- 使用
exportBigData
方法配合分页查询 - 设置合适的Sheet大小(推荐50-100万行/Sheet)
- 添加内存监控逻辑
- 使用
-
数据转换:
- 为枚举字段创建专用转换器
- 对日期字段使用
@ExcelProperty#format
- 复杂转换建议使用数据库联查
-
异常处理:
- 在读取时添加数据校验逻辑
- 使用全局异常处理器捕获
ExcelAnalysisException
-
性能优化:
- 导出时关闭自动列宽计算(
.autoTrim(false)
) - 读取时设置合适的缓存大小(
ReadCache
) - 避免在循环中创建大量临时对象
- 导出时关闭自动列宽计算(