亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

java使用EasyExcel導(dǎo)出上萬數(shù)據(jù)如何避免OOM

 更新時(shí)間:2024年11月11日 11:27:34   作者:鐘林@  
本文主要介紹了使用EasyExcel導(dǎo)出大量數(shù)據(jù)時(shí)避免OOM問題的方法,通過分頁查詢和分批次寫入Excel,可以有效避免內(nèi)存溢出,并提供了一個(gè)封裝好的工具類,簡化了導(dǎo)出代碼的編寫

一、前言

Excel 導(dǎo)出功能:大數(shù)據(jù)量的情況下,很容易出現(xiàn) OOM。

數(shù)據(jù)量不大沒有什么問題,做法是直接查全量數(shù)據(jù),然后直接往Excel里寫。但是當(dāng)數(shù)據(jù)量逐漸多了起來后,達(dá)到一萬多條,導(dǎo)出的時(shí)候就會(huì)報(bào)OOM。然后換成了阿里開源的EasyExcel,但是導(dǎo)出的時(shí)候也不太穩(wěn)定,偶爾也會(huì)出現(xiàn)OOM。所以應(yīng)該是數(shù)據(jù)量太大了,在寫入的時(shí)候把內(nèi)存占滿了。解決方式:放棄了查全量數(shù)據(jù)一次性寫入Excel的做法,采用分頁查詢,分批次寫入Excel的方式,果然不會(huì)出現(xiàn)OOM了。

封裝了一個(gè)EasyExcel的導(dǎo)出工具類,這樣只要在分頁查詢的基礎(chǔ)上寫少量的代碼,就可以實(shí)現(xiàn)分批次寫入Excel,簡化代碼的編寫并且解決OOM的問題。

二、實(shí)現(xiàn)

@Slf4j
public abstract class EasyExcelExport<T, S> {

    /**
     * EasyExcel導(dǎo)出Excel表格,每個(gè)sheet默認(rèn)最大10萬條數(shù)據(jù)
     *
     * @param fileName  excel文件前綴名
     * @param sheetName 表頁名
     */
    public void easyExcelBatchExport(String fileName, String sheetName, HttpServletResponse response) {
        this.easyExcelBatchExport(fileName, sheetName, 100000, response);
    }

    /**
     * 分批次導(dǎo)出excel數(shù)據(jù)
     *
     * @param fileName  excel文件前綴名
     * @param sheetSize 每個(gè)sheet的數(shù)據(jù)量,默認(rèn)10萬,excel有限制不能大于1048576
     * @param sheetName 表頁名
     */
    public void easyExcelBatchExport(String fileName, String sheetName, Integer sheetSize, HttpServletResponse response) {
        fileName = fileName + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
        int currentSheet = 1;   // 當(dāng)前處于第幾個(gè)sheet
        int totalLine = 0;      // 總共寫入的條數(shù)
        int currentBatch = 1;   // 當(dāng)前寫入excel的批次(第幾頁)
        int lineNum = 1;        // 行號,當(dāng)前寫入的是第幾條數(shù)據(jù)

        long startTime = System.currentTimeMillis();
        try {
            response.setCharacterEncoding("utf-8");
            // 告訴瀏覽器用什么軟件可以打開此文件
            response.setHeader("content-Type", "application/vnd.ms-excel");
            // 下載文件的默認(rèn)名稱
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));

            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]).build();
            WriteSheet sheet = EasyExcel.writerSheet(sheetName).build();

            while (true) {
                // 獲取數(shù)據(jù),然后currentBatch+1,下次調(diào)用就會(huì)獲取新的數(shù)據(jù)
                List<S> sourceDataList = getData(currentBatch);
                currentBatch++;

                List<T> exportEntityList = new ArrayList<>();
                if (CollUtil.isNotEmpty(sourceDataList)) {
                    totalLine += sourceDataList.size();
                    log.info("EasyExcel開始寫入第{}批數(shù)據(jù),當(dāng)前批次數(shù)據(jù)大小為{}", currentBatch - 1, sourceDataList.size());
                    for (S sourceData : sourceDataList) {
                        exportEntityList.add(convertSourceData2ExportEntity(sourceData, lineNum));
                        lineNum++;

                        // 當(dāng)前sheet數(shù)據(jù)已經(jīng)到達(dá)最大值,將當(dāng)前數(shù)據(jù)全寫入當(dāng)前sheet,下一條數(shù)據(jù)就會(huì)寫入新sheet
                        if (lineNum > sheetSize) {
                            excelWriter.write(exportEntityList, sheet);
                            exportEntityList.clear();
                            lineNum = 1;
                            currentSheet++;
                            sheet = EasyExcel.writerSheet(sheetName + currentSheet).build();
                        }
                    }

                    // 寫入excel
                    excelWriter.write(exportEntityList, sheet);
                } else {
                    // 未獲取到數(shù)據(jù),結(jié)束
                    break;
                }
            }
            excelWriter.finish();
        } catch (Exception e) {
            log.error("EasyExcel導(dǎo)出異常", e);
        }

        log.info("EasyExcel導(dǎo)出數(shù)據(jù)結(jié)束,總數(shù)據(jù)量為{},耗時(shí){}ms", totalLine, (System.currentTimeMillis() - startTime));
    }

    /**
     * 不分批次導(dǎo)出excel。一次性獲取所有數(shù)據(jù)寫入excel,確定數(shù)據(jù)量不大時(shí)可以使用該方法,數(shù)據(jù)量過大時(shí)使用分批次導(dǎo)出,否則會(huì)OOM
     *
     * @param fileName  excel文件前綴名
     * @param sheetName 表頁名
     */
    public void easyExcelExport(String fileName, String sheetName, HttpServletResponse response) {
        fileName = fileName + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
        int totalLine = 0;      // 總共寫入的條數(shù)
        int lineNum = 1;        // 行號,當(dāng)前寫入的是第幾條數(shù)據(jù)

        long startTime = System.currentTimeMillis();
        try {
            response.setCharacterEncoding("utf-8");
            // 告訴瀏覽器用什么軟件可以打開此文件
            response.setHeader("content-Type", "application/vnd.ms-excel");
            // 下載文件的默認(rèn)名稱
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));

            List<S> sourceDataList = getData(1);
            List<T> exportEntityList = new ArrayList<>();
            if (CollUtil.isNotEmpty(sourceDataList)) {
                totalLine += sourceDataList.size();
                log.info("EasyExcel開始寫入數(shù)據(jù),數(shù)據(jù)大小為{}", sourceDataList.size());
                for (S sourceData : sourceDataList) {
                    exportEntityList.add(convertSourceData2ExportEntity(sourceData, lineNum));
                    lineNum++;
                }
            }
            response.setCharacterEncoding("utf-8");
            // 告訴瀏覽器用什么軟件可以打開此文件
            response.setHeader("content-Type", "application/vnd.ms-excel");
            // 下載文件的默認(rèn)名稱
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));
            EasyExcel.write(response.getOutputStream(), (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]).sheet(sheetName).doWrite(exportEntityList);
        } catch (Exception e) {
            log.error("EasyExcel導(dǎo)出異常", e);
        }

        log.info("EasyExcel導(dǎo)出數(shù)據(jù)結(jié)束,總數(shù)據(jù)量為{},耗時(shí){}ms", totalLine, (System.currentTimeMillis() - startTime));
    }

    /**
     * 將原數(shù)據(jù)對象轉(zhuǎn)換為需要導(dǎo)出的目標(biāo)對象
     *
     * @param sourceData 原對象
     * @param lineNum    行號
     */
    public abstract T convertSourceData2ExportEntity(S sourceData, Integer lineNum);

    /**
     * 獲取原始數(shù)據(jù),通過currentBatch參數(shù)分頁獲取數(shù)據(jù)。
     *
     * @param currentBatch 獲取第幾批(頁)數(shù)據(jù),通過該參數(shù)分頁查詢,每次調(diào)用自動(dòng)遞增。不分批次導(dǎo)出時(shí)可以忽略該參數(shù)
     */
    public abstract List<S> getData(int currentBatch);

}

首先,這是EasyExcelExport是一個(gè)抽象類,指定了泛型 T 和 S,T是target目標(biāo)類,也就是導(dǎo)出時(shí)對應(yīng)的類,S是source原對象所對應(yīng)的類。

EasyExcelExport里還有兩個(gè)抽象方法,getData() 和 convertSourceData2ExportEntity() 。

這兩個(gè)方法是需要在平時(shí)使用時(shí)自己去實(shí)現(xiàn)的,getData是數(shù)據(jù)查詢的方法,可以在這里面去實(shí)現(xiàn)分頁查詢的邏輯,currentBatch參數(shù)是用來控制分頁查詢頁碼的,從1開始,會(huì)自動(dòng)遞增。如果確定數(shù)據(jù)量不大不需要分批次導(dǎo)出的話,那么getData()里只需要進(jìn)行普通的查詢即可,忽略currentBatch參數(shù)不用分頁查詢。還有一個(gè)方法是convertSourceData2ExportEntity(),這個(gè)是用來將對象S轉(zhuǎn)為對象T的方法,因?yàn)閺臄?shù)據(jù)庫查詢或者是從其他地方獲取到的對象類型可能是S,而導(dǎo)出時(shí)需要的對象類型是T,所以通過該方法進(jìn)行對象轉(zhuǎn)換。

最核心的是 easyExcelBatchExport() 方法,里面有一個(gè)while循環(huán),while循環(huán)里首先會(huì)去調(diào)用getData()方法獲取數(shù)據(jù),然后將currentBatch加1便于下次獲取數(shù)據(jù),接下來有個(gè)for循環(huán)去進(jìn)行對象的轉(zhuǎn)換并添加到exportEntityList集合中,這個(gè)集合中裝的是最終寫到Excel里的對象。當(dāng)轉(zhuǎn)換完成后就將當(dāng)前批次的數(shù)據(jù)寫入Excel中,然后進(jìn)行下一次循環(huán),當(dāng)getData()方法未獲取到數(shù)據(jù)時(shí),就結(jié)束循環(huán)。

同時(shí)支持指定每個(gè)sheet頁的最大行數(shù)。在對對象進(jìn)行轉(zhuǎn)換時(shí)有一個(gè)判斷,當(dāng)前sheet頁的數(shù)據(jù)是否到達(dá)指定值,到達(dá)后,直接寫入excel,然后新建一個(gè)sheet頁,這樣新的數(shù)據(jù)就會(huì)寫入新的sheet頁。

使用

那么如何使用這個(gè)工具類呢。很簡單,只要new出EasyExcelExport的對象,然后實(shí)現(xiàn)一下 convertSourceData2ExportEntity() 方法和 getData() 方法即可,然后再根據(jù)需要去調(diào)用不同的導(dǎo)出方法即可。導(dǎo)出方法有指定和不指定sheet數(shù)據(jù)頁大小的分批寫入方法 easyExcelBatchExport() 和不分批次直接一次性寫入的 easyExcelExport() 方法。

下面通過一個(gè)小案例展示一下。假設(shè)現(xiàn)在有個(gè)導(dǎo)出用戶列表的需求,數(shù)據(jù)庫User表對應(yīng)的是UserPO類:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserPO {

    private Long id;

    /**
     * 用戶編號
     */
    private String code;

    /**
     * 姓名
     */
    private String name;

    /**
     * 手機(jī)號碼
     */
    private String phone;

    /**
     * 性別。1-男,2-女
     */
    private Integer sex;

}

導(dǎo)出對應(yīng)的類是UserExportEntity:

@Data
public class UserExportEntity {

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 0, value = "序號")
    private Integer line;

    @ColumnWidth(35)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 1, value = "用戶編號")
    private String code;

    @ColumnWidth(35)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 2, value = "姓名")
    private String name;

    @ColumnWidth(35)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 3, value = "手機(jī)號碼")
    private String phone;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 4, value = "性別")
    private String sexStr;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 5, value = "fieldA")
    private String fieldA;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 6, value = "fieldB")
    private String fieldB;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 7, value = "fieldC")
    private String fieldC;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 8, value = "fieldD")
    private String fieldD;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 9, value = "fieldE")
    private String fieldE;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 10, value = "fieldF")
    private String fieldF;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 11, value = "fieldG")
    private String fieldG;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 12, value = "fieldH")
    private String fieldH;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 13, value = "fieldI")
    private String fieldI;

}

先測試一下不分批次導(dǎo)出,導(dǎo)出123456條數(shù)據(jù)。

    @GetMapping("/testExport")
    public void testExport(HttpServletResponse response) {
        new EasyExcelExport<UserExportEntity, UserPO>() {
            @Override
            public UserExportEntity convertSourceData2ExportEntity(UserPO sourceData, Integer lineNum) {
                UserExportEntity entity = new UserExportEntity();
                entity.setLine(lineNum);
                entity.setCode(sourceData.getCode());
                entity.setName(sourceData.getName());
                entity.setPhone(sourceData.getPhone());
                entity.setSexStr(Objects.equals(sourceData.getSex(), 1) ? "男" : Objects.equals(sourceData.getSex(), 2) ? "女" : StrUtil.EMPTY);
                return entity;
            }

            @Override
            public List<UserPO> getData(int currentBatch) {
                List<UserPO> userPOList = new ArrayList<>();
                // 模擬查詢數(shù)據(jù)庫,假設(shè)每次查詢會(huì)查出123456條數(shù)據(jù)
                for (int i = 0; i < 123456; i++) {
                    userPOList.add(UserPO.builder()
                            .code("USER_" + RandomUtil.randomString("1234567890", 6))
                            .name(RandomUtil.randomString("qwertyuiopasdfghjklzxcvbnm", 10))
                            .phone("138" + RandomUtil.randomString("1234567890", 8))
                            .sex(RandomUtil.randomInt(1, 3))
                            .build());
                }
                log.info("userPOList-->{}", JSONUtil.toJsonStr(userPOList));
                return userPOList;
            }
        }.easyExcelExport("測試不分批次導(dǎo)出", "測試不分批次導(dǎo)出", response);
    }

為了更清晰地看到效果,我將內(nèi)存大小限制為128M。

調(diào)用一下測試接口,可以看到,導(dǎo)出十幾萬條數(shù)據(jù)時(shí)發(fā)生了OOM。

再來看看分批次導(dǎo)出的效果,模擬一下分頁查詢,假設(shè)有200頁數(shù)據(jù),每頁8888條,一共是170多萬條數(shù)據(jù)。

    @GetMapping("/testBatchExport")
    public void testBatchExport(HttpServletResponse response) {
        new EasyExcelExport<UserExportEntity, UserPO>() {
            @Override
            public UserExportEntity convertSourceData2ExportEntity(UserPO sourceData, Integer lineNum) {
                UserExportEntity entity = new UserExportEntity();
                entity.setLine(lineNum);
                entity.setCode(sourceData.getCode());
                entity.setName(sourceData.getName());
                entity.setPhone(sourceData.getPhone());
                entity.setSexStr(Objects.equals(sourceData.getSex(), 1) ? "男" : Objects.equals(sourceData.getSex(), 2) ? "女" : StrUtil.EMPTY);
                return entity;
            }

            @Override
            public List<UserPO> getData(int currentBatch) {
                // 模擬分頁查詢,假設(shè)數(shù)據(jù)庫中有200頁數(shù)據(jù)
                if (currentBatch <= 200) {
                    List<UserPO> userPOList = new ArrayList<>();
                    // 模擬查詢數(shù)據(jù)庫,假設(shè)每次查詢會(huì)查出8888條數(shù)據(jù)
                    for (int i = 0; i < 8888; i++) {
                        userPOList.add(UserPO.builder()
                                .code("USER_" + RandomUtil.randomString("1234567890", 6))
                                .name(RandomUtil.randomString("qwertyuiopasdfghjklzxcvbnm", 10))
                                .phone("138" + RandomUtil.randomString("1234567890", 8))
                                .sex(RandomUtil.randomInt(1, 3))
                                .build());
                    }
                    return userPOList;
                } else {
                    return new ArrayList<>();
                }
            }
        }.easyExcelBatchExport("測試分批次導(dǎo)出", "測試分批次導(dǎo)出", response);
    }

通過分批次寫入Excel的方式,成功導(dǎo)出了170多萬條數(shù)據(jù),相較于不分批次導(dǎo)出,效果顯而易見。而且通過調(diào)用工具類的方式,進(jìn)一步簡化了導(dǎo)出時(shí)代碼的編寫。

到此這篇關(guān)于java使用EasyExcel導(dǎo)出上萬數(shù)據(jù)如何避免OOM的文章就介紹到這了,更多相關(guān)java EasyExcel導(dǎo)出數(shù)據(jù)避免OOM內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java HashMap 的工作原理詳解

    java HashMap 的工作原理詳解

    本文主要介紹java HashMap 的資料,這里整理了相關(guān)資料,并詳細(xì)說明了HashMap的用法,有需要的小伙伴可以參考下
    2016-09-09
  • 詳解Java如何使用注解來配置Spring容器

    詳解Java如何使用注解來配置Spring容器

    這篇文章我們將介紹如何在Java代碼中使用注解來配置Spring容器,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)有一定參考價(jià)值,感興趣的可以了解一下
    2022-06-06
  • java基于servlet實(shí)現(xiàn)文件上傳功能

    java基于servlet實(shí)現(xiàn)文件上傳功能

    這篇文章主要為大家詳細(xì)介紹了java基于servlet實(shí)現(xiàn)文件上傳功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • Java GUI制作簡單的管理系統(tǒng)

    Java GUI制作簡單的管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了Java GUI制作簡單的管理系統(tǒng)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • struts2中實(shí)現(xiàn)多個(gè)文件同時(shí)上傳代碼

    struts2中實(shí)現(xiàn)多個(gè)文件同時(shí)上傳代碼

    struts2中實(shí)現(xiàn)多個(gè)文件同時(shí)上傳代碼,需要的朋友可以參考一下
    2013-04-04
  • Struts2單選按鈕詳解及枚舉類型的轉(zhuǎn)換代碼示例

    Struts2單選按鈕詳解及枚舉類型的轉(zhuǎn)換代碼示例

    這篇文章主要介紹了Struts2單選按鈕詳解及枚舉類型的轉(zhuǎn)換代碼示例,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • 淺談Java非阻塞同步機(jī)制和CAS

    淺談Java非阻塞同步機(jī)制和CAS

    我們知道在java 5之前同步是通過Synchronized關(guān)鍵字來實(shí)現(xiàn)的,在java 5之后,java.util.concurrent包里面添加了很多性能更加強(qiáng)大的同步類。這些強(qiáng)大的類中很多都實(shí)現(xiàn)了非阻塞的同步機(jī)制從而幫助其提升性能。
    2021-06-06
  • Java數(shù)據(jù)結(jié)構(gòu)之查找

    Java數(shù)據(jù)結(jié)構(gòu)之查找

    本文主要介紹了Java數(shù)據(jù)結(jié)構(gòu)中查找的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來看下吧
    2017-03-03
  • 使用JPA主鍵@Id,@IdClass,@Embeddable,@EmbeddedId問題

    使用JPA主鍵@Id,@IdClass,@Embeddable,@EmbeddedId問題

    這篇文章主要介紹了使用JPA主鍵@Id,@IdClass,@Embeddable,@EmbeddedId問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 關(guān)于mybatis callSettersOnNulls 配置解析

    關(guān)于mybatis callSettersOnNulls 配置解析

    這篇文章主要介紹了關(guān)于mybatis callSettersOnNulls 配置,非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2018-06-06

最新評論