EasyExcel 4.X 讀寫數據

文章目錄

    • EasyExcel與SpringBoot集成
    • 讀數據
      • 讀取數據的流程
      • 定義實體類
      • 簡單讀取
        • 自定義監聽器
      • 讀取指定sheet和所有sheet
      • 多行頭讀取
      • 數據格式轉換
        • 列表數據
        • 實體類
        • 自定義轉換器
        • 自定義監聽器
        • 數據讀取
    • 寫數據
      • 簡單數據寫出
        • 存儲到磁盤
        • 返回前端下載
      • 寫出指定列寬,和數值精度丟失問題
        • 設置列寬
        • 精度丟失問題
        • 同一sheet寫多次
        • 寫出多個sheet
        • 自定義樣式
      • 合并單元格
        • 通過注解方式合并
        • 自定義合并
    • 百萬級別數據讀寫
      • 多線程讀取
        • 多線程讀取監聽器
        • 線程任務
      • 多線程寫出
        • 造數據
        • 實現分析

EasyExcel是Ailibaba團隊提供的一個基于Java的、快速、簡潔、解決大文件內存溢出的Excel處理工具。
在不用考慮性能、內存等因素下,快速完成Excel的讀、寫等功能。

官方地址:https://easyexcel.opensource.alibaba.com/

EasyExcel與SpringBoot集成

創建SpringBoot項目后引入EasyExcel依賴即可,其他依賴可按須引入。思考:這玩意為什么不是一個stater呢?

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

讀數據

讀取數據就是將excel中的數據讀到程序中,可以進行邏輯處理之后將數據根據業務需求存儲到數據庫、寫到另一個文件中或者其他的處理均可。數據讀取依據監聽器實現,可通過匿名內部類的形式,也可以新建類創建監聽器。問:兩者的最佳應用場景是什么?

讀取數據的流程

  • 需要有一個excel,判斷excel中的數據格式和excel的sheet頁
  • 創建對應的實體類,對應類型的字段,存儲讀取的數據
  • 選用不同的讀取方式即可

定義實體類

@Data
public class StockEntity {// 數據庫Idprivate Long id;// 商品序號@ExcelProperty(value = "序號")private Integer productNo;// 商品編碼@ExcelProperty(value = "商品編碼")private String productCode;// 商品類型@ExcelProperty(value = "類型")private String productType;// 商品品牌@ExcelProperty(value = "品牌")private String productBrand;// 實物庫存@ExcelProperty(value = "實物庫存")private String productStock;// 成本 浮點類型@ExcelProperty(value = "成本")private BigDecimal costPrice;}

簡單讀取

public void readSimpleExcel(MultipartFile file) {try {InputStream inputStream = file.getInputStream();EasyExcel.read(inputStream, StockEntity.class,new PageReadListener<StockEntity>(dataList -> {for (StockEntity stockEntity : dataList) {log.info("讀取到一條數據:{}", stockEntity);}})).sheet().doRead();} catch (IOException e) {throw new RuntimeException(e);}
}
自定義監聽器

新建類實現ReadListener接口,泛型指定要將數據存到到哪個實體中

package com.stt.listener.read;import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.stt.entity.StockEntity;
import lombok.extern.slf4j.Slf4j;/*** @author Jshuai* @description* @date 2024-10-29 22:14* 自定義簡單數據讀取的監聽器*/
@Slf4j
public class SimpleReadListener implements ReadListener<StockEntity> {/*** 每讀取一條數據,就會調用invoke方法* @param stockEntity* @param analysisContext*/@Overridepublic void invoke(StockEntity stockEntity, AnalysisContext analysisContext) {log.info("讀取到一條數據:{}", stockEntity);}/*** 讀取完成之后,會調用doAfterAllAnalysed方法,做一些數據清理的操作* @param analysisContext*/@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {log.info("讀取完成!");}
}

讀取指定sheet和所有sheet

public void readMultiSheetExcel(MultipartFile file) {// 讀取所有的// EasyExcel.read(file.getInputStream(), StockEntity.class,new SimpleReadListener()).doReadAll();// 讀取指定的sheet頁try (ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build()) {// 這里為了簡單 所以注冊了 同樣的head 和Listener 自己使用功能必須不同的ListenerReadSheet readSheet1 =EasyExcel.readSheet(0).head(StockEntity.class).registerReadListener(new SimpleReadListener()).build();ReadSheet readSheet2 =EasyExcel.readSheet(1).head(StockEntity.class).registerReadListener(new SimpleReadListener()).build();// 這里注意 一定要把sheet1 sheet2 一起傳進去,不然有個問題就是03版的excel 會讀取多次,浪費性能excelReader.read(readSheet1, readSheet2);} catch (IOException e) {throw new RuntimeException(e);}
}

多行頭讀取

public void readMultiHeadExcel(MultipartFile file) {try {EasyExcel.read(file.getInputStream(), StockEntity.class, new SimpleReadListener()).sheet(0)// 這里可以設置1,因為頭就是一行。如果多行頭,可以設置其他值。不傳入也可以,因為默認會根據DemoData 來解析,他沒有指定頭,也就是默認1行.headRowNumber(2).doRead();} catch (IOException e) {throw new RuntimeException(e);}
}

數據格式轉換

部分數據讀寫場景涉及到數據格式問題,需要轉換,比如性別,數據庫中存儲的一般是數字類型【0表示男,1表示女】,時間在不同系統中也有固定的格式,EasyExcel實現類型轉換有兩種方式:

  • 通過@DateTimeFormat和@NumberFormat對日期時間和數字格式轉換
  • 通過實現Converter接口實現讀寫數據的格式轉換

以員工數據為例:

列表數據
姓名性別出生日期
加油鴨2024/10/10
醬香鴨2024/10/10
果木鴨保密2024/10/10
實體類

其中birthday通過easyexcel提供的注解設置格式,性別通過自定義的GenderConverter實現

public class MyUser {@ExcelProperty(value = "姓名",index = 0)private String name;@ExcelProperty(value = "性別",index = 1,converter = GenderConverter.class)private Integer gender;@ExcelProperty(value = "出生日期",index = 2)@DateTimeFormat("yyyy-MM-dd")private LocalDate birthday;
}
自定義轉換器
@Slf4j
public class GenderConverter implements Converter<Integer> {/*** 這里讀的時候會調用*/@Overridepublic Integer convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {// 獲取讀取的字符串String gender = cellData.getStringValue();log.info("gender===》{}",gender);// 轉換成對應的數字return GenderEnum.getCodeByLabel(gender);}
}
自定義監聽器

不同的數據實體需要不同的監聽器,此處定義一個讀取員工數據的監聽器,泛型修改,并且提供list來存儲讀取到的數據,外部可以通過get方法獲取集合數據

@Slf4j
public class UserReadListener implements ReadListener<MyUser> {@Getterprivate List<MyUser> data;public UserReadListener() {this.data = new ArrayList<>();}@Overridepublic void invoke(MyUser myUser, AnalysisContext analysisContext) {data.add(myUser);}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {log.info("所有數據解析完成!");}}
數據讀取
public void readByConverterData(MultipartFile file) {try {// 創建監聽器UserReadListener readListener = new UserReadListener();EasyExcel.read(file.getInputStream(), MyUser.class, readListener).sheet(2).doRead();// 獲取讀取到的數據List<MyUser> data = readListener.getData();data.forEach(System.out::println);} catch (IOException e) {throw new RuntimeException(e);}
}

寫數據

寫數據就是將系統內的數據寫到excel中,可以將文件寫到磁盤或者提供給前端下載,本案例基于MySQL +Mybatis實現從數據庫中讀取數據寫出到excel中

簡單數據寫出

存儲到磁盤
public void demo1(HttpServletResponse response) {List<TbExcel> tbExcels = excelMapper.selectList(null);List<ExcelVO> excelVOS = BeanUtil.copyToList(tbExcels, ExcelVO.class);EasyExcel.write("C:\\demo1.xlsx", ExcelVO.class).sheet("sheet1").doWrite(excelVOS);
}
返回前端下載
public void demo1(HttpServletResponse response) {// 查詢數據庫數據,查詢所有的List<TbExcel> tbExcels = excelMapper.selectList(null);// 將TbExcel轉換為ExcelVOList<ExcelVO> excelVOS = BeanUtil.copyToList(tbExcels, ExcelVO.class);// 提供給前端下載,需要使用到HttpServletResponse對象// 這里注意 有同學反應使用swagger 會導致各種問題,請直接用瀏覽器或者用postmanresponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");// 強制調起下載
//        response.setContentType("application/octet-stream;charset=UTF-8");response.setCharacterEncoding("utf-8");try {String fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), ExcelVO.class).sheet("模板").doWrite(excelVOS);} catch (IOException e) {throw new RuntimeException(e);}
}

寫出指定列寬,和數值精度丟失問題

設置列寬

通過@ColumnWidth注解設置

public class ExcelVO {@ExcelProperty(value = "ID", index = 0)private Long id;@ExcelProperty(value = "字符串", index = 1)private String strCol;@ExcelProperty(value = "浮點數字", index = 2)@ColumnWidth(12)private BigDecimal decCol;@ExcelProperty(value = "日期時間", index = 3)@ColumnWidth(18)private LocalDateTime datetimeCol;
}
精度丟失問題

通過設置轉換器實現,將Long轉為String導出

EasyExcel.write(response.getOutputStream(), ExcelVO.class).registerConverter(new LongStringConverter()).sheet("模板").doWrite(excelVOS);
同一sheet寫多次
public void demo2(HttpServletResponse response) {List<TbExcel> tbExcels = excelMapper.selectList(null);List<ExcelVO> excelVOS = BeanUtil.copyToList(tbExcels, ExcelVO.class);try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExcelVO.class).build()) {// 這里注意 有伙伴反應使用swagger 會導致各種問題,請直接用瀏覽器或者用postmanresponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");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");// 這里注意 如果同一個sheet只要創建一次WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();// 去調用寫入,這里我調用了五次,實際使用時根據數據庫分頁的總的頁數來for (int i = 0; i < 5; i++) {excelWriter.write(excelVOS, writeSheet);}} catch (IOException e) {throw new RuntimeException(e);}
}
寫出多個sheet

根據不同的維度將數據分別存到不同的sheet表中,比如根據日期分類

public void demo3(HttpServletResponse response) {List<TbExcel> tbExcels = excelMapper.selectList(null);List<ExcelVO> excelVOS = BeanUtil.copyToList(tbExcels, ExcelVO.class);try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExcelVO.class).build()) {response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");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");// 去調用寫入,這里我調用了五次,實際使用時根據數據庫分頁的總的頁數來。這里最終會寫到5個sheet里面Map<LocalDateTime, List<ExcelVO>> collect = excelVOS.stream().collect(Collectors.groupingBy(ExcelVO::getDatetimeCol));int index = 0;for (LocalDateTime localDateTime : collect.keySet()) {WriteSheet writeSheet = EasyExcel.writerSheet(index, localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))).build();// 分頁去數據庫查詢數據 這里可以去數據庫查詢每一頁的數據excelWriter.write(collect.get(localDateTime), writeSheet);index++;}} catch (IOException e) {throw new RuntimeException(e);}
}
自定義樣式

easyexcel導出的表格有默認樣式,我們還可以自定義頭,表格內容和字體樣式

public void demo4(HttpServletResponse response) {// 頭的策略WriteCellStyle headWriteCellStyle = new WriteCellStyle();// 背景設置為紅色headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());WriteFont headWriteFont = new WriteFont();headWriteFont.setFontHeightInPoints((short)20);headWriteCellStyle.setWriteFont(headWriteFont);// 內容的策略WriteCellStyle contentWriteCellStyle = new WriteCellStyle();// 這里需要指定 FillPatternType 為FillPatternType.SOLID_FOREGROUND 不然無法顯示背景顏色.頭默認了 FillPatternType所以可以不指定contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 背景綠色contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());WriteFont contentWriteFont = new WriteFont();// 字體大小contentWriteFont.setFontHeightInPoints((short)20);contentWriteCellStyle.setWriteFont(contentWriteFont);// 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實現HorizontalCellStyleStrategy horizontalCellStyleStrategy =new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);List<TbExcel> tbExcels = excelMapper.selectList(null);List<ExcelVO> excelVOS = BeanUtil.copyToList(tbExcels, ExcelVO.class);response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String fileName = null;try {fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), ExcelVO.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板").doWrite(excelVOS);} catch (IOException e) {throw new RuntimeException(e);}
}

合并單元格

通過注解方式合并
@Data
@AllArgsConstructor
@NoArgsConstructor
// 將第6-7行的2-3列合并成一個單元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class ExcelVO {// 每兩行合并@ContentLoopMerge(eachRow = 2)@ExcelProperty(value = "ID", index = 0)private Long id;@ExcelProperty(value = "字符串", index = 1)private String strCol;@ExcelProperty(value = "浮點數字", index = 2)private BigDecimal decCol;@ExcelProperty(value = "日期時間", index = 3)private LocalDateTime datetimeCol;
}
自定義合并
public void demo5(HttpServletResponse response) {List<ExcelVO> excelVOS = new ArrayList<>();excelVOS.add(new ExcelVO(1L,"字符串1",BigDecimal.ONE, LocalDateTime.now()));excelVOS.add(new ExcelVO(1L,"字符串2",BigDecimal.ONE, LocalDateTime.now()));excelVOS.add(new ExcelVO(2L,"字符串3",BigDecimal.ONE, LocalDateTime.now()));excelVOS.add(new ExcelVO(2L,"字符串4",BigDecimal.ONE, LocalDateTime.now()));// 相加BigDecimal sum = excelVOS.stream().map(ExcelVO::getDecCol).reduce(BigDecimal.ZERO, BigDecimal::add);// 將合計追加到集合中excelVOS.add(new ExcelVO(null,"合計", sum , null));response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String fileName = null;try {fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");// 設置合并規則LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);EasyExcel.write(response.getOutputStream(), ExcelVO.class).sheet("模板").registerWriteHandler(loopMergeStrategy).doWrite(excelVOS);} catch (IOException e) {throw new RuntimeException(e);}}

百萬級別數據讀寫

多線程讀取

  • 思路

    • 分析瓶頸在哪里?
    • 數據讀取的性能:讀取可以使用多線程【重點】
    • 數據庫中插入數據,這個性能是比較不錯的,可以使用批量插入【使用數據庫連接池】
  • 多線程讀取數據的問題

    • 需要避免重復讀取,每個線程需要設置讀取哪個區間的數據
public void importExcel(MultipartFile file) {try {// 多線程讀取EasyExcel.read(file.getInputStream(),ExcelVO.class,new MutliReadListener(excelMapper)).sheet().doRead();} catch (IOException e) {throw new RuntimeException(e);}}
多線程讀取監聽器
@Service
@Slf4j
@NoArgsConstructor
public class MutliReadListener extends AnalysisEventListener<ExcelVO> {private ExcelMapper excelMapper;public MutliReadListener(ExcelMapper excelMapper) {this.excelMapper = excelMapper;}/*** 使用線程安全集合*/private List<ExcelVO> dataList = Collections.synchronizedList(new ArrayList<>());/*** 創建線程池必要參數*/private static final int CORE_POOL_SIZE = 5;//核心線程數private static final int MAX_POOL_SIZE = 10;//最大線程數private static final int QUEUE_CAPACITY = 100;//隊列大小private static final Long KEEP_ALIVE_TIME = 1L;//存活時間@Overridepublic void invoke(ExcelVO data, AnalysisContext context) {if (dataList != null) {dataList.add(data);}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {//創建線程池ExecutorService executor = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.CallerRunsPolicy());//指定每個線程需要處理的導入數量,假設每個線程處理1000條int singleThreadDealCount = 100000;//根據假設每個線程需要處理的數量以及總數,計算需要提交到線程池的線程數量int threadsize = (dataList.size() / singleThreadDealCount) + 1;log.info("threadsize===>{}",threadsize);//計算需要導入的數據總數,用于拆分時線程需要處理數據時使用int rowsize = dataList.size() + 1;//測試開始時間long startTime = System.currentTimeMillis();//申明該線程需要處理數據的開始位置int startPosition = 0;//申明該線程需要處理數據的結束位置int endPosition = 0;//為了讓每個線程執行完后回到當前線程,使用CountDownLatch,值為線程數,每次線程執行完就會執行countDown方法減1,為0后回到主CountDownLatch count = new CountDownLatch(threadsize);//計算每個線程要處理的數據for (int i = 0; i < threadsize; i++) {//如果是最后一個線程,為保證程序不發生空指針異常,特殊判斷結束位置if ((i + 1) == threadsize) {//計算開始位置startPosition = (i * singleThreadDealCount);//當前線程為劃分的最后一個線程,則取總數據的最后為此線程的結束位置endPosition = rowsize - 1;} else {//計算開始位置startPosition = (i * singleThreadDealCount);//計算結束位置endPosition = (i + 1) * singleThreadDealCount;}log.info("線程開啟====》count:{},startPosition:{},endPosition:{}",count,startPosition,endPosition);DeadMainThread deadMainThread = new DeadMainThread(count, excelMapper, dataList, startPosition, endPosition);executor.execute(deadMainThread);}try {count.await();} catch (InterruptedException e) {e.printStackTrace();}//邏輯處理完,關閉線程池executor.shutdown();long endTime = System.currentTimeMillis();System.out.println("總耗時:" + (endTime - startTime));}
}
線程任務
@Component
@Slf4j
public class DeadMainThread implements Runnable {/*** 當前線程需要處理的總數據中的開始位置*/private int startPosition;/*** 當前線程需要處理的,總數據中的結束位置*/private int endPosition;/*** 需要處理的拆分之前的全部數據*/private List<ExcelVO> list = Collections.synchronizedList(new ArrayList<>());private CountDownLatch count;private ExcelMapper excelMapper;public DeadMainThread() {}public DeadMainThread(CountDownLatch count, ExcelMapper excelMapper, List<ExcelVO> list, int startPosition, int endPosition) {this.startPosition = startPosition;this.endPosition = endPosition;this.excelMapper = excelMapper;this.list = list;this.count = count;}@Overridepublic void run() {try {List<ExcelVO> newList = list.subList(startPosition, endPosition);//批量新增excelMapper.insertBatch(BeanUtil.copyToList(newList, TbExcel.class));} catch (Exception e) {e.printStackTrace();} finally {//當一個線程執行完了計數要減一不然這個線程會被一直掛起count.countDown();log.info("減一===》{}",count.getCount());}}
}

多線程寫出

造數據
public void init() {LocalDateTime now = LocalDateTime.now();// 通過計數器,信號量CountDownLatch countDownLatch = new CountDownLatch(100);for (int i = 0; i < 100; i++) {// 開啟虛擬線程Thread.ofVirtual().start(() -> {List<TbExcel> list = new ArrayList<>();for (int j = 0; j < 10000; j++) {list.add(new TbExcel("字符串" + j, new BigDecimal(j), now));}excelMapper.insertBatch(list);// 計數器減一countDownLatch.countDown();log.info("線程{}執行完畢", Thread.currentThread().getName());});}// 是否結束try {countDownLatch.await();log.info("初始化完成");} catch (InterruptedException e) {throw new RuntimeException(e);}
}
實現分析
  • 百萬級別的數據考慮兩個核心問題

    • 內存不能爆掉,不能產生OOM
    • 速度,性能
  • 實現邏輯就是多線程,導出數據瓶頸在

    • 數據查詢,一次性查詢多少數據
    • 內存不能溢出

至于數據寫到excel中,這個就是easyexcel的表現了。

public void exportData(HttpServletResponse response) {long start1 = System.currentTimeMillis();// 每次查詢10萬條,考慮性能,內存Integer pageSize = 100000;// 線程池大小跟cpu有關,一般是cpu數量 * 2 + 1Integer poolSize = 10;// 隨機文件名String fileName = String.valueOf(UUID.randomUUID());// 查詢數據總數Long totalCount = excelMapper.selectCount(null);if (totalCount == 0) {log.info("沒有數據需要導出");return; // 如果沒有數據,直接返回}int loopCount = (int) Math.ceil((double) totalCount / pageSize);  // 使用 Math.ceil 計算循環次數// 設置final CountDownLatch latch = new CountDownLatch(loopCount);log.info("要查詢的次數===>{}", loopCount);ExecutorService executorService = Executors.newFixedThreadPool(poolSize);OutputStream outputStream = null;try {outputStream = response.getOutputStream();// 創建寫對象ExcelWriter excelWriter = EasyExcel.write(outputStream).build();for (int i = 0; i < loopCount; i++) {final int pageNum = i + 1; // 改為從1開始,直接使用 i + 1 作為頁碼executorService.execute(() -> {long start = System.currentTimeMillis();// 查詢數據IPage<TbExcel> data = excelMapper.selectPage(new Page<>(pageNum, pageSize), null);List<TbExcel> records = data.getRecords();log.info("第{}頁,查詢耗時===>{}", pageNum,System.currentTimeMillis() - start);WriteSheet writeSheet = EasyExcel.writerSheet(pageNum ,"第" + pageNum + "頁").head(ExcelVO.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();synchronized (excelWriter) {long start2 = System.currentTimeMillis();excelWriter.write(BeanUtil.copyToList(records, ExcelVO.class), writeSheet);log.info("數據寫出耗時===》{}",System.currentTimeMillis() - start2);}latch.countDown();});}latch.await();response.setContentType("application/octet-stream");response.setCharacterEncoding("utf-8");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");excelWriter.finish();outputStream.flush();executorService.shutdown(); // 關閉線程池outputStream.close();log.info("總耗時====》{}",System.currentTimeMillis() - start1);} catch (IOException | InterruptedException e) {throw new RuntimeException(e);}}/*** 虛擬線程* @param response*/
@Override
public void exportData2(HttpServletResponse response) {long start1 = System.currentTimeMillis();Integer pageSize = 100000;Integer poolSize = 10;String fileName = String.valueOf(UUID.randomUUID());// 查詢數據總數Long totalCount = excelMapper.selectCount(null);if (totalCount == 0) {log.info("沒有數據需要導出");return; // 如果沒有數據,直接返回}int loopCount = (int) Math.ceil((double) totalCount / pageSize);  // 使用 Math.ceil 計算循環次數// 設置final CountDownLatch latch = new CountDownLatch(loopCount);log.info("要查詢的次數===>{}", loopCount);ExecutorService executorService = Executors.newFixedThreadPool(poolSize);OutputStream outputStream = null;try {outputStream = response.getOutputStream();// 創建寫對象ExcelWriter excelWriter = EasyExcel.write(outputStream).build();for (int i = 0; i < loopCount; i++) {final int pageNum = i + 1; // 改為從1開始,直接使用 i + 1 作為頁碼Thread.ofVirtual().start(() -> {long start = System.currentTimeMillis();// 查詢數據IPage<TbExcel> data = excelMapper.selectPage(new Page<>(pageNum, pageSize), null);List<TbExcel> records = data.getRecords();log.info("第{}頁,查詢耗時===>{}", pageNum,System.currentTimeMillis() - start);WriteSheet writeSheet = EasyExcel.writerSheet(pageNum ,"第" + pageNum + "頁").head(ExcelVO.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();synchronized (excelWriter) {long start2 = System.currentTimeMillis();excelWriter.write(BeanUtil.copyToList(records, ExcelVO.class), writeSheet);log.info("數據寫出耗時===》{}",System.currentTimeMillis() - start2);}latch.countDown();});}latch.await();response.setContentType("application/octet-stream");response.setCharacterEncoding("utf-8");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");excelWriter.finish();outputStream.flush();executorService.shutdown(); // 關閉線程池outputStream.close();log.info("總耗時====》{}",System.currentTimeMillis() - start1);} catch (IOException | InterruptedException e) {throw new RuntimeException(e);}}/*** 單sheet頁超過100萬條數據就寫不進去了* @param response*/
@Override
public void exportData3(HttpServletResponse response) {long start1 = System.currentTimeMillis();Integer pageSize = 100000;Integer poolSize = 10;String fileName = String.valueOf(UUID.randomUUID());// 查詢數據總數Long totalCount = excelMapper.selectCount(null);if (totalCount == 0) {log.info("沒有數據需要導出");return; // 如果沒有數據,直接返回}int loopCount = (int) Math.ceil((double) totalCount / pageSize);  // 使用 Math.ceil 計算循環次數// 設置final CountDownLatch latch = new CountDownLatch(loopCount);log.info("要查詢的次數===>{}", loopCount);ExecutorService executorService = Executors.newFixedThreadPool(poolSize);OutputStream outputStream = null;try {outputStream = response.getOutputStream();// 創建寫對象ExcelWriter excelWriter = EasyExcel.write(outputStream).build();WriteSheet writeSheet = EasyExcel.writerSheet(1 ,"第" + 1 + "頁").head(ExcelVO.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerConverter(new LongStringConverter()).build();for (int i = 0; i < loopCount; i++) {final int pageNum = i + 1; // 改為從1開始,直接使用 i + 1 作為頁碼Thread.ofVirtual().start(() -> {long start = System.currentTimeMillis();// 查詢數據IPage<TbExcel> data = excelMapper.selectPage(new Page<>(pageNum, pageSize), null);List<TbExcel> records = data.getRecords();log.info("第{}頁,查詢耗時===>{}", pageNum,System.currentTimeMillis() - start);synchronized (excelWriter) {long start2 = System.currentTimeMillis();excelWriter.write(BeanUtil.copyToList(records, ExcelVO.class), writeSheet);log.info("數據寫出耗時===》{}",System.currentTimeMillis() - start2);}latch.countDown();});}latch.await();response.setContentType("application/octet-stream");response.setCharacterEncoding("utf-8");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");excelWriter.finish();outputStream.flush();executorService.shutdown(); // 關閉線程池outputStream.close();log.info("總耗時====》{}",System.currentTimeMillis() - start1);} catch (IOException | InterruptedException e) {throw new RuntimeException(e);}}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/88083.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/88083.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/88083.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JVM內存管理<一>:Java內存異常問題排查

一、 內存溢出問題的排查 1. 使用工具 - jdk自帶 jmapvisualvm 2. 流程 堆轉儲&#xff1a; (1) 方法一&#xff1a;程序運行時&#xff0c;采用&#xff1a;jmap -dump:formatb,filed:\\data\\xxlJob.hprof 23300 進行堆文件的轉儲 (2) 方法二&#xff1a;在內存溢出的時候…

Android中Glide.with().load().into() 應付面試源碼解析

1. with(this)&#xff1a;生命周期綁定 Glide.with(Activity/Fragment/Context) 核心機制&#xff1a;創建與 UI 生命周期綁定的 RequestManager 底層實現&#xff1a; 通過 RequestManagerRetriever 獲取單例 非 Application 上下文&#xff1a; 向 Activity/Fragment 添加…

#### es相關內容的索引 ####

倒排索引 結構 #### es倒排索引的結構 ####-CSDN博客 向量索引 結構應用 #### es向量檢索 的 結構及應用_es 向量 文本檢索-CSDN博客 ann算法 ann算法的種類有哪些&#xff0c;之間的區別&#xff0c;各自的適用場景-CSDN博客 地理信息索引 es地理信息索引的類型以及geo_po…

小飛電視:智能電視與移動設備的娛樂新選擇

在數字娛樂時代&#xff0c;人們對于影視內容的需求日益增長&#xff0c;不僅追求豐富多樣的節目選擇&#xff0c;還希望獲得便捷、個性化的觀看體驗。小飛電視正是這樣一款專為智能電視和移動設備設計的視頻娛樂應用&#xff0c;它憑借海量的影視資源、高清流暢的播放效果以及…

刪除node并且重裝然后重裝vue

參考第一篇文章 node.js卸載與安裝超詳細教程_node卸載重裝-CSDN博客 第二篇文章安裝vue Vue安裝與配置教程&#xff08;非常詳細&#xff09;_安裝vue-CSDN博客

基于YOLOv10算法的交通信號燈檢測與識別

目錄 一.&#x1f981; 寫在前面1.1 實現模塊劃分1.2 優化與實時性支持 二.&#x1f981; 相關技術與理論基礎2.1 各版本yolo對比2.2 YOLOv10網絡結構 三.&#x1f981; 結果分析3.1 訓練損失與驗證損失分析3.2 精確率&#xff08;Precision&#xff09;、召回率&#xff08;Re…

洪水風險圖制作全流程:HEC-RAS 與 ArcGIS 的耦合應用

技術點目錄 一、HER-RAS理論二、一維數學模型基本地形導入三、恒定流、非恒定流一維數學模型水流計算四、一維數學模型計算結果分析五、一維數學模型增設構筑物六、二維河道水動力模擬七、HEC-RAS在潰壩模型中的應用八、HEC-RAS在洪水風險圖中的應用了解更多 —————————…

視覺大語言模型未能充分利用視覺表征

視覺大語言模型未能充分利用視覺表征 FesianXu 20250612 at Wechat Search Team 前言 這兩天看到一篇新掛在arxiv上的文章 [1]&#xff0c;討論了下視覺大語言模型的視覺表征退化問題。先前的研究將VLM缺陷歸咎于視覺編碼器薄弱&#xff0c;并提出集成編碼器方案以彌補不足&am…

SSRF3 任意文件讀取

一.任意文件讀取 http://192.168.112.12/pikachu-master/vul/ssrf/ssrf_curl.php?urlfile:///etc/passwd 讀取文件使用 file://文件路徑即可&#xff0c;這里我們換協議為file&#xff0c;然后從根目錄開始讀取。 /etc/passwd 我們這樣修改完url路徑后查看結果可以看到文件內…

洛谷P3953 [NOIP 2017 提高組] 逛公園

洛谷P3953 [NOIP 2017 提高組] 逛公園 洛谷題目傳送門 題目背景 NOIP2017 D1T3 題目描述 策策同學特別喜歡逛公園。公園可以看成一張 N N N 個點 M M M 條邊構成的有向圖&#xff0c;且沒有 自環和重邊。其中 1 1 1 號點是公園的入口&#xff0c; N N N 號點是公園的出…

Vue3+TypeScript+Element Plus 表格展開行優化方案

在 Vue3 TypeScript Element Plus 項目中優化表格展開行的內存使用&#xff0c;主要從 渲染優化、數據管理 和 內存回收 三方面入手。以下是最佳實踐和完整解決方案&#xff1a; 1. 懶加載展開內容&#xff08;核心優化&#xff09; 只當行展開時才渲染內容&#xff0c;避免…

OpenCV——直方圖與匹配

直方圖與匹配 一、直方圖簡介二、直方圖統計三、直方圖比較四、直方圖均衡化五、自適應的直方圖均衡化六、直方圖反向投影七、模板匹配 一、直方圖簡介 圖像直方圖&#xff08;Histogram&#xff09;是一種頻率分布圖&#xff0c;它描述了不同強度值在圖像中出現的頻率。圖像直…

通義大模型在文檔自動化處理中的高效部署指南(OCR集成與批量處理優化)

1. 傳統OCR解決方案常面臨識別精度低、版面分析能力弱、處理效率瓶頸等問題。通義大模型憑借其多模態理解和生成能力&#xff0c;為文檔處理領域帶來革命性突破。本文將深入探討如何高效部署通義大模型實現端到端的文檔自動化處理&#xff0c;特別聚焦OCR集成與批量處理優化兩…

Ubuntu20.04通過ssh協議配置遠程終端

一、在目標計算機&#xff08;即被連接的計算機&#xff09;上操作&#xff1a; 1、安裝 OpenSSH 服務器&#xff1a; sudo apt update sudo apt install openssh-server3、啟動并設置 SSH 服務開機自啟&#xff1a; sudo systemctl enable --now ssh二、在源計算機&#xf…

《HTTP權威指南》 第7章 緩存

帶著問題學習&#xff1a; 緩存如何提高性能如何衡量緩存的有效性緩存置于何處作用最大HTTP如何保持緩存副本的新鮮度緩存如何與其他緩存及服務器通信 web緩存是可以自動保存常見文檔副本的HTTP設備。 緩存優點 減少冗余的數據傳輸&#xff0c;節省網絡費用緩解網絡瓶頸問題&…

第十三章 模板

函數模板 函數模板使用 函數模板注意事項 自動類型推導&#xff0c;必須推導出一致的數據類型T,才可以使用 模板必須要確定出T的數據類型&#xff0c;才可以使用 普通函數和函數模板的類型轉化 普通函數隱式類型轉化&#xff08;char轉int&#xff09; 函數模板正常使用不會發生…

云計算-專有網絡VPC

&#x1f310; 什么是 VPC&#xff1f;&#xff08;Virtual Private Cloud&#xff09; VPC&#xff08;Virtual Private Cloud&#xff0c;虛擬私有云&#xff09; 是公有云服務商提供的一種網絡隔離服務&#xff0c;允許用戶在云中創建一個邏輯隔離的私有網絡環境。你可以在這…

關于*gin.Context的理解

關于*gin.Context的理解 作為初學者&#xff0c;在學習go語言用gin開發web時&#xff0c;我對*gin.Context感到困惑。本文章以自我總結為主&#xff0c;大部分為來自詢問ai后的總結&#xff0c;如有問題歡迎指出。 *gin.Context可以理解為一個gin框架的上下文對象指針&#x…

Qt中的OpenGL (6)[坐標系統]

文章目錄 文章說明學習目標目錄結構坐標系統局部空間世界空間觀察空間裁剪空間正射投影矩陣透視投影矩陣組合進入3D世界頂點數據著色器設置數據矩陣設置文章說明 本文是學習OpenGL的筆記,主要參考大神JoeyDeVries的LearnOpenGL第八課《坐標系統》,并將教程中的代碼基于Qt進行…

Spring Aop @After (后置通知)的使用場景?

核心定義 After 是 Spring AOP 中的另一種通知&#xff08;Advice&#xff09;類型&#xff0c;通常被稱為“后置通知”或“最終通知”。 它的核心作用是&#xff1a; 無論目標方法是正常執行完成&#xff0c;還是在執行過程中拋出了異常&#xff0c;After 通知中的代碼 總是…