Alibaba EasyExcel初體驗

簡介

EasyExcel是一個基於Java的簡單、省內存的讀寫Excel的開源項目。在儘量節約內存的狀況下支持讀寫百M的Excel。相對於Apache POI來講,EasyExcel是從磁盤上一行行的讀取數據,而後逐個解析,避免將大量數據加載到內存從而致使OOM。java

相關依賴:web

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel</artifactId>
	<version>2.2.6</version>
</dependency>

<!-- 可選 -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.12</version>
	<scope>provided</scope>
</dependency>

Excel導入

須要導入的數據:
spring

Model類:app

@Data
public class SchoolData {

    @ExcelProperty(value = "地址")
    private String address;

    @ExcelProperty(value = "學校名稱")
    private String schoolName;

    @ExcelProperty(value = "郵政編碼")
    private String postcode;

    @ExcelProperty(value = "收集時間")
    private Date collectDate;
}

使用@ExcelProperty註解時,可使用兩種方式將數據列和屬性進行綁定:ide

  1. 經過value指定名稱,將名稱對應的excel列的數據綁定到屬性。
  2. 經過index指定下標,將下標對應的excel列的數據綁定到屬性。

在不使用@ExcelProperty註解時,默認Model類屬性和excel列數據按照前後順序進行綁定的。post

監聽器:測試

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SchoolDataListener extends AnalysisEventListener<SchoolData> {

    List<SchoolData> schoolDataList = new ArrayList<>();

    //須要傳參時,經過構造方法傳進來
    public SchoolDataListener() {
    }

    /**
     * 每解析一行數據都會進行調用
     */
    @Override
    public void invoke(SchoolData data, AnalysisContext context) {
        //通常數據量大的時候應該分批處理,防止數據所有加載到內存,致使OOM。
        schoolDataList.add(data);
    }

    /**
     * 數據所有解析完成時調用
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        System.out.println("數據讀取完畢:");
        schoolDataList.stream().forEach(System.out::println);
    }

    /**
     * 每解析一行表頭都會進行調用
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表頭:");
        headMap.forEach((k, v) -> System.out.println(v));
        System.out.println();
    }
}

導入測試:ui

導入有如下兩種寫法,通常使用簡寫的方式就行,當須要實現複雜的功能時可能會須要另一種複雜的寫法。編碼

@Test
public void importExcel() {
	ExcelReader excelReader = null;
	try {
		excelReader = EasyExcel.read("F:\\測試數據.xlsx", SchoolData.class, new SchoolDataListener()).build();
		ReadSheet readSheet = EasyExcel.readSheet(0).build();
		excelReader.read(readSheet);
	} finally {
		if (excelReader != null) {
			// 這裏千萬別忘記關閉,讀的時候會建立臨時文件,到時磁盤會崩的
			excelReader.finish();
		}
	}
}

@Test
public void importExcelSimple() {
	//sheet() 默認讀取第一個sheet
	EasyExcel.read("F:\\測試數據.xlsx", SchoolData.class, new SchoolDataListener()).sheet().doRead();
}

輸出結果:.net

表頭:
學校名稱
地址
郵政編碼
收集時間

數據讀取完畢:
SchoolData(address=晉寧縣古城鎮舊寨村  , schoolName=晉寧縣古城鎮古城中學, postcode=650604, collectDate=Sun Jan 05 00:00:00 CST 2020)
SchoolData(address=雲南省昆明市晉寧縣二街鄉老高村  , schoolName=晉寧縣二街中學, postcode=650608, collectDate=Mon Jan 06 00:00:00 CST 2020)
SchoolData(address=雲南省昆明市晉寧縣晉城鎮寺林街  , schoolName=晉寧縣晉城中學, postcode=650605, collectDate=Tue Jan 07 00:00:00 CST 2020)
SchoolData(address=雲南省晉寧縣六街鄉中學  , schoolName=晉寧縣六街中學, postcode=650609, collectDate=Wed Jan 08 00:00:00 CST 2020)
SchoolData(address=雲南省昆明市晉寧縣新街鄉文河村  , schoolName=晉寧縣新街中學, postcode=650606, collectDate=Thu Jan 09 00:00:00 CST 2020)
SchoolData(address=晉寧縣中和鄉普照路  , schoolName=晉寧縣中和中學, postcode=650600, collectDate=Fri Jan 10 00:00:00 CST 2020)
SchoolData(address=雲南省昆明市晉寧縣上蒜鄉上蒜中學  , schoolName=晉寧縣上蒜鄉上蒜中學, postcode=650607, collectDate=Sat Jan 11 00:00:00 CST 2020)
SchoolData(address=雲南省昆明市晉寧縣化樂鄉化樂村  , schoolName=晉寧縣化樂鄉中學, postcode=650611, collectDate=Sun Jan 12 00:00:00 CST 2020)
SchoolData(address=晉寧縣夕陽鄉夕陽街夕陽民族中學  , schoolName=晉寧縣夕陽民族中學, postcode=650603, collectDate=Mon Jan 13 00:00:00 CST 2020)
SchoolData(address=雲南省昆明市晉寧縣寶峯鎮古城村委會小古城村  , schoolName=晉寧縣寶峯鎮寶峯中學, postcode=650601, collectDate=Tue Jan 14 00:00:00 CST 2020)
SchoolData(address=晉寧縣雙河鄉椿樹營  , schoolName=晉寧縣雙河民族中學, postcode=650602, collectDate=Wed Jan 15 00:00:00 CST 2020)
SchoolData(address=昆明市富民縣永定鎮環城南路7號永定中學  , schoolName=富民縣永定中學, postcode=650400, collectDate=Thu Jan 16 00:00:00 CST 2020)
SchoolData(address=富民縣永定鎮大西山村  , schoolName=富民縣勤勞中學, postcode=650400, collectDate=Fri Jan 17 00:00:00 CST 2020)
SchoolData(address=富民縣大營鎮大營街16號  , schoolName=富民縣大營中學, postcode=650400, collectDate=Sat Jan 18 00:00:00 CST 2020)
SchoolData(address=雲南省昆明市羅免鄉者北村委會者北街  , schoolName=富民縣第二中學, postcode=650401, collectDate=Sun Jan 19 00:00:00 CST 2020)

Excel導出

模擬生成數據:

public class DataGenerate {

    /**
     * 數據生成
     */
    public static List<SchoolData> data() {
        List<SchoolData> schoolDataList = new ArrayList<>();
        SchoolData schoolData;
        for (int i = 0; i < 15; i++) {
            schoolData = new SchoolData();
            schoolData.setSchoolName("第" + i + "XXX中學");
            schoolData.setAddress("雲南省昆明市XXXXX");
            schoolData.setPostcode("123456");
            schoolData.setCollectDate(new Date());
            schoolDataList.add(schoolData);
        }

        return schoolDataList;
    }
}

測試導出:

同導入,導出也有如下兩種寫法。LongestMatchColumnWidthStyleStrategy用來設置自動列寬,若是須要設置列寬、行高等屬性,能夠在Model類上添加對應註解進行設置,如@ColumnWidth、@ContentRowHeight、@HeadRowHeight等等。

@Test
public void exportExcel() {
	ExcelWriter excelWriter = null;
	try {
		excelWriter = EasyExcel.write("F:\\測試數據_副本.xlsx", SchoolData.class).build();
		WriteSheet writeSheet = EasyExcel.writerSheet("學校數據").registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();
		excelWriter.write(DataGenerate.data(), writeSheet);
	} finally {
		// 千萬別忘記finish 會幫忙關閉流
		if (excelWriter != null) {
			excelWriter.finish();
		}
	}
}

@Test
public void exportExcelSimple() {
	EasyExcel.write("F:\\測試數據_副本.xlsx", SchoolData.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("學校數據").doWrite(DataGenerate.data());
}

導出結果:

基於Excel模板的填充

模板:

{} 表明普通變量 {.} 表明是list的變量。

模板填充測試:

@Test
public void excelTemplateFill() {
	ExcelWriter excelWriter = EasyExcel.write("F:\\測試數據_副本3.xlsx").withTemplate("F:\\模板.xlsx").build();
	WriteSheet writeSheet = EasyExcel.writerSheet().build();
	FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

	//填充列表數據
	excelWriter.fill(DataGenerate.data(), fillConfig, writeSheet);

	//填充普通數據
	Map<String, Object> map = new HashMap<String, Object>();
	map.put("name", "狗子");
	map.put("date", "2021-02-19");
	excelWriter.fill(map, writeSheet);
	excelWriter.finish();
}

填充結果:

Web中的導入和導出

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;

@RestController
public class SchoolDataController {

    /**
     * 導入Excel
     */
    @PostMapping("/import")
    public String importExcel(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), SchoolData.class, new SchoolDataListener()).sheet().doRead();
        return "success";
    }

    /**
     * 導出Excel
     */
    @GetMapping("/export")
    public void exportExcel(HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 這裏URLEncoder.encode能夠防止中文亂碼 固然和easyexcel沒有關係
        String fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        // 這裏須要設置不關閉流
        EasyExcel.write(response.getOutputStream(), SchoolData.class).autoCloseStream(Boolean.FALSE).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("學校數據").doWrite(DataGenerate.data());
    }
}

參考:官方文檔

相關文章
相關標籤/搜索