天天看點

簡潔、快速、節約記憶體的Excel處理工具EasyExcel

作者:river106

1、簡介

EasyExcel是一個基于Java的、快速、簡潔、解決大檔案記憶體溢出的Excel處理工具。

他能讓你在不用考慮性能、記憶體的等因素的情況下,快速完成Excel的讀、寫等功能。

EasyExcel基于POI進行封裝優化,降低記憶體使用,再大的excel也不會出現記憶體溢出,讓使用更加簡單友善。

官網: https://easyexcel.opensource.alibaba.com/

github: https://github.com/alibaba/easyexcel

gitee:https://gitee.com/easyexcel/easyexcel

2、讀取Excel

EasyExcel需引入如下依賴:

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

示例中還需引入其他依賴:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.60</version>
</dependency>           

示例Excel表格demo1.xlsx如下:

簡潔、快速、節約記憶體的Excel處理工具EasyExcel

讀取Excel資料的對象:

@Data
@HeadRowHeight(20)
@ColumnWidth(20)
public class DemoData {
    @ExcelProperty("字元串标題")
    private String string;
    @ExcelProperty("日期标題")
    private Date date;
    @ExcelProperty("數字标題")
    private Double doubleData;
    @ExcelIgnore
    private String ignore;
}           

ExcelProperty注解: 用于比對excel和實體類;

ExcelIgnore注解:預設所有字段都會和excel去比對,加了這個注解會忽略該字段;

HeadRowHeight注解:标注在類上,表頭高

ColumnWidth注解:标注在類或字段上,列寬

@Slf4j
public class EasyExcelReadTest {

    @Test
    public void syncRead() {
        String fileName = "demo1.xlsx";
        // 這裡需要指定讀用哪個class去讀,然後讀取第一個sheet 同步讀取會自動finish
        List<DemoData> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync();
        for (DemoData data : list) {
            log.info("讀取到資料:{}", JSON.toJSONString(data));
        }

        // 這裡也可以不指定class,傳回一個list,然後讀取第一個sheet 同步讀取會自動finish
        List<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync();
        for (Map<Integer, String> data : listMap) {
            // 傳回每條資料的鍵值對 表示所在的列 和所在列的值
            log.info("讀取到資料:{}", JSON.toJSONString(data));
        }
    }

    /**
     * 最簡單的讀
     */
    @Test
    public void simpleRead() {
        // 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然後裡面用到spring可以構造方法傳進去
        // 寫法1:
        String fileName = "demo1.xlsx";
        // 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet,檔案流會自動關閉
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

        // 寫法2:
        try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) {
            // 建構一個sheet 這裡可以指定名字或者no
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            // 讀取一個sheet
            excelReader.read(readSheet);
        }
    }

}           

讀監聽器:

/**
 * 模闆的讀取類
 *
 * 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然後裡面用到spring可以構造方法傳進去
 */
@Slf4j
public class DemoDataListener extends AnalysisEventListener<DemoData> {
    /**
     * 每隔5條存儲資料庫,實際使用中可以1000條,然後清理list,友善記憶體回收
     */
    private static final int BATCH_COUNT = 5;
    private List<DemoData> list = new ArrayList<>();
    private DemoService demoService;

    public DemoDataListener() {
        // 這裡是demo,是以随便new一個。實際使用如果到了spring,請使用下面的有參構造函數
        demoService = new DemoService();
    }

    public DemoDataListener(DemoService demoService) {
        this.demoService = demoService;
    }

    /**
     * 這個每一條資料解析都會來調用
     *
     * @param data
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        log.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();
        log.info("所有資料解析完成!");
    }

    /**
     * 加上存儲資料庫
     */
    private void saveData() {
        log.info("{}條資料,開始存儲資料庫!", list.size());
        demoService.save(list);
        log.info("存儲資料庫成功!");
    }
}           

業務層資料處理:

public class DemoService {
    public void save(List<DemoData> list) {
        // 這裡寫入資料庫
    }
}           

3、寫入Excel

@Slf4j
public class EasyExcelWriteTest {

    @Test
    public void writeExcel() {
        EasyExcel.write("demo1.xlsx", 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;
    }
}           

4、Web上傳下載下傳

@Controller
public class UploadDownController {

    @Autowired
    private DemoService demoService;

    /**
     * 檔案下載下傳(失敗了會傳回一個有部分資料的Excel)
     */
    @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").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), DemoData.class).sheet("模闆").doWrite(data());
    }

    /**
     * 檔案下載下傳并且失敗的時候傳回json(預設失敗了會傳回一個有部分資料的Excel)
     *
     * @since 2.1.1
     */
    @GetMapping("downloadFailedUsingJson")
    public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
        // 這裡注意有同學反應使用swagger會導緻各種問題,請直接用浏覽器或者用postman
        try {
            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(), DemoData.class).autoCloseStream(Boolean.FALSE).sheet("模闆")
                .doWrite(data());
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<String, String>();
            map.put("status", "failure");
            map.put("message", "下載下傳檔案失敗" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        }
    }

    /**
     * 檔案上傳
     */
    @PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), DemoData.class, new DemoDataListener(demoService)).sheet().doRead();
        return "success";
    }

    private List<DemoData> data() {
        List<DemoData> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字元串" + 0);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }
}           

Excel上傳時,擷取Excel資料,我們也可以封裝個工具類ExcelUtils:

@Slf4j
public class ExcelUtils {

    public static <T> List<T> getExcelModelData(final InputStream inputStream, Class<T> clazz) {
        if (null == inputStream) {
            throw new NullPointerException("the inputStream is null!");
        }
        ExcelReaderBuilder result = EasyExcel.read(inputStream, clazz, null);
        ExcelReaderSheetBuilder sheet1 = result.sheet();
        List<T> resultData = sheet1.doReadSync();
        return resultData;
    }
}           

上傳的代碼也可改為如下:

@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
    List<DemoData> excelModelData = ExcelUtils.getExcelModelData(file.getInputStream(), DemoData.class);
    demoService.save(excelModelData);
    return "success";
}           

其他示例可參考:

EasyExcel Demo: https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read

API參考:

EasyExcel API: https://easyexcel.opensource.alibaba.com/docs/current/api/

原文連結:https://river106.cn/posts/1e3d4153.html

繼續閱讀