侧边栏壁纸
博主头像
拾荒的小海螺博主等级

只有想不到的,没有做不到的

  • 累计撰写 140 篇文章
  • 累计创建 15 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

JAVA:如何优化大文件导出的攻略指南

拾荒的小海螺
2024-06-07 / 0 评论 / 0 点赞 / 8 阅读 / 6596 字

1、简述

在开发过程中,不可避免会遇到前端查询或者直接导出大文件,这个时候对于客户而已如何简洁明了的体验尤为重要。所以既需要优化前端操作的用户体验,也需要优化后端的数据处理和传输效率。以下是前端和后端的优化方案:

1717745443545.jpg

2、前端

2.1 异步处理和进度反馈

  • 异步请求:使用异步请求(AJAX 或 Fetch API)启动导出操作,避免阻塞用户界面。
  • 进度反馈:提供导出进度的反馈,例如显示加载动画或进度条,让用户了解导出进度。
// 发起异步请求
async function exportFile() {
    try {
        let response = await fetch('/api/export', { method: 'POST' });
        if (!response.ok) throw new Error('Network response was not ok');

        let result = await response.json();
        // 显示下载链接
        showDownloadLink(result.downloadUrl);
    } catch (error) {
        console.error('Error during file export:', error);
    }
}

2.2 文件分块下载

如果文件非常大,可以考虑分块下载,前端合并。这可以通过分块请求后端来实现,逐步下载文件块并合并:

async function downloadFileInChunks(url, chunkSize = 1024 * 1024) {
    let response = await fetch(url, { method: 'HEAD' });
    let fileSize = parseInt(response.headers.get('Content-Length'));
  
    let start = 0;
    let chunks = [];
    while (start < fileSize) {
        let end = Math.min(start + chunkSize, fileSize);
        let chunk = await fetch(`${url}?range=${start}-${end}`);
        chunks.push(await chunk.blob());
        start = end;
    }

    let fileBlob = new Blob(chunks);
    let downloadUrl = URL.createObjectURL(fileBlob);
    download(downloadUrl);
}

function download(url) {
    let a = document.createElement('a');
    a.href = url;
    a.download = 'exported_file';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
}

2.3 流式下载

利用浏览器的流式下载功能,边下载边保存,减少内存占用。

async function streamDownload(url) {
    const response = await fetch(url);
    const reader = response.body.getReader();
    const stream = new ReadableStream({
        start(controller) {
            function push() {
                reader.read().then(({ done, value }) => {
                    if (done) {
                        controller.close();
                        return;
                    }
                    controller.enqueue(value);
                    push();
                });
            }
            push();
        }
    });

    const blob = await new Response(stream).blob();
    const downloadUrl = URL.createObjectURL(blob);
    download(downloadUrl);
}

3、后端

3.1 分批处理

对于非常大的数据集,避免一次性加载全部数据到内存,可以采用分批处理方式,逐步读取和处理数据。

public class DataService {
    public List<Data> getDataBatch(int offset, int batchSize) {
        // 从数据库或其他数据源获取一批数据
        // 假设这里返回一个包含批量数据的列表
        return dataRepository.findDataBatch(offset, batchSize);
    }
}

3.2 使用 SXSSFWorkbook

Apache POI 提供了 SXSSFWorkbook 用于处理大文件,它基于流式 API,在内存中只保留有限数量的行。

try (SXSSFWorkbook workbook = new SXSSFWorkbook()) {
    Sheet sheet = workbook.createSheet("Data");

    // 逐行写入数据,SXSSFWorkbook自动处理内存中的行数
    for (int i = 0; i < totalRows; i++) {
        Row row = sheet.createRow(i);
        // 创建单元格并填充数据
    }

    workbook.write(outputStream);
}

3.3 流式传输

使用 HTTP 的流式传输,将数据逐行写入响应,避免后端内存占用过高。

@PostMapping("/stream-export-excel")
public void streamExportExcel(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setHeader("Content-Disposition", "attachment; filename=largefile.xlsx");

    try (ServletOutputStream outputStream = response.getOutputStream();
         SXSSFWorkbook workbook = new SXSSFWorkbook()) {
        Sheet sheet = workbook.createSheet("Data");

        int batchSize = 1000;
        int rowCount = 0;
        List<Data> dataBatch;
        do {
            dataBatch = dataService.getDataBatch(rowCount, batchSize);
            for (Data data : dataBatch) {
                Row row = sheet.createRow(rowCount++);
                createCells(row, data);
            }
        } while (dataBatch.size() == batchSize);

        workbook.write(outputStream);
    }
}

private void createCells(Row row, Data data) {
    Cell cell0 = row.createCell(0);
    cell0.setCellValue(data.getField0());
    // ... 创建其他单元格
}

3.4 文件缓存

对于需要重复下载的大文件,可以预先生成文件并缓存到磁盘或云存储,每次导出时直接返回文件链接。

@PostMapping("/export")
public ResponseEntity<String> exportFile() {
    String filePath = fileService.generateAndCacheFile();
    String fileUrl = fileService.getDownloadUrl(filePath);
    return ResponseEntity.ok(fileUrl);
}

// FileService.java
public String generateAndCacheFile() {
    String filePath = "/path/to/cache/largefile.xlsx";
    try (SXSSFWorkbook workbook = new SXSSFWorkbook()) {
        Sheet sheet = workbook.createSheet("Data");
        // 填充数据
        try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
            workbook.write(fileOut);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return filePath;
}

public String getDownloadUrl(String filePath) {
    return "https://your-domain.com/download?file=" + filePath;
}

4、总结

通过前端的异步处理、进度反馈、分块下载和流式下载,可以提升用户体验和下载效率。后端则通过分批处理、流式传输、SXSSFWorkbook、EasyExcel和文件缓存,减少内存压力,提高导出性能。通过这两方面的优化,可以有效提升导出大文件的整体性能和用户体验。

0

评论区