EasyExcel(笔记)
常用場(chǎng)景
1、將用戶信息導(dǎo)出為excel表格(導(dǎo)出數(shù)據(jù)…)
2、將Excel表中的信息錄入到網(wǎng)站數(shù)據(jù)庫(kù)(習(xí)題上傳…)
開發(fā)中經(jīng)常會(huì)設(shè)計(jì)到excel的處理,如導(dǎo)出Excel,導(dǎo)入Excel到數(shù)據(jù)庫(kù)中! 操作Excel目前比較流行的就是 Apache POI 和 阿里巴巴的 easyExcel !
首先execl有兩個(gè)版本,分別是03版和07版。
通過鼠標(biāo)右鍵即可觀看(以xls,xlsx結(jié)尾)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-lkkw0fFK-1610874934517)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20210117141319852.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-AtA6xjSz-1610874934522)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20210117141527668.png)]
Poi(適合小數(shù)據(jù)量)
Apache POI 官網(wǎng):https://poi.apache.org/
POI是Apache軟件基金會(huì)的,POI為“Poor Obfuscation Implementation”的首字母縮寫,意為“簡(jiǎn)潔版的模糊實(shí)現(xiàn)”。
所以POI的主要功能是可以用Java操作Microsoft Office的相關(guān)文件,這里我們主要講Excel
小數(shù)據(jù)寫
1 .導(dǎo)入依賴
<dependencies><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.10.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.1</version></dependency></dependencies>2 .開啟讀寫操作,代碼走起
無非就是對(duì)api的充分認(rèn)識(shí),接下來我們先去了解他的api
Workbook wordkbook =new HSSFWorkbook();//創(chuàng)建一個(gè)Workbook對(duì)象wordkbook.createSheet();//創(chuàng)建表名,如果不寫參數(shù),會(huì)有默認(rèn)值Row row1=sheet.createRow(0);//根據(jù)里面的數(shù)字拿到對(duì)應(yīng)的行,0默認(rèn)為第一行Cell cell = row1.createCell(0);//根據(jù)行對(duì)象創(chuàng)建單元格,這里0為第一個(gè)cell.setCellValue("");//可以給單元格賦值寫入一個(gè)Excel
package com.kuang;import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.joda.time.DateTime; import org.junit.Test;import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;public class ExcelWriteTest {//先要有個(gè)路勁static String path="D:\JAVA---EasyExcel\TEST";@Testpublic void testWrite03(String[] args) throws IOException {//1,創(chuàng)建一個(gè)工作薄Workbook wordkbook =new HSSFWorkbook();//表名Sheet sheet=wordkbook.createSheet("灰灰統(tǒng)計(jì)表");//創(chuàng)建行Row row1=sheet.createRow(0);//4.創(chuàng)建一個(gè)單元格Cell cell = row1.createCell(0);cell.setCellValue("今日新增觀眾");Cell cell2 = row1.createCell(1);cell2.setCellValue("盧本偉");//創(chuàng)建行Row row2=sheet.createRow(1);//4.創(chuàng)建一個(gè)單元格Cell cell3 = row2.createCell(0);cell3.setCellValue("統(tǒng)計(jì)時(shí)間");Cell cell24= row2.createCell(1);String time=new DateTime().toString("yyyy-MM-dd HH:mm:ss");cell24.setCellValue(time);//生成一張表 03是xls 07是xlsxFileOutputStream fileOutputStream = new FileOutputStream(path + "灰灰統(tǒng)計(jì)表03.xls");wordkbook.write(fileOutputStream);fileOutputStream.close();System.out.println("灰灰統(tǒng)計(jì)表03已生成");}@Testpublic void testWrite07() throws IOException {//1,創(chuàng)建一個(gè)工作薄Workbook wordkbook =new XSSFWorkbook();//表名Sheet sheet=wordkbook.createSheet("灰灰統(tǒng)計(jì)表");//創(chuàng)建行Row row1=sheet.createRow(0);//4.創(chuàng)建一個(gè)單元格Cell cell = row1.createCell(0);cell.setCellValue("今日新增觀眾");Cell cell2 = row1.createCell(1);cell2.setCellValue("盧本偉");//創(chuàng)建行Row row2=sheet.createRow(1);//4.創(chuàng)建一個(gè)單元格Cell cell3 = row2.createCell(0);cell3.setCellValue("統(tǒng)計(jì)時(shí)間");Cell cell24= row2.createCell(1);String time=new DateTime().toString("yyyy-MM-dd HH:mm:ss");cell24.setCellValue(time);//生成一張表 03是xls 07是xlsxFileOutputStream fileOutputStream = new FileOutputStream(path + "\灰灰統(tǒng)計(jì)表07.xlsx");wordkbook.write(fileOutputStream);fileOutputStream.close();System.out.println("灰灰統(tǒng)計(jì)表07已生成");}}上面寫完后會(huì)在項(xiàng)目目錄下生成一個(gè)表格
03 | 07 版本的寫,就是對(duì)象不同,方法一樣的!
大數(shù)據(jù)寫
HSSF
缺點(diǎn):最多只能處理65536行,否則會(huì)拋出異常
java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0…65535)
優(yōu)點(diǎn):過程中寫入緩存,不操作磁盤,最后一次性寫入磁盤,速度快
@Test public void testWrite03BigData() throws IOException {long begin = System.currentTimeMillis();HSSFWorkbook workbook = new HSSFWorkbook();HSSFSheet sheet = workbook.createSheet();for (int rowNum = 0; rowNum < 65535; rowNum++) {Row row = sheet.createRow(rowNum);for (int cellNum = 0; cellNum < 10; cellNum++) {Cell cell = row.createCell(cellNum);cell.setCellValue(cellNum);}}System.out.println("over");FileOutputStream outputStream = new FileOutputStream(path + "//testWrite03BigData");workbook.write(outputStream);outputStream.close();long end = System.currentTimeMillis();System.out.println((double) (end-begin)/1000);}XSSF
缺點(diǎn):寫數(shù)據(jù)時(shí)速度非常慢,非常耗內(nèi)存,也會(huì)發(fā)生內(nèi)存溢出,如100萬條
優(yōu)點(diǎn):可以寫較大的數(shù)據(jù)量,如20萬條
@Test public void testWrite07BigData() throws IOException {long begin = System.currentTimeMillis();Workbook workbook = new XSSFWorkbook();Sheet sheet = workbook.createSheet();for (int rowNum = 0; rowNum < 655350; rowNum++) {Row row = sheet.createRow(rowNum);for (int cellNum = 0; cellNum < 10; cellNum++) {Cell cell = row.createCell(cellNum);cell.setCellValue(cellNum);}}System.out.println("over");FileOutputStream outputStream = new FileOutputStream(path + "//testWrite03BigData.xlsx");workbook.write(outputStream);outputStream.close();long end = System.currentTimeMillis();System.out.println((double) (end-begin)/1000);}SXSSF
優(yōu)點(diǎn):可以寫非常大的數(shù)據(jù)量,如100萬條甚至更多條,寫數(shù)據(jù)速度快,占用更少的內(nèi)存
注意:
過程中會(huì)產(chǎn)生臨時(shí)文件,需要清理臨時(shí)文件
默認(rèn)由100條記錄被保存在內(nèi)存中,如果超過這數(shù)量,則最前面的數(shù)據(jù)被寫入臨時(shí)文件 如果想自定義內(nèi)存中數(shù)據(jù)的數(shù)量,可以使用new SXSSFWorkbook ( 數(shù)量 )
@Test public void testWrite07BigDataS() throws IOException {long begin = System.currentTimeMillis();Workbook workbook = new SXSSFWorkbook();Sheet sheet = workbook.createSheet();for (int rowNum = 0; rowNum < 100000; rowNum++) {Row row = sheet.createRow(rowNum);for (int cellNum = 0; cellNum < 10; cellNum++) {Cell cell = row.createCell(cellNum);cell.setCellValue(cellNum);}}System.out.println("over");FileOutputStream outputStream = new FileOutputStream(path + "//testWrite07BigDataS.xlsx");workbook.write(outputStream);outputStream.close();((SXSSFWorkbook)workbook).dispose();//關(guān)閉臨時(shí)文件long end = System.currentTimeMillis();System.out.println((double) (end-begin)/1000);}SXSSFWorkbook-來至官方的解釋:實(shí)現(xiàn)“BigGridDemo”策略的流式XSSFWorkbook版本。這允許寫入 非常大的文件而不會(huì)耗盡內(nèi)存,因?yàn)槿魏螘r(shí)候只有可配置的行部分被保存在內(nèi)存中。
請(qǐng)注意,仍然可能會(huì)消耗大量?jī)?nèi)存,這些內(nèi)存基于您正在使用的功能,例如合并區(qū)域,注釋…仍然只存 儲(chǔ)在內(nèi)存中,因此如果廣泛使用,可能需要大量?jī)?nèi)存。
讀取單一類型的數(shù)據(jù)
這個(gè)操作跟上述的寫并沒有什么不同,不同就是方法是get而不是set
static String path="F:\\demo\\javapoi\\demopoi";@Testpublic void testRead03() throws IOException {//Sheet sheet=workbook.createSheet("統(tǒng)計(jì)表");//sheet操作表中元素FileInputStream fileInputStream = new FileInputStream(path + "\灰灰統(tǒng)計(jì)表03.xls");Workbook workbook=new HSSFWorkbook(fileInputStream);Sheet sheet = workbook.getSheetAt(0); // Sheet sheet2 = workbook.getSheet("灰灰統(tǒng)計(jì)表");Row row = sheet.getRow(1);Cell cell = row.getCell(0);Cell cell2 = row.getCell(1);System.out.println(cell.getStringCellValue());System.out.println(cell2.getStringCellValue());fileInputStream.close();}這里值得注意的是,使用表格對(duì)象要注意三種創(chuàng)建方式
- POI-HSSF
- POI-XSSF
- SXSSF
**HSSF:*Excel97-2003版本,擴(kuò)展名為.xls。一個(gè)sheet最大行數(shù)*65536,最大列數(shù)256。
**XSSF:*Excel2007版本開始,擴(kuò)展名為.xlsx。一個(gè)sheet最大行數(shù)*1048576,最大列數(shù)16384。
SXSSF:**是在XSSF基礎(chǔ)上,POI3.8版本開始提供的**支持低內(nèi)存占用的操作方式,擴(kuò)展名為.xlsx。
Excel版本兼容性是向下兼容。
讀取不同類型的數(shù)據(jù)
在讀取數(shù)據(jù)的時(shí)候我們需要先判斷值類型,才能用對(duì)應(yīng)API
下面這個(gè)是先拿到表頭那一行,相當(dāng)于數(shù)據(jù)庫(kù)的字段
FileInputStream fileInputStream = new FileInputStream(path + "數(shù)據(jù)表07.xlsx");Workbook workbook=new XSSFWorkbook(fileInputStream);Sheet sheet = workbook.getSheetAt(0);Row rowTitle = sheet.getRow(0);if(rowTitle!=null){int cellCount=rowTitle.getPhysicalNumberOfCells(); //拿到第row行的那一行的總個(gè)數(shù)for (int i = 0; i <cellCount ; i++) { //循環(huán)個(gè)數(shù)取出Cell cell = rowTitle.getCell(i);if(cell!=null){ //如果不等于空取出值int cellType = cell.getCellType(); //這里是知道我們標(biāo)題是String,考慮不確定的時(shí)候怎么取String cellValue = cell.getStringCellValue();System.out.print(cellValue+"|");}}System.out.println();}下面接著讀取對(duì)應(yīng)的數(shù)據(jù),這里就需要我們剛剛講的類型判斷
int cellType=cell.getCellType();利用這個(gè),然后判斷它的XSSFCell類型再具體輸出
//獲取表中內(nèi)容int rowCount=sheet.getPhysicalNumberOfRows();for(int rowNum=1;rowNum<rowCount;rowNum++){Row rowData=sheet.getRow(rowNum); //取出對(duì)應(yīng)的行if(rowData!=null){int cellCount=rowTitle.getPhysicalNumberOfCells();for(int cellNum=0;cellNum<cellCount;cellNum++){System.out.print("["+(rowNum+1+"-"+(cellNum+1)+"]"));Cell cell = rowData.getCell(cellNum);//匹配數(shù)據(jù)類型if(cell!=null){int cellType=cell.getCellType();switch (cellType){case XSSFCell.CELL_TYPE_STRING: System.out.print("字符串:"+cell.getStringCellValue());break;case XSSFCell.CELL_TYPE_BOOLEAN: System.out.print("布爾:"+cell.getBooleanCellValue());break;case XSSFCell.CELL_TYPE_NUMERIC:if(HSSFDateUtil.isCellDateFormatted(cell)){System.out.println("日期格式:"+new DateTime(cell.getDateCellValue()).toString("yyyy-MM-dd HH:mm:ss"));break;}elsecell.setCellType(XSSFCell.CELL_TYPE_STRING);System.out.print("整形:"+cell.toString());break;case XSSFCell.CELL_TYPE_BLANK: System.out.print("空");break;case XSSFCell.CELL_TYPE_ERROR: System.out.print("數(shù)據(jù)類型錯(cuò)誤");break;case Cell.CELL_TYPE_FORMULA://拿到計(jì)算公式XSSFFormulaEvaluator FormulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);String formula=cell.getCellFormula();System.out.println("公式:"+formula);//CellValue evaluate = FormulaEvaluator.evaluate(cell);String cellValue=evaluate.formatAsString();System.out.println(cellValue);break;default:break;}}}}}fileInputStream.close();EasyExcel(適合大數(shù)據(jù)量)
這個(gè)的出現(xiàn)比poi簡(jiǎn)單非常多,只需要認(rèn)清他的對(duì)應(yīng)API就可以進(jìn)行操作了,即使記不清楚了,我們也可以去網(wǎng)站上在線COPY
https://www.yuque.com/
導(dǎo)入依賴
//注意它里面自帶poi依賴,如果重復(fù)帶入會(huì)報(bào)ClassNotfound <dependency> <groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.0-beta2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.16</version></dependency>讀寫操作
我們以上面這個(gè)表格為例來進(jìn)行讀寫操作,觸類旁通
寫操作
先來個(gè)實(shí)體類方便插入數(shù)據(jù)
import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data;import java.util.Date;@Data //lombok public class DemoData {@ExcelProperty("字符串標(biāo)題")private String string;@ExcelProperty("日期標(biāo)題")private Date date;@ExcelProperty("數(shù)字標(biāo)題")private Double doubleData;/*** 忽略這個(gè)字段*/@ExcelIgnore //注意這個(gè)注解是高版本的easyexcel依賴才有private String ignore; }再來一個(gè)工具類方便我們寫數(shù)據(jù)
public class utilList {public static 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;} }進(jìn)行寫
@Testpublic void simpleWrite() {// 寫法1String path="D:\JAVA---EasyExcel\TEST";String fileName = path + "\EasyTest.xlsx";// 這里 需要指定寫用哪個(gè)class去寫,然后寫到第一個(gè)sheet,名字為模板 然后文件流會(huì)自動(dòng)關(guān)閉// 如果這里想使用03 則 傳入excelType參數(shù)即可EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());// 寫法2 // fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // // 這里 需要指定寫用哪個(gè)class去寫 // ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); // WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); // excelWriter.write(data(), writeSheet); // // 千萬別忘記finish 會(huì)幫忙關(guān)閉流 // excelWriter.finish();}寫完就有了這樣一個(gè)表格
讀操作
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.60</version> </dependency>首先我們一個(gè)監(jiān)聽器,因?yàn)楹蚿oi的不同,easyExcel是spring接管的,自己監(jiān)控和改寫方法
package com.example.easyExcel;import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.fastjson.JSON; import com.example.Dao.DemoDAO; import org.slf4j.Logger; import org.slf4j.LoggerFactory;import java.util.ArrayList; import java.util.List; // 有個(gè)很重要的點(diǎn) DemoDataListener 不能被spring管理,要每次讀取excel都要new,然后里面用到spring可以構(gòu)造方法傳進(jìn)去 public class DemoDataListener extends AnalysisEventListener<DemoData> {private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);/*** 每隔5條存儲(chǔ)數(shù)據(jù)庫(kù),實(shí)際使用中可以3000條,然后清理list ,方便內(nèi)存回收*/private static final int BATCH_COUNT = 5;List<DemoData> list = new ArrayList<DemoData>();/*** 假設(shè)這個(gè)是一個(gè)DAO,當(dāng)然有業(yè)務(wù)邏輯這個(gè)也可以是一個(gè)service。當(dāng)然如果不用存儲(chǔ)這個(gè)對(duì)象沒用。*/private DemoDAO demoDAO;public DemoDataListener() {// 這里是demo,所以隨便new一個(gè)。實(shí)際使用如果到了spring,請(qǐng)使用下面的有參構(gòu)造函數(shù)demoDAO = new DemoDAO();}/*** 如果使用了spring,請(qǐng)使用這個(gè)構(gòu)造方法。每次創(chuàng)建Listener的時(shí)候需要把spring管理的類傳進(jìn)來** @param demoDAO*/public DemoDataListener(DemoDAO demoDAO) {this.demoDAO = demoDAO;}/*** 這個(gè)每一條數(shù)據(jù)解析都會(huì)來調(diào)用** @param data* one row value. Is is same as {@link AnalysisContext#readRowHolder()}* @param context*/@Overridepublic void invoke(DemoData data, AnalysisContext context) {LOGGER.info("解析到一條數(shù)據(jù):{}", JSON.toJSONString(data));System.out.println( JSON.toJSONString(data));list.add(data);// 達(dá)到BATCH_COUNT了,需要去存儲(chǔ)一次數(shù)據(jù)庫(kù),防止數(shù)據(jù)幾萬條數(shù)據(jù)在內(nèi)存,容易OOMif (list.size() >= BATCH_COUNT) {saveData();// 存儲(chǔ)完成清理 listlist.clear();}}/*** 所有數(shù)據(jù)解析完成了 都會(huì)來調(diào)用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 這里也要保存數(shù)據(jù),確保最后遺留的數(shù)據(jù)也存儲(chǔ)到數(shù)據(jù)庫(kù)saveData();LOGGER.info("所有數(shù)據(jù)解析完成!");}/*** 加上存儲(chǔ)數(shù)據(jù)庫(kù)*/private void saveData() {LOGGER.info("{}條數(shù)據(jù),開始存儲(chǔ)數(shù)據(jù)庫(kù)!", list.size());demoDAO.save(list);LOGGER.info("存儲(chǔ)數(shù)據(jù)庫(kù)成功!");} }這里的saveData是為了給讀取前臺(tái)的表格之后可以執(zhí)行這個(gè)然后通過下面的方法持久化到數(shù)據(jù)庫(kù),而且這里默認(rèn)是5條持久一次
/*** 假設(shè)這個(gè)是你的DAO存儲(chǔ)。當(dāng)然還要這個(gè)類讓spring管理,當(dāng)然你不用需要存儲(chǔ),也不需要這個(gè)類。**/ public class DemoDAO {public void save(List<DemoData> list) {// 如果是mybatis,盡量別直接調(diào)用多次insert,自己寫一個(gè)mapper里面新增一個(gè)方法batchInsert,所有數(shù)據(jù)一次性插入} }@Override報(bào)錯(cuò)
可能是jdk版本不是8
檢查java和javac
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-wa1x84lP-1610874934528)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20210117165613834.png)]
開始讀的操作
/*** 最簡(jiǎn)單的讀* <p>1. 創(chuàng)建excel對(duì)應(yīng)的實(shí)體對(duì)象 參照{(diào)@link DemoData}* <p>2. 由于默認(rèn)一行行的讀取excel,所以需要?jiǎng)?chuàng)建excel一行一行的回調(diào)監(jiān)聽器,參照{(diào)@link DemoDataListener}* <p>3. 直接讀即可*/@Testpublic void simpleRead() {String fileName = path + "EasyTest.xlsx";EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();}總結(jié)
- easyExcel的確比poi方便,但是它的讀需要編寫監(jiān)聽器
- 建議大數(shù)據(jù)用easyExcel,因?yàn)榇髷?shù)據(jù)時(shí)poi對(duì)于內(nèi)存消耗非常大
- 由于apache poi和jxl,excelPOI都有一個(gè)嚴(yán)重的問題,就是非常消耗內(nèi)存,特別處理數(shù)據(jù)量多時(shí),速度慢并且時(shí)有異常發(fā)生,所以改用由阿里研發(fā)的easyExcel更可靠一些,它的官方建議對(duì)于1000行以內(nèi)的采用原來poi的寫法一次讀寫,但于1000行以上的數(shù)據(jù),有用了一行行進(jìn)行解析的方案,這樣避免了內(nèi)存的溢出。
- EasyExcel擴(kuò)展功能很多,且Api式調(diào)用真的輕松很多
總結(jié)
以上是生活随笔為你收集整理的EasyExcel(笔记)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 协方差计算公式 公式讲解
- 下一篇: Rest风格---ElasticSear