POI與easyExcel的區別:java
、web
POI是經過WorkBook來解析表格文件的,雖然內部有使用到Sax模式,能後大大的提升效率,可是要解析大文件(10w,100w行)的話很容易出現OOM(內存溢出)。
相比之下,
一、easyExcel解析實在磁盤上進行的,幾乎能夠將幾mb級別的內存壓縮到kb級別,幾乎不用擔憂OOM;
二、用Java模型進行關係映射,項目中最經常使用的就Java模型映射,經過 @ExcelProperty註解就能夠完成行與列的映射;
三、easyExcel中有一個類AnalysisEventListener,裏面有一個方法invoke實現了一行一行返回,另外還能夠重寫該類的doAfterAllAnalysed方法,用來作過後處理之類的操做,至關的靈活。spring
準備階段apache
第一步:引入easyExcel依賴app
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>1.1.2-beta5</version> </dependency>
第二步:自定義監聽器ExcelListener繼承於AnalysisEventListener,重寫invoke()方法(能夠讀取到excel每一行的數據)和doAfterAllAnalysed()方法(用於後置處理),其中datas用於存取讀取到的數據,importHeads爲導入表頭,modelHeadsxss
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
package com.cloud.data.utils; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.metadata.ExcelHeadProperty; import org.springframework.util.StringUtils; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * EasyExcell導入監聽類 */ public class ExcelListener extends AnalysisEventListener { // 自定義用於暫時存儲數據 private List<Object> datas = new ArrayList<>(); // 導入表頭 private String importHeads = ""; // 模版表頭 private String modelHeads = ""; /** * 經過 AnalysisContext對象獲取當前sheet,當前行等數據 */ @Override public void invoke(Object o, AnalysisContext analysisContext) { Integer currentRowNum = analysisContext.getCurrentRowNum(); // 獲取導入表頭,默認第一行爲表頭 if(currentRowNum == 0){ try { Map<String,Object> m = objToMap(o); for (Object v : m.values()) { if(!StringUtils.isEmpty(v)){ importHeads += String.valueOf(v).trim() + ","; } } } catch (Exception e) { e.printStackTrace(); } }else{ datas.add(o); } } /** * 監聽器獲取模板表頭 */ @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { // 獲取模版表頭 ExcelHeadProperty ehp = analysisContext.getExcelHeadProperty(); for(List<String> s : ehp.getHead()){ modelHeads += s.get(0) + ","; } } // Object轉換爲Map private Map<String,Object> objToMap(Object obj) throws Exception{ Map<String,Object> map = new LinkedHashMap<String, Object>(); Field[] fields = obj.getClass().getDeclaredFields(); for(Field field : fields){ field.setAccessible(true); map.put(field.getName(), field.get(obj)); } return map; } public List<Object> getDatas() { return datas; } public void setDatas(List<Object> datas) { this.datas = datas; } public String getImportHeads() { return importHeads; } public void setImportHeads(String importHeads) { this.importHeads = importHeads; } public String getModelHeads() { return modelHeads; } public void setModelHeads(String modelHeads) { this.modelHeads = modelHeads; } }
第三步:添加接收excel導入的實體類OutDbillDto,繼承BaseRowModel,其中@ExcelProperty(value = "機組調度名稱(必填)", index = 0)對應excel表頭的(value爲表頭名稱,index爲索引位置)ide
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
package com.cloud.data.entity.dto; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.metadata.BaseRowModel; import lombok.Data; import java.io.Serializable; /** * 日結算帳單導入類 */ @Data public class OutDbillDto extends BaseRowModel implements Serializable { @ExcelProperty(value = "機組調度名稱(必填)", index = 0) private String unitDispatchName; @ExcelProperty(value = "日期(必填)", index = 1) private String billTime; @ExcelProperty(value = "收費項目", index = 2) private String project; @ExcelProperty(value = "本期電量(萬kWh)", index = 3) private String quan; @ExcelProperty(value = "單價(釐/kWh)", index = 4) private String avgPrice; @ExcelProperty(value = "本期電費(元)",index = 5) private String price; /**標記*/ private String mark; /**錯誤信息*/ private String errMsg; }
第四步,定義EasyExcelUtil工具類,用於讀取excel工具
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
package com.cloud.data.utils; import com.alibaba.excel.EasyExcelFactory; import com.alibaba.excel.ExcelReader; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.metadata.BaseRowModel; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.support.ExcelTypeEnum; import com.cloud.frame.util.DateUtil; import com.google.common.collect.Lists; import org.apache.commons.beanutils.BeanUtils; import org.apache.poi.ss.formula.functions.T; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; public class EasyExcelUtil { /** * 讀取某個 sheet 的 Excel * * @param excel 文件 * @param rowModel 實體類映射,繼承 BaseRowModel 類 * @return Excel 數據 list */ public static List<Object> readExcel(MultipartFile excel, BaseRowModel rowModel) throws IOException { return readExcel(excel, rowModel, 1, 1); } /** * 讀取某個 sheet 的 Excel * @param excel 文件 * @param rowModel 實體類映射,繼承 BaseRowModel 類 * @param sheetNo sheet 的序號 從1開始 * @return Excel 數據 list */ public static Map<String,Object> readExcel(MultipartFile excel, BaseRowModel rowModel, int sheetNo) throws IOException { Map<String,Object> result = new HashMap<>(); ExcelListener excelListener = new ExcelListener(); ExcelReader reader = getReader(excel, excelListener); if (Objects.isNull(reader)) { return null; } reader.read(new Sheet(sheetNo, 0, rowModel.getClass())); //校驗表頭 Boolean flag = false; if(excelListener.getImportHeads().equals(excelListener.getModelHeads())){ flag = true; } result.put("flag", flag); result.put("datas", excelListener.getDatas()); return result; } /** * 讀取某個 sheet 的 Excel * @param excel 文件 * @param rowModel 實體類映射,繼承 BaseRowModel 類 * @param sheetNo sheet 的序號 從1開始 * @param headLineNum 表頭行數,默認爲1 * @return Excel 數據 list */ public static List<Object> readExcel(MultipartFile excel, BaseRowModel rowModel, int sheetNo, int headLineNum) throws IOException { ExcelListener excelListener = new ExcelListener(); ExcelReader reader = getReader(excel, excelListener); if (reader == null) { return null; } reader.read(new Sheet(sheetNo, headLineNum, rowModel.getClass())); return excelListener.getDatas(); } /** * 讀取指定sheetName的Excel(多個 sheet) * @param excel 文件 * @param rowModel 實體類映射,繼承 BaseRowModel 類 * @return Excel 數據 list * @throws IOException */ public static List<Object> readExcel(MultipartFile excel, BaseRowModel rowModel,String sheetName) throws IOException { ExcelListener excelListener = new ExcelListener(); ExcelReader reader = getReader(excel, excelListener); if (reader == null) { return null; } for (Sheet sheet : reader.getSheets()) { if (rowModel != null) { sheet.setClazz(rowModel.getClass()); } //讀取指定名稱的sheet if(sheet.getSheetName().contains(sheetName)){ reader.read(sheet); break; } } return excelListener.getDatas(); } /** * 返回 ExcelReader * @param excel 須要解析的 Excel 文件 * @param excelListener new ExcelListener() * @throws IOException */ private static ExcelReader getReader(MultipartFile excel,ExcelListener excelListener) throws IOException { String filename = excel.getOriginalFilename(); if(Objects.nonNull(filename) && (filename.toLowerCase().endsWith(".xls") || filename.toLowerCase().endsWith(".xlsx"))){ InputStream is = new BufferedInputStream(excel.getInputStream()); return new ExcelReader(is, null,null, excelListener, false); }else{ return null; } } /** * 將導入失敗的數據寫到系統指定路徑 * @param failDatas 數據 * @param name 文件名 * @param dir 寫入的路徑 * @param header Excel表頭 * @param columns 須要寫入Excel的對象屬性集合 * @param index 合併單元格索引 * @return * @throws Exception */ public static String saveExcel2Loacl(List<?> failDatas, String name, String dir, String[] header,String[] columns,Integer index) throws Exception { // 1.惟一文件名 String fileName = name + "_" + DateUtil.getNowStr(DateUtil.TIME_FORMAT)+".xls"; Path path= Paths.get(ExcelUtil.FILE_UPLOAD_ROOT, dir).toAbsolutePath(); File file = new File(path.toString()); if (!file.exists()){ file.mkdirs(); } // 2.添加Sheet名 Sheet sheet = new Sheet(1,0); sheet.setSheetName(name); // 3.動態添加Excel表頭 List<List<String>> head = new ArrayList<>(); for (String h : header) { head.add(Lists.newArrayList(h)); } sheet.setHead(head); // 4.寫入數據 List<T> datas = new ArrayList(failDatas); List<List<Object>> data = new ArrayList<>(); for (Object var : datas) { List<Object> objects = Lists.newArrayList(); Arrays.stream(columns).forEach(e ->{ try { String property = BeanUtils.getProperty(var, e); objects.add(property); } catch (Exception err) { err.printStackTrace(); } }); data.add(objects); } FileOutputStream fileOutputStream = new FileOutputStream(Paths.get(ExcelUtil.FILE_UPLOAD_ROOT, dir, fileName).toFile()); ExcelWriter writer = EasyExcelFactory.getWriter(fileOutputStream, ExcelTypeEnum.XLS, true); writer.write1(data,sheet); // 5.合併單元格 for (int i = 1; i <= failDatas.size(); i = i + index) { writer.merge(i,i + index - 1,0,0); writer.merge(i,i + index - 1,1,1); writer.merge(i,i + index - 1,2,2); } writer.finish(); return fileName; } }
執行原理:ui
ExcelAnalyserImpl是解析器的真正實現,整合了v07好人v03,解析的時候會根據getSaxAnalyser來選擇使用哪一種版本的解析器。this
經過ExcelAnalyserImpl()構造方法,將inputstream(也就是file文件)和自定義的監聽器eventListener(繼承於AnalysisEventListener) 存入ExcelAnalyserImpl類的AnalysisContext屬性
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
package com.alibaba.excel.analysis; import com.alibaba.excel.analysis.v03.XlsSaxAnalyser; import com.alibaba.excel.analysis.v07.XlsxSaxAnalyser; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.context.AnalysisContextImpl; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.exception.ExcelAnalysisException; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.modelbuild.ModelBuildEventListener; import com.alibaba.excel.support.ExcelTypeEnum; import java.io.InputStream; import java.util.List; /** * @author jipengfei */ public class ExcelAnalyserImpl implements ExcelAnalyser { private AnalysisContext analysisContext; private BaseSaxAnalyser saxAnalyser; public ExcelAnalyserImpl(InputStream inputStream, ExcelTypeEnum excelTypeEnum, Object custom, AnalysisEventListener eventListener, boolean trim) { analysisContext = new AnalysisContextImpl(inputStream, excelTypeEnum, custom, eventListener, trim); } private BaseSaxAnalyser getSaxAnalyser() { if (saxAnalyser != null) { return this.saxAnalyser; } try { if (analysisContext.getExcelType() != null) { switch (analysisContext.getExcelType()) { case XLS: this.saxAnalyser = new XlsSaxAnalyser(analysisContext); break; case XLSX: this.saxAnalyser = new XlsxSaxAnalyser(analysisContext); break; } } else { try { this.saxAnalyser = new XlsxSaxAnalyser(analysisContext); } catch (Exception e) { if (!analysisContext.getInputStream().markSupported()) { throw new ExcelAnalysisException( "Xls must be available markSupported,you can do like this <code> new " + "BufferedInputStream(new FileInputStream(\"/xxxx\"))</code> "); } this.saxAnalyser = new XlsSaxAnalyser(analysisContext); } } } catch (Exception e) { throw new ExcelAnalysisException("File type error,io must be available markSupported,you can do like " + "this <code> new BufferedInputStream(new FileInputStream(\\\"/xxxx\\\"))</code> \"", e); } return this.saxAnalyser; } @Override public void analysis(Sheet sheetParam) { analysisContext.setCurrentSheet(sheetParam); analysis(); } @Override public void analysis() { BaseSaxAnalyser saxAnalyser = getSaxAnalyser(); appendListeners(saxAnalyser); saxAnalyser.execute(); analysisContext.getEventListener().doAfterAllAnalysed(analysisContext); } @Override public List<Sheet> getSheets() { BaseSaxAnalyser saxAnalyser = getSaxAnalyser(); saxAnalyser.cleanAllListeners(); return saxAnalyser.getSheets(); } private void appendListeners(BaseSaxAnalyser saxAnalyser) { saxAnalyser.cleanAllListeners(); if (analysisContext.getCurrentSheet() != null && analysisContext.getCurrentSheet().getClazz() != null) { saxAnalyser.appendLister("model_build_listener", new ModelBuildEventListener()); } if (analysisContext.getEventListener() != null) { saxAnalyser.appendLister("user_define_listener", analysisContext.getEventListener()); } } }
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
package com.alibaba.excel.analysis; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.event.AnalysisEventRegisterCenter; import com.alibaba.excel.event.OneRowAnalysisFinishEvent; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.util.TypeUtil; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * @author jipengfei */ public abstract class BaseSaxAnalyser implements AnalysisEventRegisterCenter, ExcelAnalyser { protected AnalysisContext analysisContext; private LinkedHashMap<String, AnalysisEventListener> listeners = new LinkedHashMap<String, AnalysisEventListener>(); /** * execute method */ protected abstract void execute(); @Override public void appendLister(String name, AnalysisEventListener listener) { if (!listeners.containsKey(name)) { listeners.put(name, listener); } } @Override public void analysis(Sheet sheetParam) { execute(); } @Override public void analysis() { execute(); } /** */ @Override public void cleanAllListeners() { listeners = new LinkedHashMap<String, AnalysisEventListener>(); } @Override public void notifyListeners(OneRowAnalysisFinishEvent event) { analysisContext.setCurrentRowAnalysisResult(event.getData()); /** Parsing header content **/ if (analysisContext.getCurrentRowNum() < analysisContext.getCurrentSheet().getHeadLineMun()) { if (analysisContext.getCurrentRowNum() <= analysisContext.getCurrentSheet().getHeadLineMun() - 1) { analysisContext.buildExcelHeadProperty(null, (List<String>)analysisContext.getCurrentRowAnalysisResult()); } } else { List<String> content = converter((List<String>)event.getData()); /** Parsing Analyze the body content **/ analysisContext.setCurrentRowAnalysisResult(content); if (listeners.size() == 1) { analysisContext.setCurrentRowAnalysisResult(content); } /** notify all event listeners **/ for (Map.Entry<String, AnalysisEventListener> entry : listeners.entrySet()) { entry.getValue().invoke(analysisContext.getCurrentRowAnalysisResult(), analysisContext); } } } private List<String> converter(List<String> data) { List<String> list = new ArrayList<String>(); if (data != null) { for (String str : data) { list.add(TypeUtil.formatFloat(str)); } } return list; } }
經過ExcelAnalyserImpl類的getSaxAnalyser()方法獲取Excel是.xls仍是.xlsx類型後,將ExcelAnalyserImpl的analysisContext屬性賦給BaseSaxAnalyser類的analysisContext屬性,再用appendListeners()方法封裝默認的監聽器和自定義的監聽器到BaseSaxAnalyser類 的listeners屬性中,到此爲止,BaseSaxAnalyser的兩個屬性:analysisContext、listeners已經分別存有file文件、excel枚舉類型和監聽器等值
接下來說一個DefaultHandler類(核心類),該類是SAX事件解析器,底層在磁盤中解析文件並一行一行的讀取,而且會依次執行startDocument()、startElement()、characters()、endElement()、endDocument()方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
package org.xml.sax.helpers; import java.io.IOException; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.Attributes; import org.xml.sax.EntityResolver; import org.xml.sax.DTDHandler; import org.xml.sax.ContentHandler; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; public class DefaultHandler implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler { public InputSource resolveEntity (String publicId, String systemId) throws IOException, SAXException { return null; } public void notationDecl (String name, String publicId, String systemId) throws SAXException { // no op } public void unparsedEntityDecl (String name, String publicId, String systemId, String notationName) throws SAXException { // no op } public void setDocumentLocator (Locator locator) { // no op } public void startDocument () throws SAXException { // no op } public void endDocument () throws SAXException { // no op } public void startPrefixMapping (String prefix, String uri) throws SAXException { // no op } public void endPrefixMapping (String prefix) throws SAXException { // no op } public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { // no op } public void endElement (String uri, String localName, String qName) throws SAXException { // no op } public void characters (char ch[], int start, int length) throws SAXException { // no op } public void ignorableWhitespace (char ch[], int start, int length) throws SAXException { // no op } public void processingInstruction (String target, String data) throws SAXException { // no op } public void skippedEntity (String name) throws SAXException { // no op } public void warning (SAXParseException e) throws SAXException { // no op } public void error (SAXParseException e) throws SAXException { // no op } public void fatalError (SAXParseException e) throws SAXException { throw e; } } // end of DefaultHandler.java
經過XlsxRowHandler類繼承DefaultHandler類,將每一行讀取的行數和數據分別存入curCol和curRowContent屬性中
讀取完以後,經過後置方法endElement()方法,喚醒最初本身定義的監聽器ExcelListener,並將讀取到的行數、數據等賦給該監聽器
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
package com.alibaba.excel.analysis.v07; import com.alibaba.excel.annotation.FieldType; import com.alibaba.excel.constant.ExcelXmlConstants; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventRegisterCenter; import com.alibaba.excel.event.OneRowAnalysisFinishEvent; import com.alibaba.excel.util.PositionUtils; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.util.Arrays; import static com.alibaba.excel.constant.ExcelXmlConstants.*; /** * * @author jipengfei */ public class XlsxRowHandler extends DefaultHandler { private String currentCellIndex; private FieldType currentCellType; private int curRow; private int curCol; private String[] curRowContent = new String[20]; private String currentCellValue; private SharedStringsTable sst; private AnalysisContext analysisContext; private AnalysisEventRegisterCenter registerCenter; public XlsxRowHandler(AnalysisEventRegisterCenter registerCenter, SharedStringsTable sst, AnalysisContext analysisContext) { this.registerCenter = registerCenter; this.analysisContext = analysisContext; this.sst = sst; } @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { setTotalRowCount(name, attributes); startCell(name, attributes); startCellValue(name); } private void startCellValue(String name) { if (name.equals(CELL_VALUE_TAG) || name.equals(CELL_VALUE_TAG_1)) { // initialize current cell value currentCellValue = ""; } } private void startCell(String name, Attributes attributes) { if (ExcelXmlConstants.CELL_TAG.equals(name)) { currentCellIndex = attributes.getValue(ExcelXmlConstants.POSITION); int nextRow = PositionUtils.getRow(currentCellIndex); if (nextRow > curRow) { curRow = nextRow; // endRow(ROW_TAG); } analysisContext.setCurrentRowNum(curRow); curCol = PositionUtils.getCol(currentCellIndex); String cellType = attributes.getValue("t"); currentCellType = FieldType.EMPTY; if (cellType != null && cellType.equals("s")) { currentCellType = FieldType.STRING; } } } private void endCellValue(String name) throws SAXException { // ensure size if (curCol >= curRowContent.length) { curRowContent = Arrays.copyOf(curRowContent, (int)(curCol * 1.5)); } if (CELL_VALUE_TAG.equals(name)) { switch (currentCellType) { case STRING: int idx = Integer.parseInt(currentCellValue); currentCellValue = new XSSFRichTextString(sst.getEntryAt(idx)).toString(); currentCellType = FieldType.EMPTY; break; } curRowContent[curCol] = currentCellValue; } else if (CELL_VALUE_TAG_1.equals(name)) { curRowContent[curCol] = currentCellValue; } } @Override public void endElement(String uri, String localName, String name) throws SAXException { endRow(name); endCellValue(name); } @Override public void characters(char[] ch, int start, int length) throws SAXException { currentCellValue += new String(ch, start, length); } private void setTotalRowCount(String name, Attributes attributes) { if (DIMENSION.equals(name)) { String d = attributes.getValue(DIMENSION_REF); String totalStr = d.substring(d.indexOf(":") + 1, d.length()); String c = totalStr.toUpperCase().replaceAll("[A-Z]", ""); analysisContext.setTotalCount(Integer.parseInt(c)); } } private void endRow(String name) { if (name.equals(ROW_TAG)) { registerCenter.notifyListeners(new OneRowAnalysisFinishEvent(curRowContent,curCol)); curRowContent = new String[20]; } } }
此時回到ExcelListener監聽器,即可以去取到每一行的數據存入datas屬性中,而且對比導入的表頭和模板表頭是否一致等
到此,能夠讀到excel的每一行數據,用於業務處理。