【源码解析】EasyExcel导入导出源码解析

news/2025/1/2 2:38:00/

EasyExcel介绍

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

网站

  • 官方网站:https://easyexcel.opensource.alibaba.com/
  • github地址:https://github.com/alibaba/easyexcel
  • gitee地址:https://gitee.com/easyexcel/easyexcel

数据导入

demo

    @PostMapping(value = "/t3", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)public void t3(@RequestPart("file") MultipartFile file) throws IOException {ReadListener<OrderExcel> readListener = new ReadListener<OrderExcel>() {@Overridepublic void invoke(OrderExcel orderExcel, AnalysisContext analysisContext) {System.out.println(orderExcel);}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println(analysisContext);}};EasyExcel.read(file.getInputStream(), OrderExcel.class, readListener).sheet().doRead();}
}

源码解读

EasyExcelFactory#read(InputStream, Class, ReadListener)

    public static ExcelReaderBuilder read(InputStream inputStream, Class head, ReadListener readListener) {ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();excelReaderBuilder.file(inputStream);if (head != null) {excelReaderBuilder.head(head);}if (readListener != null) {excelReaderBuilder.registerReadListener(readListener);}return excelReaderBuilder;}

ExcelReaderBuilder#sheet(),设置sheet。

    public ExcelReaderSheetBuilder sheet() {return this.sheet((Integer)null, (String)null);}

ExcelReaderSheetBuilder#doRead,执行读取操作。

    public void doRead() {if (this.excelReader == null) {throw new ExcelGenerateException("Must use 'EasyExcelFactory.read().sheet()' to call this method");} else {this.excelReader.read(new ReadSheet[]{this.build()});this.excelReader.finish();}}

ExcelReader#read(ReadSheet...),读取。

    public ExcelReader read(ReadSheet... readSheet) {return this.read(Arrays.asList(readSheet));}public ExcelReader read(List<ReadSheet> readSheetList) {this.excelAnalyser.analysis(readSheetList, Boolean.FALSE);return this;}

ExcelAnalyserImpl#analysis,调用 this.excelReadExecutor.execute();

    public void analysis(List<ReadSheet> readSheetList, Boolean readAll) {try {if (!readAll && CollectionUtils.isEmpty(readSheetList)) {throw new IllegalArgumentException("Specify at least one read sheet.");} else {this.analysisContext.readWorkbookHolder().setParameterSheetDataList(readSheetList);this.analysisContext.readWorkbookHolder().setReadAll(readAll);try {this.excelReadExecutor.execute();} catch (ExcelAnalysisStopException var4) {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Custom stop!");}}}} catch (RuntimeException var5) {this.finish();throw var5;} catch (Throwable var6) {this.finish();throw new ExcelAnalysisException(var6);}}

DefaultAnalysisEventProcessor#endRow,挨个调用监听器来处理数据。如果未设置ReadListener,那么监听器集合中只有ModelBuildEventListener

    @Overridepublic void endRow(AnalysisContext analysisContext) {if (RowTypeEnum.EMPTY.equals(analysisContext.readRowHolder().getRowType())) {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Empty row!");}if (analysisContext.readWorkbookHolder().getIgnoreEmptyRow()) {return;}}dealData(analysisContext);}private void dealData(AnalysisContext analysisContext) {ReadRowHolder readRowHolder = analysisContext.readRowHolder();Map<Integer, ReadCellData<?>> cellDataMap = (Map)readRowHolder.getCellMap();readRowHolder.setCurrentRowAnalysisResult(cellDataMap);int rowIndex = readRowHolder.getRowIndex();int currentHeadRowNumber = analysisContext.readSheetHolder().getHeadRowNumber();boolean isData = rowIndex >= currentHeadRowNumber;// Last head columnif (!isData && currentHeadRowNumber == rowIndex + 1) {buildHead(analysisContext, cellDataMap);}// Now is datafor (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) {try {if (isData) {readListener.invoke(readRowHolder.getCurrentRowAnalysisResult(), analysisContext);} else {readListener.invokeHead(cellDataMap, analysisContext);}} catch (Exception e) {onException(analysisContext, e);break;}if (!readListener.hasNext(analysisContext)) {throw new ExcelAnalysisStopException();}}}

ModelBuildEventListener#invokeModelBuildEventListener模型构建事件监听器用来构建模型。buildUserModel主要是获取传入的类信息,进行实例化。将实体类转化为Map,BeanMap dataMap = BeanMapUtils.create(resultModel);

    @Overridepublic void invoke(Map<Integer, ReadCellData<?>> cellDataMap, AnalysisContext context) {ReadSheetHolder readSheetHolder = context.readSheetHolder();if (HeadKindEnum.CLASS.equals(readSheetHolder.excelReadHeadProperty().getHeadKind())) {context.readRowHolder().setCurrentRowAnalysisResult(buildUserModel(cellDataMap, readSheetHolder, context));return;}context.readRowHolder().setCurrentRowAnalysisResult(buildStringList(cellDataMap, readSheetHolder, context));}private Object buildUserModel(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,AnalysisContext context) {ExcelReadHeadProperty excelReadHeadProperty = readSheetHolder.excelReadHeadProperty();Object resultModel;try {resultModel = excelReadHeadProperty.getHeadClazz().newInstance();} catch (Exception e) {throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0,new ReadCellData<>(CellDataTypeEnum.EMPTY), null,"Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e);}Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap();BeanMap dataMap = BeanMapUtils.create(resultModel);for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {Integer index = entry.getKey();Head head = entry.getValue();String fieldName = head.getFieldName();if (!cellDataMap.containsKey(index)) {continue;}ReadCellData<?> cellData = cellDataMap.get(index);Object value = ConverterUtils.convertToJavaObject(cellData, head.getField(),ClassUtils.declaredExcelContentProperty(dataMap, readSheetHolder.excelReadHeadProperty().getHeadClazz(),fieldName), readSheetHolder.converterMap(), context, context.readRowHolder().getRowIndex(), index);if (value != null) {dataMap.put(fieldName, value);}}return resultModel;}

ConverterUtils#doConvertToJavaObject,获取Converter,根据属性上的转换器进行转换操作。

    private static Object doConvertToJavaObject(ReadCellData<?> cellData, Class<?> clazz,ExcelContentProperty contentProperty, Map<ConverterKey, Converter<?>> converterMap, AnalysisContext context,Integer rowIndex, Integer columnIndex) {Converter<?> converter = null;if (contentProperty != null) {converter = contentProperty.getConverter();}boolean canNotConverterEmpty = cellData.getType() == CellDataTypeEnum.EMPTY&& !(converter instanceof NullableObjectConverter);if (canNotConverterEmpty) {return null;}if (converter == null) {converter = converterMap.get(ConverterKeyBuild.buildKey(clazz, cellData.getType()));}if (converter == null) {throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty,"Converter not found, convert " + cellData.getType() + " to " + clazz.getName());}try {return converter.convertToJavaData(new ReadConverterContext<>(cellData, contentProperty, context));} catch (Exception e) {throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty,"Convert data " + cellData + " to " + clazz + " error ", e);}}

BeanMap的使用,通过设置Map中的key,修改实体类的数据。

    public static void main(String[] args) {OrderExcel orderExcel = new OrderExcel();BeanMap beanMap = BeanMapUtils.create(orderExcel);beanMap.put("orderNo", "111");System.out.println(beanMap);System.out.println(orderExcel);}

数据导出

demo

    @PostMapping("/t4")public void t4(HttpServletResponse response) throws Exception {OrderExcel orderExcel = new OrderExcel();List<OrderExcel> orderExcels = Arrays.asList(orderExcel);response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系//"测试":就是我们要生成文档的名称,可以改为自己的String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), OrderExcel.class).sheet("模板").doWrite(orderExcels);}

源码解读

ExcelWriterSheetBuilder#doWrite(java.util.Collection<?>),执行写入操作

    public void doWrite(Collection<?> data) {if (excelWriter == null) {throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet()' to call this method");}excelWriter.write(data, build());excelWriter.finish();}

ExcelWriter#write(Collection<?>, WriteSheet),调用内置的ExcelBuilder

    public ExcelWriter write(Collection<?> data, WriteSheet writeSheet) {return write(data, writeSheet, null);}public ExcelWriter write(Collection<?> data, WriteSheet writeSheet, WriteTable writeTable) {excelBuilder.addContent(data, writeSheet, writeTable);return this;}

ExcelBuilderImpl#addContent(),创建ExcelWriteAddExecutor进行添加操作。

    @Overridepublic void addContent(Collection<?> data, WriteSheet writeSheet, WriteTable writeTable) {try {context.currentSheet(writeSheet, WriteTypeEnum.ADD);context.currentTable(writeTable);if (excelWriteAddExecutor == null) {excelWriteAddExecutor = new ExcelWriteAddExecutor(context);}excelWriteAddExecutor.add(data);} catch (RuntimeException e) {finishOnException();throw e;} catch (Throwable e) {finishOnException();throw new ExcelGenerateException(e);}}

ExcelWriteAddExecutor#add,添加数据到WorkBook中。

    public void add(Collection<?> data) {if (CollectionUtils.isEmpty(data)) {data = new ArrayList<>();}WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder();int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite();if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) {newRowIndex += writeContext.currentWriteHolder().relativeHeadRowIndex();}// BeanMap is out of order, so use sortedAllFieldMapMap<Integer, Field> sortedAllFieldMap = new TreeMap<>();int relativeRowIndex = 0;for (Object oneRowData : data) {int lastRowIndex = relativeRowIndex + newRowIndex;addOneRowOfDataToExcel(oneRowData, lastRowIndex, relativeRowIndex, sortedAllFieldMap);relativeRowIndex++;}}private void addOneRowOfDataToExcel(Object oneRowData, int rowIndex, int relativeRowIndex,Map<Integer, Field> sortedAllFieldMap) {if (oneRowData == null) {return;}RowWriteHandlerContext rowWriteHandlerContext = WriteHandlerUtils.createRowWriteHandlerContext(writeContext,rowIndex, relativeRowIndex, Boolean.FALSE);WriteHandlerUtils.beforeRowCreate(rowWriteHandlerContext);Row row = WorkBookUtil.createRow(writeContext.writeSheetHolder().getSheet(), rowIndex);rowWriteHandlerContext.setRow(row);WriteHandlerUtils.afterRowCreate(rowWriteHandlerContext);if (oneRowData instanceof Collection<?>) {addBasicTypeToExcel(new CollectionRowData((Collection<?>)oneRowData), row, rowIndex, relativeRowIndex);} else if (oneRowData instanceof Map) {addBasicTypeToExcel(new MapRowData((Map<Integer, ?>)oneRowData), row, rowIndex, relativeRowIndex);} else {addJavaObjectToExcel(oneRowData, row, rowIndex, relativeRowIndex, sortedAllFieldMap);}WriteHandlerUtils.afterRowDispose(rowWriteHandlerContext);}

ExcelWriteAddExecutor#addJavaObjectToExcel,添加java实体类到Excel中国。

    private void addJavaObjectToExcel(Object oneRowData, Row row, int rowIndex, int relativeRowIndex,Map<Integer, Field> sortedAllFieldMap) {WriteHolder currentWriteHolder = writeContext.currentWriteHolder();BeanMap beanMap = BeanMapUtils.create(oneRowData);// Bean the contains of the Map Key method with poor performance,So to create a keySet hereSet<String> beanKeySet = new HashSet<>(beanMap.keySet());Set<String> beanMapHandledSet = new HashSet<>();int maxCellIndex = -1;// If it's a class it needs to be cast by typeif (HeadKindEnum.CLASS.equals(writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadKind())) {Map<Integer, Head> headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap();for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {int columnIndex = entry.getKey();Head head = entry.getValue();String name = head.getFieldName();if (!beanKeySet.contains(name)) {continue;}ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(beanMap,currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), name);CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(writeContext, row, rowIndex, head, columnIndex, relativeRowIndex, Boolean.FALSE,excelContentProperty);WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext);Cell cell = WorkBookUtil.createCell(row, columnIndex);cellWriteHandlerContext.setCell(cell);WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext);cellWriteHandlerContext.setOriginalValue(beanMap.get(name));cellWriteHandlerContext.setOriginalFieldClass(head.getField().getType());converterAndSet(cellWriteHandlerContext);WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext);beanMapHandledSet.add(name);maxCellIndex = Math.max(maxCellIndex, columnIndex);}}// Finishif (beanMapHandledSet.size() == beanMap.size()) {return;}maxCellIndex++;Map<String, Field> ignoreMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getIgnoreMap();initSortedAllFieldMapFieldList(oneRowData.getClass(), sortedAllFieldMap);for (Map.Entry<Integer, Field> entry : sortedAllFieldMap.entrySet()) {Field field = entry.getValue();String fieldName = FieldUtils.resolveCglibFieldName(field);boolean uselessData = !beanKeySet.contains(fieldName) || beanMapHandledSet.contains(fieldName)|| ignoreMap.containsKey(fieldName);if (uselessData) {continue;}Object value = beanMap.get(fieldName);ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(beanMap,currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), fieldName);CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(writeContext, row, rowIndex, null, maxCellIndex, relativeRowIndex, Boolean.FALSE, excelContentProperty);WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext);// fix https://github.com/alibaba/easyexcel/issues/1870// If there is data, it is written to the next cellCell cell = WorkBookUtil.createCell(row, maxCellIndex);cellWriteHandlerContext.setCell(cell);WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext);cellWriteHandlerContext.setOriginalValue(value);cellWriteHandlerContext.setOriginalFieldClass(FieldUtils.getFieldClass(beanMap, fieldName, value));converterAndSet(cellWriteHandlerContext);WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext);maxCellIndex++;}}

AbstractExcelWriteExecutor#doConvert,获取对应的属性上的转换器,如果获取不到,获取converterMap默认的转换器,调用转换器转换。

    private WriteCellData<?> doConvert(CellWriteHandlerContext cellWriteHandlerContext) {ExcelContentProperty excelContentProperty = cellWriteHandlerContext.getExcelContentProperty();Converter<?> converter = null;if (excelContentProperty != null) {converter = excelContentProperty.getConverter();}if (converter == null) {// csv is converted to string by defaultif (writeContext.writeWorkbookHolder().getExcelType() == ExcelTypeEnum.CSV) {cellWriteHandlerContext.setTargetCellDataType(CellDataTypeEnum.STRING);}converter = writeContext.currentWriteHolder().converterMap().get(ConverterKeyBuild.buildKey(cellWriteHandlerContext.getOriginalFieldClass(),cellWriteHandlerContext.getTargetCellDataType()));}if (cellWriteHandlerContext.getOriginalValue() == null && !(converter instanceof NullableObjectConverter)) {return new WriteCellData<>(CellDataTypeEnum.EMPTY);}if (converter == null) {throw new ExcelWriteDataConvertException(cellWriteHandlerContext,"Can not find 'Converter' support class " + cellWriteHandlerContext.getOriginalFieldClass().getSimpleName() + ".");}WriteCellData<?> cellData;try {cellData = ((Converter<Object>)converter).convertToExcelData(new WriteConverterContext<>(cellWriteHandlerContext.getOriginalValue(), excelContentProperty,writeContext));} catch (Exception e) {throw new ExcelWriteDataConvertException(cellWriteHandlerContext,"Convert data:" + cellWriteHandlerContext.getOriginalValue() + " error, at row:"+ cellWriteHandlerContext.getRowIndex(), e);}if (cellData == null || cellData.getType() == null) {throw new ExcelWriteDataConvertException(cellWriteHandlerContext,"Convert data:" + cellWriteHandlerContext.getOriginalValue()+ " return is null or return type is null, at row:"+ cellWriteHandlerContext.getRowIndex());}return cellData;}

在这里插入图片描述


http://www.ppmy.cn/news/93920.html

相关文章

图灵完备游戏:信号计数 解法记录

使用1个全加器 2个半加器完成。这关的思想主旨在于如何把输出4&#xff0c;输出2&#xff0c;输出1的情况统一在一根导线上。 首先用一个全加器来完成输入2-4这三个引脚的计数&#xff0c;因为全加器输出范围二进制是00 - 11&#xff0c;而输入正好有两个引脚数位是2和1&…

spring概述

什么是spring? Spring是一个轻量级Java开发框架&#xff0c;最早有Rod Johnson创建&#xff0c;目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack&#xff08;一站式&#xff09;轻量级开源框架&#xff0c;为开发Java…

15.二分法

一、算法内容 1.简介 二分法是一种基础但非常精妙的算法&#xff0c;经常能为我们打开解题的思路&#xff0c;也常常作为题目的其中一个重要环节出现。二分的基本用法就是在一个单调序列或单调函数中进行参照点&#xff08;中心点&#xff09;的移动。通过不断尝试并每次缩小…

Dock的安装和使用

1、docker基础 三大组件: 仓库、镜像、容器什么是docker: 通俗来讲就是提供服务的容器Docker 两个概念:容器:可以看做空间 例如:磁盘、文件夹 镜像:灵魂 例如:系统、应用 一个镜像可以放在多个容器中(就如同把同一个文件复制到多个磁盘或文件夹一样) 一个容器可以放多个镜…

C# 复制、移动、删除文件,获取文件信息

文章目录 文件操作链接文件属性 File类提供了常见的文件操作函数&#xff0c;包括复制、移动、删除、创建快捷方式等整个文件的操作以及读取、设置文件属性等功能。 文件操作 我们每天都要和文件打交道&#xff0c;常见的文件操作无非是创建、复制、移动、删除以及创建快捷方式…

DTW 2023:戴尔发力多云战略与边缘运营

近日&#xff0c;2023戴尔科技全球科技大会&#xff08;Dell Technologies World&#xff0c;简称DTW&#xff09;在美国拉斯维加斯如期而至。 作为戴尔科技集团一年一度的科技盛宴&#xff0c;本届DTW吸引了众多业界人士的关注。而作为本届大会的重头戏&#xff0c;戴尔科技集…

面试官:你会从哪些维度进行MySQL性能优化?

面试官如果问你&#xff1a;你会从哪些维度进行MySQL性能优化&#xff1f;你会怎么回答&#xff1f; 所谓的性能优化&#xff0c;一般针对的是MySQL查询的优化。既然是优化查询&#xff0c;我们自然要先知道查询操作要经过哪些环节&#xff0c;然后思考可以在哪些环节进行优化…

Python突破某网游游戏JS加密限制,进行逆向解密,实现自动登录

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 今天来分享一下如何使用Python突破某网游游戏JS加密限制&#xff0c;进行逆向解密&#xff0c;实现自动登录。 逆向目标 目标&#xff1a;某 7 网游登录 主页&#xff1a;aHR0cHM6Ly93d3cuMzcuY29tLw 接口&#xff1a;aHR…