Java?EasyExcel導出合并單元格的示例詳解
前言
使用spring boot 對excel 進行操作在平時項目中要經(jīng)常使用。常見通過jxl和poi 的方式進行操作。但他們都存在一個嚴重的問題就是非常的耗內(nèi)存。這里介紹一種 Easy Excel 工具來對excel進行操作。
一、Easy Excel是什么
EasyExcel是阿里巴巴開源的一個excel處理框架,以使用簡單、節(jié)省內(nèi)存著稱。easyExcel能大大減少占用內(nèi)存的主要原因是在解析Excel時沒有將文件數(shù)據(jù)一次性全部加載到內(nèi)存中,而是從磁盤上一行行讀取數(shù)據(jù),逐個解析。
二、使用EasyExcel 實現(xiàn)讀操作
從excel 中讀取數(shù)據(jù),常用的場景就是讀取excel的數(shù)據(jù),將相應(yīng)的數(shù)據(jù)保存到數(shù)據(jù)庫中。需要實現(xiàn)一定的邏輯處理。
1、導入依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.10</version>
</dependency>2、創(chuàng)建讀取數(shù)據(jù)封裝類
@Data
public class User {
@ExcelProperty(index = 0)
private Integer id;
@ExcelProperty(index = 1)
private String name;
@ExcelProperty(index = 2)
private Integer age;
}比如我們要讀取兩列的數(shù)據(jù),就寫兩個屬性。@ExcelProperty(index = 0)來設(shè)置要讀取的列,index=0表示讀取第一列。
3、創(chuàng)建讀取excel的監(jiān)聽類
監(jiān)聽器繼承 AnalysisEventListener 類
@Slf4j
public class UserExcelListener extends AnalysisEventListener<User> {
/**
* 解析excel文檔的每一行
* @param user 參數(shù)user即是每行讀取數(shù)據(jù)轉(zhuǎn)換的User對象
* @param analysisContext
*/
@Override
public void invoke(User user, AnalysisContext analysisContext){
log.info("excel數(shù)據(jù)行:{}",user.toString());
}
/**
* 整個文檔解析完執(zhí)行
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
log.info("文檔解析完畢");
}
}當解析每一條數(shù)據(jù)時都會調(diào)用invoke方法,當所有數(shù)據(jù)都解析完畢時最后會調(diào)用doAfterAllAnalysed方法??梢栽诒O(jiān)聽類內(nèi)的方法中將每次讀取到的數(shù)據(jù)進行保存或者其他操作處理。
4、接口使用easyExcel讀取excel文件調(diào)用監(jiān)聽器
/**
* 上傳excel文件并讀取其中內(nèi)容
*
* @param file
* @return
*/
@PostMapping("/upload")
public String uploadExcel(MultipartFile file) {
log.info("easyExcel上傳文件:{}", file);
try {
InputStream inputStream = file.getInputStream();
EasyExcel.read(inputStream, User.class, <strong>new</strong><strong> UserExcelListener()</strong>)
.sheet()
.doRead();
} catch (Exception e) {
}
return "表格文件上傳成功";
}三、使用EasyExcel 實現(xiàn)寫操作
寫操作有兩種寫法,一種是不創(chuàng)建對象的寫入,另一種是根據(jù)對象寫入。這里主要介紹創(chuàng)建對象寫入
創(chuàng)建對象寫入
1、創(chuàng)建excel對象類
@Data
public class User {
@ExcelProperty(index = 0)
private Integer id;
@ExcelProperty(index = 1)
private String name;
@ExcelProperty(index = 2)
private Integer age;
}注意@ExcelProperty(“用戶編號”) 會生成相應(yīng)的列名為 用戶編號,如果不設(shè)置,則會直接將字段名設(shè)置為excel的列名。
2、接口使用測試數(shù)據(jù)導出(常規(guī)導出不合并單元格)
/**
* 輸出導出excel
*/
@PostMapping("/export")
public void export() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setId(i);
user.setName("測試用戶-" + i);
user.setAge(20 + i);
users.add(user);
}
log.info("導出數(shù)據(jù)結(jié)果集:{}", users);
String fileName = "C:\\Users\\pytho\\Desktop\\fsdownload\\用戶信息表.xlsx";
EasyExcel.write(fileName, User.class)
.autoCloseStream(true)
.sheet("sheet名稱")
.doWrite(users);
}3、接口測試導出(單列合并單元格)
/**
* 輸出導出excel
*/
@PostMapping("/export1")
public void export1() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setId(i);
if (i == 3 || i == 4 || i == 5) {
user.setName("測試用戶-3");
} else {
user.setName("測試用戶-" + i);
}
user.setAge(20 + i);
users.add(user);
}
log.info("導出數(shù)據(jù)結(jié)果集:{}", users);
String fileName = "C:\\Users\\pytho\\Desktop\\fsdownload\\(單列相同內(nèi)容合并單元格)用戶信息表.xlsx";
EasyExcel.write(fileName, User.class)
<strong> .registerWriteHandler(</strong><strong>new</strong><strong> SimpleExcelMergeUtil())</strong>
.autoCloseStream(true)
.sheet("sheet名稱")
.doWrite(users);
}如果要對導出的excel進行處理,就需要自定義處理器類進行處理
自定義easyExcel處理器(單列合并:根據(jù)用戶id相同的列進行合并單元格):
/**
* @version 1.0
* @Package: com.stech.bms.buss.utils
* @ClassName: ExcelMergeUtil
* @Author: sgq
* @Date: 2023/7/28 13:29
* @Description: 僅處理單列數(shù)據(jù)相同合并單元格
*/
public class SimpleExcelMergeUtil implements CellWriteHandler {
public SimpleExcelMergeUtil() {
}
/**
* 創(chuàng)建每個單元格之前執(zhí)行
*
* @param writeSheetHolder
* @param writeTableHolder
* @param row
* @param head
* @param columnIndex
* @param relativeRowIndex
* @param isHead
*/
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 創(chuàng)建每個單元格之后執(zhí)行
*
* @param writeSheetHolder
* @param writeTableHolder
* @param cell
* @param head
* @param relativeRowIndex
* @param isHead
*/
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 每個單元格數(shù)據(jù)內(nèi)容渲染之后執(zhí)行
*
* @param writeSheetHolder
* @param writeTableHolder
* @param cellData
* @param cell
* @param head
* @param relativeRowIndex
* @param isHead
*/
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 每個單元格完全創(chuàng)建完之后執(zhí)行
*
* @param writeSheetHolder
* @param writeTableHolder
* @param cellDataList
* @param cell
* @param head
* @param relativeRowIndex
* @param isHead
*/
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
// 當前行
int curRowIndex = cell.getRowIndex();
// 當前列
int curColIndex = cell.getColumnIndex();
if (!isHead) {
if (curRowIndex > 1 && curColIndex == 1) {
// 從第二行數(shù)據(jù)行開始,獲取當前行第二列數(shù)據(jù)
Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
// 獲取上一行第二列數(shù)據(jù)
Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
if (curData.equals(preData)) {
Sheet sheet = writeSheetHolder.getSheet();
List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
boolean isMerged = false;
for (int i = 0; i < mergedRegions.size() && !isMerged; i++) {
CellRangeAddress cellRangeAddr = mergedRegions.get(i);
// 若上一個單元格已經(jīng)被合并,則先移出原有的合并單元,再重新添加合并單元
if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
sheet.removeMergedRegion(i);
cellRangeAddr.setLastRow(curRowIndex);
sheet.addMergedRegion(cellRangeAddr);
isMerged = true;
}
}
// 若上一個單元格未被合并,則新增合并單元
if (!isMerged) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
sheet.addMergedRegion(cellRangeAddress);
}
}
}
}
}
}4、接口測試導出(通用合并單元格)
/**
* 輸出導出excel
*/
@PostMapping("/export2")
public void export2() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setId(i);
if (i == 3 || i == 4 || i == 5) {
user.setName("測試用戶-3");
} else {
user.setName("測試用戶-" + i);
}
user.setAge(20 + i);
users.add(user);
}
log.info("導出數(shù)據(jù)結(jié)果集:{}", users);
// 從第幾行開始合并
int mergeStartRowIndex = 5;
// 需要合并哪些列
int[] mergeColumns = {1};
String fileName = "C:\\Users\\pytho\\Desktop\\fsdownload\\(單列相同內(nèi)容合并單元格-通用版)用戶信息表.xlsx";
EasyExcel.write(fileName, User.class)
.registerWriteHandler(new SimpleCommonExcelMergeUtil(mergeStartRowIndex,mergeColumns))
.autoCloseStream(true)
.sheet("sheet名稱")
.doWrite(users);
}excel處理器類:
/**
* @version 1.0
* @Package: com.stech.bms.buss.utils
* @ClassName: ExcelMergeUtil
* @Author: sgq
* @Date: 2023/7/28 13:29
* @Description: 僅處理單列數(shù)據(jù)相同合并單元格
*/
public class SimpleCommonExcelMergeUtil implements CellWriteHandler {
private int mergeStartRowIndex;
private int[] mergeColumns;
private List<Integer> mergeColumnList;
public SimpleCommonExcelMergeUtil() {
}
public SimpleCommonExcelMergeUtil(int mergeStartRowIndex, int[] mergeColumns) {
this.mergeStartRowIndex = mergeStartRowIndex;
this.mergeColumns = mergeColumns;
mergeColumnList = new ArrayList<>();
for (int i : mergeColumns) {
mergeColumnList.add(i);
}
}
/**
* 創(chuàng)建每個單元格之前執(zhí)行
*
* @param writeSheetHolder
* @param writeTableHolder
* @param row
* @param head
* @param columnIndex
* @param relativeRowIndex
* @param isHead
*/
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 創(chuàng)建每個單元格之后執(zhí)行
*
* @param writeSheetHolder
* @param writeTableHolder
* @param cell
* @param head
* @param relativeRowIndex
* @param isHead
*/
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 每個單元格數(shù)據(jù)內(nèi)容渲染之后執(zhí)行
*
* @param writeSheetHolder
* @param writeTableHolder
* @param cellData
* @param cell
* @param head
* @param relativeRowIndex
* @param isHead
*/
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 每個單元格完全創(chuàng)建完之后執(zhí)行
*
* @param writeSheetHolder
* @param writeTableHolder
* @param cellDataList
* @param cell
* @param head
* @param relativeRowIndex
* @param isHead
*/
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
// 當前行
int curRowIndex = cell.getRowIndex();
// 當前列
int curColIndex = cell.getColumnIndex();
if (!isHead) {
if (curRowIndex > mergeStartRowIndex && mergeColumnList.contains(curColIndex)) {
// 從第二行數(shù)據(jù)行開始,獲取當前行第二列數(shù)據(jù)
Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
// 獲取上一行第二列數(shù)據(jù)
Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
if (curData.equals(preData)) {
Sheet sheet = writeSheetHolder.getSheet();
List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
boolean isMerged = false;
for (int i = 0; i < mergedRegions.size() && !isMerged; i++) {
CellRangeAddress cellRangeAddr = mergedRegions.get(i);
// 若上一個單元格已經(jīng)被合并,則先移出原有的合并單元,再重新添加合并單元
if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
sheet.removeMergedRegion(i);
cellRangeAddr.setLastRow(curRowIndex);
sheet.addMergedRegion(cellRangeAddr);
isMerged = true;
}
}
// 若上一個單元格未被合并,則新增合并單元
if (!isMerged) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
sheet.addMergedRegion(cellRangeAddress);
}
}
}
}
}
}這只是簡單的合并單元格例子,拋磚引玉的作用。工作中可能會遇到很多情況:合并單元格后第一列序列號也需要根據(jù)其他列進行合并單元格且序列號還必須保持連續(xù),根據(jù)部分列合并單元格,隔行合并單元格等等情況,這就需要開發(fā)者對easyExcel的處理器類里面的api比較了解才能完成。遇到的問題也可以留言,看到也會嘗試一起處理解決。
到此這篇關(guān)于Java EasyExcel導出合并單元格的示例詳解的文章就介紹到這了,更多相關(guān)Java EasyExcel內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Cloud Data Flow初體驗以Local模式運行
這篇文章主要介紹了Spring Cloud Data Flow初體驗以Local模式運行,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
Spring @Valid @Validated實現(xiàn)驗證
這篇文章主要介紹了Spring @Valid @Validated實現(xiàn)驗證,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-01-01
spring boot和spring cloud之間的版本關(guān)系
這篇文章主要介紹了spring boot和spring cloud之間的版本關(guān)系,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
Java 實戰(zhàn)練手項目之校園超市管理系統(tǒng)的實現(xiàn)流程
讀萬卷書不如行萬里路,只學書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+Mysql+Maven+Bootstrap實現(xiàn)一個校園超市管理系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-11-11

