以前咱們想到Excel解析通常是使用POI,但POI存在一個嚴重的問題,就是很是消耗內存。因此阿里人員對它進行了重寫從而誕生了easyexcel
,它解決了過於消耗內存問題,也對它進行了封裝讓使用者使用更加便利java
接下來我先一一介紹它全部的功能細節、如何使用及部分源碼解析git
/** * 最簡單的讀 * <p>1. 建立excel對應的實體對象 參照{@link DemoData} * <p>2. 因爲默認異步讀取excel,因此須要建立excel一行一行的回調監聽器,參照{@link DemoDataListener} * <p>3. 直接讀便可 */ @Test public void simpleRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 這裏 須要指定讀用哪一個class去讀,而後讀取第一個sheet 文件流會自動關閉 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); }
fileName
是路徑+文件名
,DemoData
是Excel數據對應的實體類,DemoDataListener
這看名字就是監聽器,用來監聽處理讀取到的每一條數據XlsxSaxAnalyser
XlsxSaxAnalyser
,在它的構造方法中作了不少事public XlsxSaxAnalyser(AnalysisContext analysisContext, InputStream decryptedStream) throws Exception { ... //從這開始將數據讀取成inputStream流,緩存到了sheetMap XSSFReader xssfReader = new XSSFReader(pkg); analysisUse1904WindowDate(xssfReader, readWorkbookHolder); stylesTable = xssfReader.getStylesTable(); sheetList = new ArrayList<ReadSheet>(); sheetMap = new HashMap<Integer, InputStream>(); XSSFReader.SheetIterator ite = (XSSFReader.SheetIterator)xssfReader.getSheetsData(); int index = 0; if (!ite.hasNext()) { throw new ExcelAnalysisException("Can not find any sheet!"); } while (ite.hasNext()) { InputStream inputStream = ite.next(); sheetList.add(new ReadSheet(index, ite.getSheetName())); sheetMap.put(index, inputStream); index++; } }
doRead
方法,不斷進入此方法,會看到真正執行的最後方法就是XlsxSaxAnalyser
類的execute
方法;能夠看到以下方法中parseXmlSource
解析的就是sheetMap
緩存的真正數據@Override public void execute(List<ReadSheet> readSheetList, Boolean readAll) { for (ReadSheet readSheet : sheetList) { readSheet = SheetUtils.match(readSheet, readSheetList, readAll, analysisContext.readWorkbookHolder().getGlobalConfiguration()); if (readSheet != null) { analysisContext.currentSheet(readSheet); parseXmlSource(sheetMap.get(readSheet.getSheetNo()), new XlsxRowHandler(analysisContext, stylesTable)); // The last sheet is read analysisContext.readSheetHolder().notifyAfterAllAnalysed(analysisContext); } } }
DemoDataListener
實現DemoDataListener
中有兩個實現方法以下,invoke
就對應了上述代碼中的parseXmlSource
而doAfterAllAnalysed
對應了上述方法中的notifyAfterAllAnalysed
,分別表示了先解析每一條數據和當最後一頁讀取完畢通知全部監聽器@Override public void invoke(DemoData data, AnalysisContext context) { LOGGER.info("解析到一條數據:{}", JSON.toJSONString(data)); list.add(data); if (list.size() >= BATCH_COUNT) { saveData(); list.clear(); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); LOGGER.info("全部數據解析完成!"); }
parseXmlSource
具體實現private void parseXmlSource(InputStream inputStream, ContentHandler handler) { InputSource inputSource = new InputSource(inputStream); try { SAXParserFactory saxFactory = SAXParserFactory.newInstance(); saxFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); saxFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); saxFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); SAXParser saxParser = saxFactory.newSAXParser(); XMLReader xmlReader = saxParser.getXMLReader(); xmlReader.setContentHandler(handler); //重點 xmlReader.parse(inputSource); inputStream.close(); } catch (ExcelAnalysisException e) { throw e; } catch (Exception e) { throw new ExcelAnalysisException(e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { throw new ExcelAnalysisException("Can not close 'inputStream'!"); } } } }
notifyAfterAllAnalysed
具體實現notifyAfterAllAnalysed
的代碼,咱們實現的DemoDataListener
監聽器繼承AnalysisEventListener
,而AnalysisEventListener
實現ReadListener
接口@Override public void notifyAfterAllAnalysed(AnalysisContext analysisContext) { for (ReadListener readListener : readListenerList) { readListener.doAfterAllAnalysed(analysisContext); } }
/** * 最簡單的寫 * <p>1. 建立excel對應的實體對象 參照{@link com.alibaba.easyexcel.test.demo.write.DemoData} * <p>2. 直接寫便可 */ @Test public void simpleWrite() { String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; // 這裏 須要指定寫用哪一個class去讀,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 // 若是這裏想使用03 則 傳入excelType參數便可 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); } private List<DemoData> data() { List<DemoData> list = new ArrayList<DemoData>(); for (int i = 0; i < 10; i++) { DemoData data = new DemoData(); data.setString("字符串" + i); data.setDate(new Date()); data.setDoubleData(0.56); list.add(data); } return list; }
doWrite
doWrite
纔是實際作事的,此次咱們從這個入口跟進public void doWrite(List data) { if (excelWriter == null) { throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet()' to call this method"); } excelWriter.write(data, build()); excelWriter.finish(); }
write
write
是核心,繼續進入ExcelWriter
類,看名字addContent
就是添加數據了,由excelBuilder
Excel建造者來添加,這是ExcelBuilderImpl
類public ExcelWriter write(List data, WriteSheet writeSheet, WriteTable writeTable) { excelBuilder.addContent(data, writeSheet, writeTable); return this; }
addContent
ExcelWriteAddExecutor
寫數據執行器,核心就是add
方法了@Override public void addContent(List data, WriteSheet writeSheet, WriteTable writeTable) { try { if (data == null) { return; } context.currentSheet(writeSheet, WriteTypeEnum.ADD); context.currentTable(writeTable); if (excelWriteAddExecutor == null) { excelWriteAddExecutor = new ExcelWriteAddExecutor(context); } //核心 excelWriteAddExecutor.add(data); } catch (RuntimeException e) { finish(); throw e; } catch (Throwable e) { finish(); throw new ExcelGenerateException(e); } }
add
addOneRowOfDataToExcel
插入到Excel表了public void add(List data) { if (CollectionUtils.isEmpty(data)) { return; } 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 fieldList List<Field> fieldList = new ArrayList<Field>(); for (int relativeRowIndex = 0; relativeRowIndex < data.size(); relativeRowIndex++) { int n = relativeRowIndex + newRowIndex; addOneRowOfDataToExcel(data.get(relativeRowIndex), n, relativeRowIndex, fieldList); } }
addOneRowOfDataToExcel
addJavaObjectToExcel
方法private void addOneRowOfDataToExcel(Object oneRowData, int n, int relativeRowIndex, List<Field> fieldList) { if (oneRowData == null) { return; } WriteHandlerUtils.beforeRowCreate(writeContext, n, relativeRowIndex, Boolean.FALSE); Row row = WorkBookUtil.createRow(writeContext.writeSheetHolder().getSheet(), n); WriteHandlerUtils.afterRowCreate(writeContext, row, relativeRowIndex, Boolean.FALSE); if (oneRowData instanceof List) { addBasicTypeToExcel((List)oneRowData, row, relativeRowIndex); } else { addJavaObjectToExcel(oneRowData, row, relativeRowIndex, fieldList); } WriteHandlerUtils.afterRowDispose(writeContext, row, relativeRowIndex, Boolean.FALSE); }
addJavaObjectToExcel
ExcelWriteAddExecutor
執行器類中執行addJavaObjectToExcel
,在這裏進行了數據的解析,將數據解析成標題和內容,封裝成適合Excel的格式CellData
,數據類型等,通過這步咱們還沒看到文件流的生成,那麼下一步了private void addJavaObjectToExcel(Object oneRowData, Row row, int relativeRowIndex, List<Field> fieldList) { WriteHolder currentWriteHolder = writeContext.currentWriteHolder(); BeanMap beanMap = BeanMap.create(oneRowData); Set<String> beanMapHandledSet = new HashSet<String>(); int cellIndex = 0; // If it's a class it needs to be cast by type if (HeadKindEnum.CLASS.equals(writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadKind())) { Map<Integer, Head> headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap(); Map<Integer, ExcelContentProperty> contentPropertyMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getContentPropertyMap(); for (Map.Entry<Integer, ExcelContentProperty> entry : contentPropertyMap.entrySet()) { cellIndex = entry.getKey(); ExcelContentProperty excelContentProperty = entry.getValue(); String name = excelContentProperty.getField().getName(); if (writeContext.currentWriteHolder().ignore(name, cellIndex)) { continue; } if (!beanMap.containsKey(name)) { continue; } Head head = headMap.get(cellIndex); WriteHandlerUtils.beforeCellCreate(writeContext, row, head, cellIndex, relativeRowIndex, Boolean.FALSE); Cell cell = WorkBookUtil.createCell(row, cellIndex); WriteHandlerUtils.afterCellCreate(writeContext, cell, head, relativeRowIndex, Boolean.FALSE); Object value = beanMap.get(name); CellData cellData = converterAndSet(currentWriteHolder, excelContentProperty.getField().getType(), cell, value, excelContentProperty); WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, head, relativeRowIndex, Boolean.FALSE); beanMapHandledSet.add(name); } } // Finish if (beanMapHandledSet.size() == beanMap.size()) { return; } if (cellIndex != 0) { cellIndex++; } Map<String, Field> ignoreMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getIgnoreMap(); initFieldList(oneRowData.getClass(), fieldList); for (Field field : fieldList) { String filedName = field.getName(); boolean uselessData = !beanMap.containsKey(filedName) || beanMapHandledSet.contains(filedName) || ignoreMap.containsKey(filedName) || writeContext.currentWriteHolder().ignore(filedName, cellIndex); if (uselessData) { continue; } Object value = beanMap.get(filedName); if (value == null) { continue; } WriteHandlerUtils.beforeCellCreate(writeContext, row, null, cellIndex, relativeRowIndex, Boolean.FALSE); Cell cell = WorkBookUtil.createCell(row, cellIndex++); WriteHandlerUtils.afterCellCreate(writeContext, cell, null, relativeRowIndex, Boolean.FALSE); CellData cellData = converterAndSet(currentWriteHolder, value.getClass(), cell, value, null); WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, null, relativeRowIndex, Boolean.FALSE); } }
finish
doWrite
中以後還有一步finish
public void finish() { excelBuilder.finish(); }
ExcelBuilderImpl
類@Override public void finish() { if (context != null) { context.finish(); } }
WriteContextImpl
寫內容實現類的finish
方法中,咱們能夠看到writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream());
這句是重點,將寫Excel持有容器中的內容流輸出;以後就是關閉流,刪除臨時文件的過程@Override public void finish() { WriteHandlerUtils.afterWorkbookDispose(this); if (writeWorkbookHolder == null) { return; } Throwable throwable = null; boolean isOutputStreamEncrypt = false; try { isOutputStreamEncrypt = doOutputStreamEncrypt07(); } catch (Throwable t) { throwable = t; } if (!isOutputStreamEncrypt) { try { // 重點 writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream()); writeWorkbookHolder.getWorkbook().close(); } catch (Throwable t) { throwable = t; } } try { Workbook workbook = writeWorkbookHolder.getWorkbook(); if (workbook instanceof SXSSFWorkbook) { ((SXSSFWorkbook)workbook).dispose(); } } catch (Throwable t) { throwable = t; } try { if (writeWorkbookHolder.getAutoCloseStream() && writeWorkbookHolder.getOutputStream() != null) { writeWorkbookHolder.getOutputStream().close(); } } catch (Throwable t) { throwable = t; } if (!isOutputStreamEncrypt) { try { doFileEncrypt07(); } catch (Throwable t) { throwable = t; } } try { if (writeWorkbookHolder.getTempTemplateInputStream() != null) { writeWorkbookHolder.getTempTemplateInputStream().close(); } } catch (Throwable t) { throwable = t; } clearEncrypt03(); if (throwable != null) { throw new ExcelGenerateException("Can not close IO", throwable); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Finished write."); } }
InputStream
的參數,以後和Excel讀沒多大區別/** * 文件上傳 * <p> * 1. 建立excel對應的實體對象 參照{@link UploadData} * <p> * 2. 因爲默認異步讀取excel,因此須要建立excel一行一行的回調監聽器,參照{@link UploadDataListener} * <p> * 3. 直接讀便可 */ @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener()).sheet().doRead(); return "success"; }
OutputStream
,其它和文件寫入差很少/** * 文件下載 * <p> * 1. 建立excel對應的實體對象 參照{@link DownloadData} * <p> * 2. 設置返回的 參數 * <p> * 3. 直接寫,這裏注意,finish的時候會自動關閉OutputStream,固然你外面再關閉流問題不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { // 這裏注意 有同窗反應使用swagger 會致使各類問題,請直接用瀏覽器或者用postman response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); // 這裏URLEncoder.encode能夠防止中文亂碼 固然和easyexcel沒有關係 String fileName = URLEncoder.encode("測試", "UTF-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); }
sheet()
方法時,實際上都是默認第1頁,那麼如何讀取多頁?/** * 讀多個或者所有sheet,這裏注意一個sheet不能讀取屢次,屢次讀取須要從新讀取文件 * <p> * 1. 建立excel對應的實體對象 參照{@link DemoData} * <p> * 2. 因爲默認異步讀取excel,因此須要建立excel一行一行的回調監聽器,參照{@link DemoDataListener} * <p> * 3. 直接讀便可 */ @Test public void repeatedRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 讀取所有sheet // 這裏須要注意 DemoDataListener的doAfterAllAnalysed 會在每一個sheet讀取完畢後調用一次。而後全部sheet都會往同一個DemoDataListener裏面寫 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll(); // 讀取部分sheet fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; ExcelReader excelReader = EasyExcel.read(fileName).build(); // 這裏爲了簡單 因此註冊了 一樣的head 和Listener 本身使用功能必須不一樣的Listener ReadSheet readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); ReadSheet readSheet2 = EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); // 這裏注意 必定要把sheet1 sheet2 一塊兒傳進去,否則有個問題就是03版的excel 會讀取屢次,浪費性能 excelReader.read(readSheet1, readSheet2); // 這裏千萬別忘記關閉,讀的時候會建立臨時文件,到時磁盤會崩的 excelReader.finish(); }
doReadAll
方法能夠讀取全部sheet頁面readSheet(index)
,index爲頁面位置,從0開始計數@Data public class ConverterData { /** * 我自定義 轉換器,無論數據庫傳過來什麼 。我給他加上「自定義:」 */ @ExcelProperty(converter = CustomStringStringConverter.class) private String string; /** * 這裏用string 去接日期才能格式化。我想接收年月日格式 */ @DateTimeFormat("yyyy年MM月dd日HH時mm分ss秒") private String date; /** * 我想接收百分比的數字 */ @NumberFormat("#.##%") private String doubleData; }
CustomStringStringConverter
類爲自定義轉換器,能夠對字符串進行必定修改,而日期數字的格式化,它已經有提供註解了DateTimeFormat
和NumberFormat
Converter
接口後便可使用supportExcelTypeKey
這是判斷單元格類型,convertToJavaData
這是讀取轉換,convertToExcelData
這是寫入轉換import com.alibaba.excel.converters.Converter; import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.property.ExcelContentProperty; public class CustomStringStringConverter implements Converter<String> { @Override public Class supportJavaTypeKey() { return String.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } /** * 這裏讀的時候會調用 */ @Override public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return "自定義:" + cellData.getStringValue(); } /** * 這裏是寫的時候會調用 不用管 */ @Override public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return new CellData(value); } }
字符串0 2020/1/1 1:01 1
解析到一條數據:{"date":"2020年01月01日01時01分01秒","doubleData":"100%","string":"自定義:字符串0"}
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet() // 這裏能夠設置1,由於頭就是一行。若是多行頭,能夠設置其餘值。不傳入也能夠,由於默認會根據DemoData 來解析,他沒有指定頭,也就是默認1行 .headRowNumber(1).doRead();
AnalysisEventListener
接口的監聽器中,重寫invokeHeadMap
方法便可/** * 這裏會一行行的返回頭 * * @param headMap * @param context */ @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { LOGGER.info("解析到一條頭數據:{}", JSON.toJSONString(headMap)); }
AnalysisEventListener
接口的監聽器中,重寫onException
方法便可@Override public void onException(Exception exception, AnalysisContext context) { LOGGER.error("解析失敗,可是繼續解析下一行:{}", exception.getMessage()); if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; LOGGER.error("第{}行,第{}列解析異常", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex()); } }
CellData
封裝起來@Data public class CellDataReadDemoData { private CellData<String> string; // 這裏注意 雖然是日期 可是 類型 存儲的是number 由於excel 存儲的就是number private CellData<Date> date; private CellData<Double> doubleData; // 這裏並不必定能完美的獲取 有些公式是依賴性的 可能會讀不到 這個問題後續會修復 private CellData<String> formulaValue; }
解析到一條數據:{"date":{"data":1577811661000,"dataFormat":22,"dataFormatString":"m/d/yy h:mm","formula":false,"numberValue":43831.0423726852,"type":"NUMBER"},"doubleData":{"data":1.0,"formula":false,"numberValue":1,"type":"NUMBER"},"formulaValue":{"data":"字符串01","formula":true,"formulaValue":"_xlfn.CONCAT(A2,C2)","stringValue":"字符串01","type":"STRING"},"string":{"data":"字符串0","dataFormat":0,"dataFormatString":"General","formula":false,"stringValue":"字符串0","type":"STRING"}}
doReadSync
方法,直接返回List
/** * 同步的返回,不推薦使用,若是數據量大會把數據放到內存裏面 */ @Test public void synchronousRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 這裏 須要指定讀用哪一個class去讀,而後讀取第一個sheet 同步讀取會自動finish List<Object> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync(); for (Object obj : list) { DemoData data = (DemoData)obj; LOGGER.info("讀取到數據:{}", JSON.toJSONString(data)); } // 這裏 也能夠不指定class,返回一個list,而後讀取第一個sheet 同步讀取會自動finish list = EasyExcel.read(fileName).sheet().doReadSync(); for (Object obj : list) { // 返回每條數據的鍵值對 表示所在的列 和所在列的值 Map<Integer, String> data = (Map<Integer, String>)obj; LOGGER.info("讀取到數據:{}", JSON.toJSONString(data)); } }
public class NoModleDataListener extends AnalysisEventListener<Map<Integer, String>> { ... }
解析到一條數據:{0:"字符串0",1:"2020-01-01 01:01:01",2:"1"}
excludeColumnFiledNames
來排除特定字段寫入,用includeColumnFiledNames
表示只寫入特定字段/** * 根據參數只導出指定列 * <p> * 1. 建立excel對應的實體對象 參照{@link DemoData} * <p> * 2. 根據本身或者排除本身須要的列 * <p> * 3. 直接寫便可 */ @Test public void excludeOrIncludeWrite() { String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx"; // 根據用戶傳入字段 假設咱們要忽略 date Set<String> excludeColumnFiledNames = new HashSet<String>(); excludeColumnFiledNames.add("date"); // 這裏 須要指定寫用哪一個class去讀,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板") .doWrite(data()); fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx"; // 根據用戶傳入字段 假設咱們只要導出 date Set<String> includeColumnFiledNames = new HashSet<String>(); includeColumnFiledNames.add("date"); // 這裏 須要指定寫用哪一個class去讀,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板") .doWrite(data()); }
@Data public class IndexData { @ExcelProperty(value = "字符串標題", index = 0) private String string; @ExcelProperty(value = "日期標題", index = 1) private Date date; /** * 這裏設置3 會致使第二列空的 */ @ExcelProperty(value = "數字標題", index = 3) private Double doubleData; }
@Data public class ComplexHeadData { @ExcelProperty({"主標題", "字符串標題"}) private String string; @ExcelProperty({"主標題", "日期標題"}) private Date date; @ExcelProperty({"主標題", "數字標題"}) private Double doubleData; }
/** * 重複屢次寫入 * <p> * 1. 建立excel對應的實體對象 參照{@link ComplexHeadData} * <p> * 2. 使用{@link ExcelProperty}註解指定複雜的頭 * <p> * 3. 直接調用二次寫入便可 */ @Test public void repeatedWrite() { // 方法1 若是寫到同一個sheet String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 這裏 須要指定寫用哪一個class去讀 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); // 這裏注意 若是同一個sheet只要建立一次 WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); // 去調用寫入,這裏我調用了五次,實際使用時根據數據庫分頁的總的頁數來 for (int i = 0; i < 5; i++) { // 分頁去數據庫查詢數據 這裏能夠去數據庫查詢每一頁的數據 List<DemoData> data = data(); writeSheet.setSheetName("模板"); excelWriter.write(data, writeSheet); } /// 千萬別忘記finish 會幫忙關閉流 excelWriter.finish(); // 方法2 若是寫到不一樣的sheet 同一個對象 fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 這裏 指定文件 excelWriter = EasyExcel.write(fileName, DemoData.class).build(); // 去調用寫入,這裏我調用了五次,實際使用時根據數據庫分頁的總的頁數來。這裏最終會寫到5個sheet裏面 for (int i = 0; i < 5; i++) { // 每次都要建立writeSheet 這裏注意必須指定sheetNo writeSheet = EasyExcel.writerSheet(i, "模板"+i).build(); // 分頁去數據庫查詢數據 這裏能夠去數據庫查詢每一頁的數據 List<DemoData> data = data(); excelWriter.write(data, writeSheet); } /// 千萬別忘記finish 會幫忙關閉流 excelWriter.finish(); // 方法3 若是寫到不一樣的sheet 不一樣的對象 fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 這裏 指定文件 excelWriter = EasyExcel.write(fileName).build(); // 去調用寫入,這裏我調用了五次,實際使用時根據數據庫分頁的總的頁數來。這裏最終會寫到5個sheet裏面 for (int i = 0; i < 5; i++) { // 每次都要建立writeSheet 這裏注意必須指定sheetNo。這裏注意DemoData.class 能夠每次都變,我這裏爲了方便 因此用的同一個class 實際上能夠一直變 writeSheet = EasyExcel.writerSheet(i, "模板"+i).head(DemoData.class).build(); // 分頁去數據庫查詢數據 這裏能夠去數據庫查詢每一頁的數據 List<DemoData> data = data(); excelWriter.write(data, writeSheet); } /// 千萬別忘記finish 會幫忙關閉流 excelWriter.finish(); }
@Test public void imageWrite() throws Exception { String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx"; // 若是使用流 記得關閉 InputStream inputStream = null; try { List<ImageData> list = new ArrayList<ImageData>(); ImageData imageData = new ImageData(); list.add(imageData); String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg"; // 放入四種類型的圖片 實際使用只要選一種便可 imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath))); imageData.setFile(new File(imagePath)); imageData.setString(imagePath); inputStream = FileUtils.openInputStream(new File(imagePath)); imageData.setInputStream(inputStream); EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list); } finally { if (inputStream != null) { inputStream.close(); } } }
@Data @ContentRowHeight(100) @ColumnWidth(100 / 8) public class ImageData { private File file; private InputStream inputStream; /** * 若是string類型 必須指定轉換器,string默認轉換成string */ @ExcelProperty(converter = StringImageConverter.class) private String string; private byte[] byteArray; }
導出結果:兩行四列,每列都對應一張圖片,四種導出類型都可github
StringImageConverter
自定義轉換器爲public class StringImageConverter implements Converter<String> { @Override public Class supportJavaTypeKey() { return String.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.IMAGE; } @Override public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { throw new UnsupportedOperationException("Cannot convert images to string"); } @Override public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws IOException { return new CellData(FileUtils.readFileToByteArray(new File(value))); } }
@Data @ContentRowHeight(10) @HeadRowHeight(20) @ColumnWidth(25) public class WidthAndHeightData { @ExcelProperty("字符串標題") private String string; @ExcelProperty("日期標題") private Date date; /** * 寬度爲50 */ @ColumnWidth(50) @ExcelProperty("數字標題") private Double doubleData; }
@Test public void styleWrite() { String fileName = TestFileUtil.getPath() + "styleWrite" + System.currentTimeMillis() + ".xlsx"; // 頭的策略 WriteCellStyle headWriteCellStyle = new WriteCellStyle(); // 背景設置爲紅色 headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short)20); headWriteCellStyle.setWriteFont(headWriteFont); // 內容的策略 WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); // 這裏須要指定 FillPatternType 爲FillPatternType.SOLID_FOREGROUND 否則沒法顯示背景顏色.頭默認了 FillPatternType因此能夠不指定 contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); // 背景綠色 contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); WriteFont contentWriteFont = new WriteFont(); // 字體大小 contentWriteFont.setFontHeightInPoints((short)20); contentWriteCellStyle.setWriteFont(contentWriteFont); // 這個策略是 頭是頭的樣式 內容是內容的樣式 其餘的策略能夠本身實現 HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); // 這裏 須要指定寫用哪一個class去讀,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板") .doWrite(data()); }
@Test public void mergeWrite() { String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx"; // 每隔2行會合並。固然其餘合併策略也能夠本身寫 LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0); // 這裏 須要指定寫用哪一個class去讀,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data()); }
LongestMatchColumnWidthStyleStrategy
@Test public void longestMatchColumnWidthWrite() { String fileName = TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx"; // 這裏 須要指定寫用哪一個class去讀,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 EasyExcel.write(fileName, LongestMatchColumnWidthData.class) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong()); }
@Test public void customHandlerWrite() { String fileName = TestFileUtil.getPath() + "customHandlerWrite" + System.currentTimeMillis() + ".xlsx"; // 這裏 須要指定寫用哪一個class去讀,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CustomSheetWriteHandler()) .registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data()); }
CustomCellWriteHandler
類,其實現CellWriteHandler
接口,咱們在後處理方法afterCellDispose
作處理public class CustomCellWriteHandler implements CellWriteHandler { private static final Logger LOGGER = LoggerFactory.getLogger(CustomCellWriteHandler.class); @Override public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { // 這裏能夠對cell進行任何操做 LOGGER.info("第{}行,第{}列寫入完成。", cell.getRowIndex(), cell.getColumnIndex()); if (isHead && cell.getColumnIndex() == 0) { CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper(); Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL); hyperlink.setAddress("https://github.com/alibaba/easyexcel"); cell.setHyperlink(hyperlink); } } }
List<List<String>>
的對象頭@Test public void noModleWrite() { // 寫法1 String fileName = TestFileUtil.getPath() + "noModleWrite" + System.currentTimeMillis() + ".xlsx"; // 這裏 須要指定寫用哪一個class去讀,而後寫到第一個sheet,名字爲模板 而後文件流會自動關閉 EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList()); } private List<List<String>> head() { List<List<String>> list = new ArrayList<List<String>>(); List<String> head0 = new ArrayList<String>(); head0.add("字符串" + System.currentTimeMillis()); List<String> head1 = new ArrayList<String>(); head1.add("數字" + System.currentTimeMillis()); List<String> head2 = new ArrayList<String>(); head2.add("日期" + System.currentTimeMillis()); list.add(head0); list.add(head1); list.add(head2); return list; }
easyexcel的 github地址
歡迎訪問收藏做者 知識點整理,沒註冊的請點擊 這裏