常用資訊
- 将使用者資訊導出為Excel表格(導出資料)
- 将Excel表中的資訊錄入到網站資料庫(習題上傳…),可以大大減輕網站錄入量
開發中經常涉及到Excel的處理,如導出Excel,導入資料到Excel中
操作Excel目前比較流行的就是 Apache POI 和 阿裡巴巴的 easyExcel
Apache POI
Apache POI 官網 : https://poi.apache.org/
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-eGWtRorg-1622371513711)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530182529061-1519557148.png)]
POI(Poor Obfuscation Implementation),直譯為“可憐的模糊實作”,利用POI接口可以通過
Java
操作 Microsoft office 套件工具的讀寫功能。POI支援office的所有版本。
基本功能
結構:
HSSF - 提供讀寫Microsoft Excel格式檔案的功能。(03版本,行數最多支援65536行)
XSSF - 提供讀寫Microsoft Excel OOXML格式檔案的功能。(07版本,行數無限制)
HWPF - 提供讀寫Microsoft Word格式檔案的功能。
HSLF - 提供讀寫Microsoft PowerPoint格式檔案的功能。
HDGF - 提供讀寫Microsoft Visio格式檔案的功能。
POI 較為原生和複雜,操作起來比較麻煩,POI 會将資料先加載到記憶體中,然後再寫入到檔案中,處理大量資料時可能出現OOM(Out Of Memory,記憶體溢出)問題。
easyExcel
easyExcel 官網: https://github.com/alibaba/easyexcel
官方文檔:https://www.yuque.com/easyexcel/doc/easyexcel
介紹
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-QzD18Zjx-1622371513716)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183743793-2035327558.png)]
easyExcel 是阿裡巴巴開源的一個Excel處理架構,以使用簡單,節省記憶體著稱。
easyExcel能大大減少占用記憶體的主要原因是在解析Excel時沒有将檔案資料一次性全部加載到記憶體中,而是從磁盤上一行行讀取資料,進行逐個解析。
easyExcel 和 POI 在解析Excel時的對比圖

POI - Excel 寫
建立一個項目,建立普通Maven的Module
導入依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shiguang</groupId>
<artifactId>poi-easyExcel</artifactId>
<version>1.0-SNAPSHOT</version>
<!--導入依賴-->
<dependencies>
<!-- xls (03) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!-- xlsx (07) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 日期格式化工具 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.8</version>
</dependency>
<!-- 單元測試 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
注意:2003和2007版本存在相容性的問題!03版本最多隻有65535行!
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-B589QY0P-1622371513724)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183024518-1536589830.png)]
可以将Excle 抽象出工作簿,工作表,行,列 這幾個對象
Workbook接口下的實作類
示例代碼
package com.shiguang;
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.FileOutputStream;
import java.io.IOException;
public class ExcelWriteTest {
String PATH = "D:\\Desktop\\"; //表輸出路徑
@Test
public void testWrite03() throws IOException {
//1.建立一個工作簿
Workbook workbook = new HSSFWorkbook();
//2.建立一個工作表
Sheet sheet = workbook.createSheet("時光收支統計表");
//3.建立一個行
Row row1 = sheet.createRow(0);
//4.建立一個單元格
// 第一行一列資料(1,1)
Cell cell11 = row1.createCell(0);
cell11.setCellValue("今日新增收入");
//第一行二列資料(1,2)
Cell cell12 = row1.createCell(1);
cell12.setCellValue("統計時間");
//第二行
Row row2 = sheet.createRow(1);
//第二行第一列(2,1)
Cell cell21 = row2.createCell(0);
cell21.setCellValue("888");
//第二行第二列(2,2)
Cell cell22 = row2.createCell(1);
String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell22.setCellValue(time);
//生成一張表(IO操作,需要使用流) 03版本使用xls結尾,
FileOutputStream fileOutputStream = new FileOutputStream(PATH + "時光收支情況統計表03.xls");
//輸出
workbook.write(fileOutputStream);
//關閉流
fileOutputStream.close();
System.out.println("Excle檔案生成完畢ヾ(•ω•`)o");
}
}
效果
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-11dLLlxm-1622371513730)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183214360-1707090783.png)]
07版本隻需要将對象該為
XSSFWorkbook()
,并将表名稱字尾改為
xlsx
即可
資料批量導入
大檔案寫HSSF
示例代碼
@Test
public void testWrite03BigData() throws IOException {
long begin = System.currentTimeMillis();
//建立一個工作簿
Workbook workbook = new HSSFWorkbook();
//建立表
Sheet sheet = workbook.createSheet();
//寫入資料
for(int rowNum = 0; rowNum<65536;rowNum++){
Row row = sheet.createRow(rowNum);
for (int cellNum = 0; cellNum < 10; cellNum++) {
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}
System.out.println("工作表建立成功ヾ(^▽^*)))");
FileOutputStream OutputStream = new FileOutputStream(PATH + "大資料測試03.xls");
workbook.write(OutputStream);
OutputStream.close();
long end = System.currentTimeMillis();
System.out.println((double) (end-begin)/1000);
}
缺點:最多隻能處理
65536
行資料,否則會抛出異常
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-BBH0Drei-1622371513733)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183255295-181106482.png)]
優點: 先寫入緩存,最後一次性寫入磁盤,速度快
大檔案寫XSSF
示例代碼
@Test
public void testWrite07BigData() throws IOException {
long begin = System.currentTimeMillis();
//建立一個工作簿
Workbook workbook = new XSSFWorkbook();
//建立表
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("工作表建立成功ヾ(^▽^*)))");
FileOutputStream OutputStream = new FileOutputStream(PATH + "大資料測試07.xlsx");
workbook.write(OutputStream);
OutputStream.close();
long end = System.currentTimeMillis();
System.out.println((double) (end-begin)/1000);
}
缺點:寫資料時速度非常慢,非常小号記憶體,并且會發生記憶體溢出,如處理100萬條資料時
優點:可以寫較大的資料量,如20萬條
大檔案寫SXSSF
示例代碼
@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("工作表建立成功ヾ(^▽^*)))");
FileOutputStream OutputStream = new FileOutputStream(PATH + "大資料測試07S.xlsx");
workbook.write(OutputStream);
OutputStream.close();
//清除臨時檔案
((SXSSFWorkbook) workbook).dispose();
long end = System.currentTimeMillis();
System.out.println((double) (end-begin)/1000);
}
優點:可以寫非常大的資料量,如100萬條甚至更多條,寫資料速度快,占用更少的記憶體
注意:
過程中會産生臨時檔案,需要清理臨時檔案
預設有100條記錄被儲存在記憶體中,如果超出這個數量,則最前面的資料會被寫入臨時檔案中。
如果想自定義記憶體中資料的數量,可以使用
new SXSSFWorkbook(數量)
SXSSFWorkbook官方解釋:“BigGridDemo”政策的流式XSSFWorkbook版本,這允許寫入非常大的檔案而不會耗盡記憶體,因為任何時候隻有可配置的行部分被儲存在記憶體中。
請注意,仍可能會消耗大量記憶體,這些記憶體基于您正在使用的功能,如合并區域,注釋…這些功能仍然隻存儲在記憶體中,是以如果廣泛使用,可能需要大量記憶體。
POI-Excle 讀
03版本
表資料
示例代碼
package com.shiguang;
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.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.joda.time.DateTime;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ExcelReadTest {
String PATH = "D:\\Desktop\\"; //Excel表所在路徑
@Test
public void testRead03() throws IOException {
//1.建立一個工作簿 03
// 擷取檔案流
FileInputStream inputStream = new FileInputStream(PATH + "時光收支情況統計表03.xls");
Workbook workbook = new HSSFWorkbook(inputStream);
//2.擷取一個工作表
Sheet sheet = workbook.getSheetAt(0);
//擷取第一行
Row row = sheet.getRow(1);
//擷取第一行第一列
Cell cell = row.getCell(0);
//讀取值時一定要注意類型
//getStringCellValue() 字元串類型
//System.out.println(cell.getStringCellValue());
//getNumericCellValue() 數值類型
System.out.println(cell.getNumericCellValue());
//關閉流
inputStream.close();
}
}
07 版本
示例代碼
@Test
public void testRead07() throws IOException {
//1.建立一個工作簿 07
// 擷取檔案流
FileInputStream inputStream = new FileInputStream(PATH + "時光收支情況統計表07.xlsx");
Workbook workbook = new XSSFWorkbook(inputStream);
//2.擷取一個工作表
Sheet sheet = workbook.getSheetAt(0);
//擷取第一行
Row row = sheet.getRow(1);
//擷取第一行第一列
Cell cell = row.getCell(0);
//讀取值時一定要注意類型
//getStringCellValue() 字元串類型
//System.out.println(cell.getStringCellValue());
//getNumericCellValue() 數值類型
System.out.println(cell.getNumericCellValue());
//關閉流
inputStream.close();
}
注意:讀取值時一定要注意類型
讀取不同資料類型
示例代碼
@Test
public void testRead07demo() throws IOException {
//擷取檔案流
FileInputStream fileInputStream = new FileInputStream(PATH + "銷售資訊統計表.xls");
//建立一個工作簿
Workbook workbook = new HSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
//擷取表頭資訊
Row rowTitle = sheet.getRow(0);
if(rowTitle != null){
//擷取總列數
int cellCount = rowTitle.getPhysicalNumberOfCells();
//周遊所有列資訊
for (int cellNum = 0; cellNum < cellCount ; cellNum++) {
Cell cell = rowTitle.getCell(cellNum);
if(cell != null){
//擷取目前列資訊資料類型
CellType cellType = cell.getCellType();
String stringCellValue = cell.getStringCellValue();
System.out.print(stringCellValue + "|");
}
}
System.out.println();
}
//擷取表中的内容
//擷取所有行數
int rowCount = sheet.getPhysicalNumberOfRows();
//由于第0行是表頭資訊,是以從第一行開始周遊
for (int rowNum = 1; rowNum < rowCount ; rowNum++) {
Row rowData = sheet.getRow(rowNum);
if(rowData != null){
//讀取行中的列資訊
//擷取總列數
//int cellCount = rowTitle.getPhysicalNumberOfCells();
int cellCount = rowData.getPhysicalNumberOfCells();
for (int celNum = 0; celNum <cellCount ; celNum++) {
System.out.print("[" + (rowNum+1) + "-" + (celNum+1) + "]");
Cell cell = rowData.getCell(celNum);
//比對列的資料類型
if(cell != null){
//擷取目前列的資料類型
CellType cellType = cell.getCellType();
String cellValue = "";
switch (cellType){
case BLANK: // 空
System.out.println("[BLANK]");
break;
case STRING: //字元串
System.out.println("[STRING]");
cellValue = cell.getStringCellValue();
break;
case BOOLEAN: //布爾類型
System.out.println("[BOOLEAN]");
//強制轉換為字元串類型
cellValue= String.valueOf(cell.getBooleanCellValue());
break;
case NUMERIC: //數字(日期,普通數字)
System.out.print("[Numeric]");
//新版本HSSFDateUtil方法過時,使用DataUtil
if (DateUtil.isCellDateFormatted(cell)){
// 日期
System.out.print("[日期]");
Date dateCellValue = cell.getDateCellValue();
cellValue = new DateTime(dateCellValue).toString("yyyy-MM-dd");
}else {
System.out.print("[轉換為字元串輸出]");
// 不是日期格式,防止數字過長,轉換為字元串類型
// cell.setCellType(CellType.STRING); 5.0 版本後過時,使用如下方法
HSSFDataFormatter hssfDataFormat = new HSSFDataFormatter();
cellValue = hssfDataFormat.formatCellValue(cell).toString();
}
break;
case ERROR: //錯誤
System.out.println("[資料類型錯誤!!]");
break;
}
System.out.println(cellValue);
}
}
}
}
//關閉流
fileInputStream.close();
}
公式計算
表資料
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-uLLK0BwG-1622371513739)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183401806-1233248115.png)]
示例代碼
@Test
public void testFormula() throws IOException {
FileInputStream fileInputStream = new FileInputStream(PATH + "公式.xls");
Workbook workbook = new HSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
Row row = sheet.getRow(5);
Cell cell = row.getCell(0);
//拿到計算公式
FormulaEvaluator FormulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
//輸出單元格的内容
CellType cellType = cell.getCellType();
switch (cellType){
case FORMULA: //公式
//擷取目前單元格的計算公式
String cellFormula = cell.getCellFormula();
System.out.println("使用公式:\t" + cellFormula);
//計算得到計算結果
CellValue evaluate = FormulaEvaluator.evaluate(cell);
//将計算結果轉換為字元串類型
String cellValue = evaluate.formatAsString();
System.out.println("計算結果為:\t" + cellValue);
break;
}
}
EasyExcel基本使用
easyExcel 官網: https://github.com/alibaba/easyexcel
官方文檔:https://www.yuque.com/easyexcel/doc/easyexcel
導入依賴
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.10</version>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
寫入測試
實體類
@Data
public class DemoData {
@ExcelProperty("字元串标題")
private String string;
@ExcelProperty("日期标題")
private Date date;
@ExcelProperty("數字标題")
private Double doubleData;
/**
* 忽略這個字段
*/
@ExcelIgnore
private String ignore;
}
測試類
package com.shiguang.easy;
import com.alibaba.excel.EasyExcel;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Date;
public class easyTest {
String PATH = "D:\\Desktop\\";
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;
}
/**
* 最簡單的寫
* <p>1. 建立excel對應的實體對象 參照{@link DemoData}
* <p>2. 直接寫即可
*/
@Test
public void simpleWrite() {
// 寫法1
String fileName = PATH + "EasyTest.xlsx";
// 這裡 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
// 如果這裡想使用03 則 傳入excelType參數即可
EasyExcel.write(fileName, DemoData.class).sheet("模闆").doWrite(data());
}
}
此處運作時遇到了一個錯誤
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-xNKRVu5v-1622371513744)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183428098-1872114401.png)]
官方解釋:
此錯誤在組織slf4j.inf.strestcoperbinder類無法裝入記憶體時報告。當在類路徑上找不到合适的slf4j綁定時,就會發生這種情況。slf4j-nop.jar放置一個(且隻有一個), slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar 或 logback-classic.jar 的類路徑應該解決這個問題。
解決方法:
在Maven工程的pom檔案中新增如下依賴
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
重新重新整理依賴就可以了
運作
simpleWrite()
方法後生成的Excel表如下所示
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-bw8Se6ov-1622371513746)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183449011-1983856433.png)]
讀測試
實體類
參考上文
監聽器
// 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然後裡面用到spring可以構造方法傳進去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
/**
* 每隔5條存儲資料庫,實際使用中可以3000條,然後清理list ,友善記憶體回收
*/
private static final int BATCH_COUNT = 5;
List<DemoData> list = new ArrayList<DemoData>();
/**
* 假設這個是一個DAO,當然有業務邏輯這個也可以是一個service。當然如果不用存儲這個對象沒用。
*/
private DemoDAO demoDAO;
public DemoDataListener() {
// 這裡是demo,是以随便new一個。實際使用如果到了spring,請使用下面的有參構造函數
demoDAO = new DemoDAO();
}
/**
* 如果使用了spring,請使用這個構造方法。每次建立Listener的時候需要把spring管理的類傳進來
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 這個每一條資料解析都會來調用
*
* @param data
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
LOGGER.info("解析到一條資料:{}", JSON.toJSONString(data));
list.add(data);
// 達到BATCH_COUNT了,需要去存儲一次資料庫,防止資料幾萬條資料在記憶體,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存儲完成清理 list
list.clear();
}
}
/**
* 所有資料解析完成了 都會來調用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 這裡也要儲存資料,確定最後遺留的資料也存儲到資料庫
saveData();
LOGGER.info("所有資料解析完成!");
}
/**
* 加上存儲資料庫
*/
private void saveData() {
LOGGER.info("{}條資料,開始存儲資料庫!", list.size());
demoDAO.save(list);
LOGGER.info("存儲資料庫成功!");
}
}
持久層
/**
* 假設這個是你的DAO存儲。當然還要這個類讓spring管理,當然你不用需要存儲,也不需要這個類。
**/
public class DemoDAO {
public void save(List<DemoData> list) {
// 如果是mybatis,盡量别直接調用多次insert,自己寫一個mapper裡面新增一個方法batchInsert,所有資料一次性插入
}
}
注意:
如果
DemoDataListener
的
@Override
錯誤,将Modules該為8版本即可
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-H1ERgUH7-1622371513750)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183512223-2088337746.png)]
另外,還需要導入
fastjson
依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
測試代碼
@Test
public void simpleRead() {
// 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然後裡面用到spring可以構造方法傳進去
// 寫法1:
String fileName = PATH + "EasyTest.xlsx";
// 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
執行效果如下:
總結
寫入:根據固定類格式寫入
讀取:根據監聽器設定的規則進行讀取