• 聊聊Excel的大批量导入导出


    几乎每个后台管理系统都难逃excel导入和导出的功能,目前针对大数据量的导入和导出多数是使用EasyExcel,EasyExcel极大地简单了java对excel的读写操作,从pom的依赖上知道easyexcel依赖POI,那么EasyExcel是如何实现高性能的excel读写操作呢,实现上和POI有什么区别和联系呢?

    要讲EasyExcel,必须先讲一下excel版本和POI。

    关于Excel版本

    版本

    最大数据量

    后缀

    文档类型

    解压后的文件结构

    2003

    65535

    .xls

    复合文档

    2007

    1048575

    .xlsx

    XML文档

    Excel2007及以上版本,其文件由多个xml文件打包组成,其存数据的文件位于xl/worksheets/下,一个sheet以一个xml文件的形成存在,而正是由于这个文件结构,java可凭借其丰富强大的xml解析类库,实现对xml文件的流式追加写入和流式读取,避免全量读入数据而内存溢出,使读取大批量数据成为可能。

    POI

    可用于操作Microsoft Office的相关文件(可用于excel,word,ppt等)的一套程序库,apache出品。POI用于操作Excel的对象都是实现了Workbook接口的类.

    Workboot是创建Excel对象或Sheet等的顶级对象,主要的子类有:E

    实现类

    Excel版本

    特点

    HSSFWorkbook

    2003及以前

    对象常驻内存,易OOM

    XSSFWorkbook

    2007及以后

    对象常驻内存,易OOM

    SXSSFWorkbook

    2007及以后

    流式的XSSFWorkbook,

    有效避免OOM,仅用于写

    这三套实现,还对应着各自的家族成员

    工作簿HSSFWorkbookXSSFWorkbookSXSSFWorkbook
    SheetHSSFSheetXSSFSheetSXSSFSheet
    HSSFRowXSSFRowSXSSFRow
    单元格HSSFCellXSSFCellSXSSFCell

    其中HSSFWorkbook和XSSFWorkbook对Excel的处理和解析是非常吃内存的,每个单元格都是一个 Cell对象,每个Cell对象又有CellStyle对象,想想1个10列*10W行,单单它的Cell对象就已经有100W个,如果需要全部加载进内存,无疑是程序的灾难,于是SXSSFWorkbook随之出现。

    SXSSFWorkbook

    SXSSFWorkbook是streaming版本的XSSFWorkbook,它只会保存最新的excel rows(默认100行)在内存里供查看,在此之前的excel rows都会被写入到硬盘里。被写入到硬盘里的rows对程序而言是不可见的,详见:

    SXSSFSheet#createRow

    1. public SXSSFRow createRow(int rownum) {
    2. int maxrow = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
    3. if (rownum < 0 || rownum > maxrow) {
    4. throw new IllegalArgumentException("Invalid row number (" + rownum
    5. + ") outside allowable range (0.." + maxrow + ")");
    6. }
    7. // attempt to overwrite a row that is already flushed to disk
    8. if(rownum <= _writer.getLastFlushedRow() ) {
    9. throw new IllegalArgumentException(
    10. "Attempting to write a row["+rownum+"] " +
    11. "in the range [0," + _writer.getLastFlushedRow() + "] that is already written to disk.");
    12. }
    13. // attempt to overwrite a existing row in the input template
    14. if(_sh.getPhysicalNumberOfRows() > 0 && rownum <= _sh.getLastRowNum() ) {
    15. throw new IllegalArgumentException(
    16. "Attempting to write a row["+rownum+"] " +
    17. "in the range [0," + _sh.getLastRowNum() + "] that is already written to disk.");
    18. }
    19. SXSSFRow newRow = new SXSSFRow(this);
    20. _rows.put(rownum, newRow);
    21. allFlushed = false;
    22. if(_randomAccessWindowSize >= 0 && _rows.size() > _randomAccessWindowSize) {
    23. try {
    24. //如果写入的行数>_randomAccessWindowSize(默认100),则将老的记录写入磁盘
    25. flushRows(_randomAccessWindowSize);
    26. } catch (IOException ioe) {
    27. throw new RuntimeException(ioe);
    28. }
    29. }
    30. return newRow;
    31. }

    在创建新行的过程中,SXSSFSheet通过flushRows()将多于随机读取窗口大小的Row写入磁盘的临时文件中,同时从内存中移除,从而实现内存减负,避免OOM的出现。

    原生POI,通过SXSSF系列API,可以实现100W数据无压力输出(在-Xmx20M下即可运行)

    POI版的导出程序(DEMO)

    1. import java.io.File;
    2. import java.io.FileOutputStream;
    3. import java.io.IOException;
    4. import java.lang.reflect.Field;
    5. import java.util.Collection;
    6. import org.apache.commons.beanutils.BeanUtils;
    7. import org.apache.poi.ss.usermodel.Row;
    8. import org.apache.poi.xssf.streaming.SXSSFSheet;
    9. import org.apache.poi.xssf.streaming.SXSSFWorkbook;
    10. import com.alibaba.excel.annotation.ExcelProperty;
    11. public class PoiWriter {
    12. private SXSSFWorkbook workbook = null;
    13. private SXSSFSheet sheet = null;
    14. private Class head = null;
    15. private boolean headerInited = false;
    16. private int rowNum = 0;
    17. public PoiWriter(Class head) {
    18. this.workbook = new SXSSFWorkbook();
    19. this.sheet = workbook.createSheet("sheet0");
    20. this.head = head;
    21. }
    22. public void write(Collection dataset) throws Exception {
    23. initHeader();
    24. for (T object : dataset) {
    25. writeRow(object);
    26. }
    27. }
    28. private void writeRow(T object) throws Exception {
    29. Field[] fields = head.getDeclaredFields();
    30. Row row = sheet.createRow(rowNum ++);
    31. int index = 0;
    32. for (Field field : fields) {
    33. String value = BeanUtils.getProperty(object, field.getName());
    34. row.createCell(index ++).setCellValue(value);
    35. }
    36. }
    37. public void flush(String fileName) throws IOException {
    38. FileOutputStream fos = new FileOutputStream(new File(fileName));
    39. workbook.write(fos);
    40. fos.close();
    41. }
    42. private void initHeader() {
    43. if(!headerInited) {
    44. createHeader();
    45. headerInited = true;
    46. }
    47. }
    48. private void createHeader() {
    49. Field[] fields = head.getDeclaredFields();
    50. int index = 0;
    51. Row row = sheet.createRow(rowNum ++);
    52. for (Field field : fields) {
    53. ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
    54. String columnName = excelProperty != null ? excelProperty.value()[0] : field.getName();
    55. row.createCell(index ++).setCellValue(columnName);
    56. }
    57. }
    58. }
    1. public class PoiWriterTest {
    2. @Test
    3. public void write() throws Exception {
    4. PoiWriter poiWriter = new PoiWriter<>(Student.class);
    5. int pageSize = 1000;
    6. for (int pageNo = 1; pageNo <= 100; pageNo++) {
    7. poiWriter.write(pageQuery(pageNo, pageSize));
    8. }
    9. poiWriter.flush("D:/stu.xlsx");
    10. }
    11. //模拟分页查询
    12. private List pageQuery(int pageNo, int pageSize) {
    13. List students = new ArrayList<>();
    14. for (int i = 0; i < pageSize; i++) {
    15. int index = (pageNo - 1) * pageSize + i;
    16. students.add(build(index));
    17. }
    18. return students;
    19. }
    20. private Student build(int index) {
    21. Student s = new Student();
    22. s.setName("学生" + index);
    23. s.setAddress("地址" + index);
    24. s.setAge(String.valueOf(RandomUtils.nextInt(20, 30)));
    25. s.setClazz("班别" + index % 8);
    26. s.setGrade("年级" + index % 6);
    27. s.setTel(String.valueOf(13500000000L + RandomUtils.nextInt(1, 10000000)));
    28. s.setChinese(String.valueOf(RandomUtils.nextInt(50, 100)));
    29. s.setEnglish(String.valueOf(RandomUtils.nextInt(50, 100)));
    30. return s;
    31. }
    32. }
    1. @Data
    2. public class Student {
    3. @ExcelProperty(value="英语")
    4. private String english;
    5. @ExcelProperty(value="姓名")
    6. private String name;
    7. @ExcelProperty(value="年龄")
    8. private String age;
    9. @ExcelProperty(value="学校")
    10. private String school;
    11. @ExcelProperty(value="年级")
    12. private String grade;
    13. @ExcelProperty(value="班别")
    14. private String clazz;
    15. @ExcelProperty(value="地址")
    16. private String address;
    17. @ExcelProperty(value="电话")
    18. private String tel;
    19. @ExcelProperty(value="语文")
    20. private String chinese;
    21. @ExcelProperty(value="数学")
    22. private String math;
    23. }

    注意,这时的写到磁盘,只是当前用户的一个临时目录,是一个xml文件,里面记录着行的内容,格式化后形如:

    1. <row r="1">
    2. <c r="A1" s="1" t="inlineStr">
    3. <is>
    4. <t>内容1t>
    5. is>
    6. c>
    7. <c r="B1" s="1" t="inlineStr">
    8. <is>
    9. <t>内容2t>
    10. is>
    11. c>
    12. row>

    表示一行,表示一个单元格,记录单元格的内容,其它各自脑补。

    SXSSFWorkbook只实现了流式写,并没有实现流式读,流式读取excel需要通过Sax模式的事件API进行实现,该核心实现如下:

    POI版Excel解析(DEMO)

    1. public class PoiSaxReader {
    2. public void read(String path, AbstractSheetHandler handler) throws Exception {
    3. // 1.根据excel报表获取OPCPackage
    4. OPCPackage opcPackage = OPCPackage.open(path, PackageAccess.READ);
    5. // 2.创建XSSFReader
    6. XSSFReader reader = new XSSFReader(opcPackage);
    7. // 3.获取SharedStringTable对象
    8. SharedStringsTable table = reader.getSharedStringsTable();
    9. // 4.获取styleTable对象
    10. StylesTable stylesTable = reader.getStylesTable();
    11. // 5.创建Sax的xmlReader对象
    12. XMLReader xmlReader = XMLReaderFactory.createXMLReader();
    13. // 6.注册事件处理器
    14. XSSFSheetXMLHandler xmlHandler = new XSSFSheetXMLHandler(stylesTable, table, handler, false);
    15. xmlReader.setContentHandler(xmlHandler);
    16. // 7.第一个sheet的流数据
    17. XSSFReader.SheetIterator sheetIterator = (XSSFReader.SheetIterator) reader.getSheetsData();
    18. InputStream stream = sheetIterator.next();
    19. InputSource is = new InputSource(stream);
    20. // 8.最关键:执行xml解析,解析过程中会回调XSSFSheetXMLHandler的startElement等方法
    21. xmlReader.parse(is);
    22. }
    23. static class StudentHandler extends AbstractSheetHandler {
    24. private int total = 0;
    25. @Override
    26. protected void invoke(Student object) {
    27. total += 1;
    28. if(total % 1000 == 0) {
    29. System.out.println("读取到:"+total);
    30. System.out.println(object);
    31. }
    32. }
    33. }
    34. public static void main(String[] args) throws Exception {
    35. PoiSaxReader reader = new PoiSaxReader();
    36. reader.read("D:/stu_100W.xlsx", new StudentHandler());
    37. }
    38. }
    1. /**
    2. * 基于Sax的解析处理器抽象
    3. */
    4. public abstract class AbstractSheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {
    5. // 将输出到excel的对象
    6. private T object;
    7. private Class clazz;
    8. @SuppressWarnings("unchecked")
    9. public AbstractSheetHandler() {
    10. Type genType = getClass().getGenericSuperclass();
    11. Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
    12. this.clazz = (Class) params[0];
    13. }
    14. /**
    15. * 当开始解析某一行的时候触发
    16. */
    17. @Override
    18. public void startRow(int i) {
    19. try {
    20. object = clazz.newInstance();
    21. } catch (InstantiationException | IllegalAccessException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. /**
    26. * 当结束解析某一行的时候触发
    27. */
    28. @Override
    29. public void endRow(int i) {
    30. invoke(object);
    31. }
    32. protected abstract void invoke(T object);
    33. /**
    34. * cellReference:单元格引用,形如A1,B1
    35. */
    36. @Override
    37. public void cell(String cellReference, String value, XSSFComment xssfComment) {
    38. Field[] fields = clazz.getDeclaredFields();
    39. int columnIndex = new CellReference(cellReference).getCol();
    40. try {
    41. BeanUtils.setProperty(object, fields[columnIndex].getName(), value);
    42. } catch (IllegalAccessException | InvocationTargetException e) {
    43. e.printStackTrace();
    44. }
    45. }
    46. }

    以上程序,同样在-Xmx20M下可以成功运行。该程序只是孤立地对每一条数据进行处理,真实情况下程序可能需要拿到所有行的完整视图,如果需要将所有对象放进一个List中,那所消耗的内存当然远不止20M,但这种只能说是业务消耗的内存,而非POI消耗的内存。

    上面用到的一些类可能都相对陌生,其核心逻辑就是解压xlsx文件,然后通过sax事件驱动模式对excel数据(即xml)进行逐行解释。这里重要的一个点是XSSFSheetXMLHandler 

    XSSFSheetXMLHandler

    该类继承自DefaultHandler,XMLReader执行解析的过程中,会回调其startElement/endElement/characters等方法,后面说到的easyexcel,其解析过程是easyexcel自己搞一个XlsxRowHandler。

    原生的POI虽然可以实现大数据量的导入和导出,但对于业务开发人员来说,使用起来还是没那么友好,特别是在处理大数据量导入的时候,EasyExcel基于POI,主要解决易用度的问题,同时也对POI使用Sax事件API进行数据行解释那一块进行了重写,减少了内存消耗。见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模式,在上层做了模型转换的封装,让使用者更加简单方便

    EasyExcel

    读Excel

    1. @Test
    2. public void read() {
    3. EasyExcel.read("D:/stu_100W.xlsx", Student.class, new PageReadListener(dataList -> {
    4. log.info("读取到:{}", dataList.size());
    5. })).sheet().doRead();
    6. }

    EasyExcel的API确实很简洁,一波debug跟进,来到XlsxSaxAnalyser#execute

    XlsxSaxAnalyser#execute

    1. @Override
    2. public void execute() {
    3. for (ReadSheet readSheet : sheetList) {
    4. readSheet = SheetUtils.match(readSheet, xlsxReadContext);
    5. if (readSheet != null) {
    6. xlsxReadContext.currentSheet(readSheet);
    7. // 注意这个 XlsxRowHandler
    8. parseXmlSource(sheetMap.get(readSheet.getSheetNo()), new XlsxRowHandler(xlsxReadContext));
    9. readComments(readSheet);
    10. xlsxReadContext.analysisEventProcessor().endSheet(xlsxReadContext);
    11. }
    12. }
    13. }
    14. private void parseXmlSource(InputStream inputStream, ContentHandler handler) {
    15. InputSource inputSource = new InputSource(inputStream);
    16. try {
    17. SAXParserFactory saxFactory;
    18. String xlsxSAXParserFactoryName = xlsxReadContext.xlsxReadWorkbookHolder().getSaxParserFactoryName();
    19. if (StringUtils.isEmpty(xlsxSAXParserFactoryName)) {
    20. saxFactory = SAXParserFactory.newInstance();
    21. } else {
    22. saxFactory = SAXParserFactory.newInstance(xlsxSAXParserFactoryName, null);
    23. }
    24. try {
    25. saxFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    26. } catch (Throwable ignore) {}
    27. try {
    28. saxFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    29. } catch (Throwable ignore) {}
    30. try {
    31. saxFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    32. } catch (Throwable ignore) {}
    33. SAXParser saxParser = saxFactory.newSAXParser();
    34. XMLReader xmlReader = saxParser.getXMLReader();
    35. xmlReader.setContentHandler(handler);
    36. //和POI一样的套路
    37. xmlReader.parse(inputSource);
    38. inputStream.close();
    39. } catch (IOException | ParserConfigurationException | SAXException e) {
    40. throw new ExcelAnalysisException(e);
    41. } finally {
    42. if (inputStream != null) {
    43. try {
    44. inputStream.close();
    45. } catch (IOException e) {
    46. throw new ExcelAnalysisException("Can not close 'inputStream'!");
    47. }
    48. }
    49. }
    50. }

     XlsxRowHandler

    1. public class XlsxRowHandler extends DefaultHandler {
    2. private final XlsxReadContext xlsxReadContext;
    3. private static final Map XLSX_CELL_HANDLER_MAP = new HashMap(32);
    4. static {
    5. CellFormulaTagHandler cellFormulaTagHandler = new CellFormulaTagHandler();
    6. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_FORMULA_TAG, cellFormulaTagHandler);
    7. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_FORMULA_TAG, cellFormulaTagHandler);
    8. CellInlineStringValueTagHandler cellInlineStringValueTagHandler = new CellInlineStringValueTagHandler();
    9. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_INLINE_STRING_VALUE_TAG, cellInlineStringValueTagHandler);
    10. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_INLINE_STRING_VALUE_TAG, cellInlineStringValueTagHandler);
    11. CellTagHandler cellTagHandler = new CellTagHandler();
    12. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_TAG, cellTagHandler);
    13. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_TAG, cellTagHandler);
    14. CellValueTagHandler cellValueTagHandler = new CellValueTagHandler();
    15. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_VALUE_TAG, cellValueTagHandler);
    16. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_VALUE_TAG, cellValueTagHandler);
    17. CountTagHandler countTagHandler = new CountTagHandler();
    18. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.DIMENSION_TAG, countTagHandler);
    19. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_DIMENSION_TAG, countTagHandler);
    20. HyperlinkTagHandler hyperlinkTagHandler = new HyperlinkTagHandler();
    21. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.HYPERLINK_TAG, hyperlinkTagHandler);
    22. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_HYPERLINK_TAG, hyperlinkTagHandler);
    23. MergeCellTagHandler mergeCellTagHandler = new MergeCellTagHandler();
    24. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.MERGE_CELL_TAG, mergeCellTagHandler);
    25. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_MERGE_CELL_TAG, mergeCellTagHandler);
    26. RowTagHandler rowTagHandler = new RowTagHandler();
    27. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.ROW_TAG, rowTagHandler);
    28. XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_ROW_TAG, rowTagHandler);
    29. }
    30. public XlsxRowHandler(XlsxReadContext xlsxReadContext) {
    31. this.xlsxReadContext = xlsxReadContext;
    32. }
    33. @Override
    34. public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
    35. XlsxTagHandler handler = XLSX_CELL_HANDLER_MAP.get(name);
    36. if (handler == null || !handler.support(xlsxReadContext)) {
    37. return;
    38. }
    39. xlsxReadContext.xlsxReadSheetHolder().getTagDeque().push(name);
    40. handler.startElement(xlsxReadContext, name, attributes);
    41. }
    42. @Override
    43. public void characters(char[] ch, int start, int length) throws SAXException {
    44. String currentTag = xlsxReadContext.xlsxReadSheetHolder().getTagDeque().peek();
    45. if (currentTag == null) {
    46. return;
    47. }
    48. XlsxTagHandler handler = XLSX_CELL_HANDLER_MAP.get(currentTag);
    49. if (handler == null || !handler.support(xlsxReadContext)) {
    50. return;
    51. }
    52. handler.characters(xlsxReadContext, ch, start, length);
    53. }
    54. @Override
    55. public void endElement(String uri, String localName, String name) throws SAXException {
    56. XlsxTagHandler handler = XLSX_CELL_HANDLER_MAP.get(name);
    57. if (handler == null || !handler.support(xlsxReadContext)) {
    58. return;
    59. }
    60. handler.endElement(xlsxReadContext, name);
    61. xlsxReadContext.xlsxReadSheetHolder().getTagDeque().pop();
    62. }
    63. }

    相比于XSSFSheetXMLHandler,XlsxRowHandler维护一个XlsxTagHandler的集合,XlsxRowHandler充当一个门面,解析逻辑交给各个具体的XlsxTagHandler

    XlsxTagHandler

    easyexcel实现了如下的XlsxTagHandler:

     其中CellTagHandler解析元素,CellValueTagHandler和CellInlineStringValueTagHandler解析等元素,提取出单元格内容,这里不做过多阐述。

    写Excel

    1. @Test
    2. public void export() {
    3. ExcelWriter excelWriter = EasyExcel.write("D:/student_100W.xlsx", Student.class).build();
    4. WriteSheet sheet0 = EasyExcel.writerSheet("sheet0").build();
    5. for (int pageNo = 1; pageNo <= 2; pageNo++) {
    6. excelWriter.write(studentService.pageQuery(pageNo, pageSize), sheet0);
    7. }
    8. excelWriter.finish();
    9. }

    EasyExcel的POI进行了良好的封装,简单易用,API在使用上很符合大家使用日常操作excel的习惯:

     

    得益于POI的SXSSFSheet.createRow时,会进行自动将老的Row进行刷盘操作,EasyExcel只在SXSSF家族上面做一个封装,即可实现大数据量的写入操作。主要代码见:

    ExcelWriteAddExecutor#add

    1. public void add(Collection data) {
    2. if (CollectionUtils.isEmpty(data)) {
    3. data = new ArrayList<>();
    4. }
    5. WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder();
    6. int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite();
    7. if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) {
    8. newRowIndex += writeContext.currentWriteHolder().relativeHeadRowIndex();
    9. }
    10. // BeanMap is out of order, so use sortedAllFieldMap
    11. Map sortedAllFieldMap = new TreeMap<>();
    12. int relativeRowIndex = 0;
    13. for (Object oneRowData : data) {
    14. int lastRowIndex = relativeRowIndex + newRowIndex;
    15. addOneRowOfDataToExcel(oneRowData, lastRowIndex, relativeRowIndex, sortedAllFieldMap);
    16. relativeRowIndex++;
    17. }
    18. }
    19. //向Excel写入一行
    20. private void addOneRowOfDataToExcel(Object oneRowData, int rowIndex, int relativeRowIndex,
    21. Map sortedAllFieldMap) {
    22. if (oneRowData == null) {
    23. return;
    24. }
    25. RowWriteHandlerContext rowWriteHandlerContext = WriteHandlerUtils.createRowWriteHandlerContext(writeContext,
    26. rowIndex, relativeRowIndex, Boolean.FALSE);
    27. WriteHandlerUtils.beforeRowCreate(rowWriteHandlerContext);
    28. //这里调用:SXSSFSheet#createRow
    29. Row row = WorkBookUtil.createRow(writeContext.writeSheetHolder().getSheet(), rowIndex);
    30. rowWriteHandlerContext.setRow(row);
    31. WriteHandlerUtils.afterRowCreate(rowWriteHandlerContext);
    32. if (oneRowData instanceof Collection) {
    33. addBasicTypeToExcel(new CollectionRowData((Collection)oneRowData), row, rowIndex, relativeRowIndex);
    34. } else if (oneRowData instanceof Map) {
    35. addBasicTypeToExcel(new MapRowData((Map)oneRowData), row, rowIndex, relativeRowIndex);
    36. } else {
    37. addJavaObjectToExcel(oneRowData, row, rowIndex, relativeRowIndex, sortedAllFieldMap);
    38. }
    39. WriteHandlerUtils.afterRowDispose(rowWriteHandlerContext);
    40. }

    总结:

    1.EasyExcel的写,借助于SXSSFSheet.createRow解决导出时的内存消耗问题

    2.EasyExcel的读,自己实现了DefaultHandler,对Excel文件包中xml数据文件进行流式解释,解决读时的内存消耗问题。

    by simple

  • 相关阅读:
    哈希表的实现
    php查询大量sql语句时内存溢出的解决方法
    Python并发编程之托管对象
    【设计模式】3种工厂模式
    ST表倒序释放:1019T1
    C高级作业 【使用shell脚本】 实现一个对数组求和的函数,数组通过实参传递给函数+写一个函数输出当前用户的uid和gid,并使用变量接收结果
    获取系统信息==Linux应用编程3
    ReentrantLock源码浅析
    【算法优选】双指针专题——贰
    使用 乐天 / V-IM 作为网页即时聊天
  • 原文地址:https://blog.csdn.net/vipshop_fin_dev/article/details/126697159