You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring+Angular项目中如何通过base64传输大文件并解决堆内存溢出问题

内存溢出问题优化方案

问题根因

现有实现采用全量加载逻辑,先将完整文件读取到内存字节数组中,再一次性编码为全量Base64字符串,大文件场景下字节数组、编码后的Base64字符串两个大对象会直接占满JVM堆内存,触发OOM错误;同时loadAll方法批量加载文件夹内所有文件的全量Base64内容存入集合,进一步放大了内存占用问题。

核心优化方案

1. 流式编码替代全量加载

用分段读取+流式编码逻辑,避免将完整文件和完整Base64字符串一次性加载到内存,Java 8自带的Base64.Encoder提供wrap()方法可直接包装输出流,实现边读取文件边编码输出。
优化后的编码方法示例:

public static String encodeFileToBase64Binary(String fileName) {
    try (InputStream inputStream = new FileInputStream(fileName);
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         OutputStream base64OutputStream = Base64.getEncoder().wrap(outputStream)) {
        // 8k缓冲区,可根据实际场景调整为4k~16k
        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            base64OutputStream.write(buffer, 0, bytesRead);
        }
        base64OutputStream.flush();
        return outputStream.toString(StandardCharsets.US_ASCII);
    } catch (IOException e) {
        logger.debug("errore encodeFileToBase64Binary, ", e);
        throw new EFWMException(e);
    }
}

2. 避免批量返回全量文件内容

原loadAll方法不要返回文件Base64内容,仅返回文件名、文件类型等元数据,前端需要查看/下载具体文件时,再调用单文件接口获取对应Base64内容,避免同时持有多个大文件的Base64字符串。
优化后的loadAll方法示例:

@Override
public List<FileDto> loadAll() {
    List<FileDto> files = new ArrayList<FileDto>();
    File folder = new File("uploads"+ File.separator +"adminfolder");
    File[] listOfFiles = folder.listFiles();
    // 空文件夹判空处理,避免空指针
    if (listOfFiles == null) {
        return files;
    }
    for (File file : listOfFiles) {
        // 跳过子文件夹,只处理文件
        if (file.isFile()) {
            String tipoFile = BufferedImageUtility.getMimeType(file.getAbsolutePath());
            String estensione = "";
            // 不加载Base64内容,仅返回元数据
            files.add(new FileDto("", null, file.getName(), tipoFile, estensione));
        }
    }
    return files;
}

3. 超大文件场景采用流式响应

如果单文件体积超过100M,建议用Spring的StreamingResponseBody实现边编码边响应,不需要在内存中生成完整的Base64字符串即可返回给前端,内存占用仅为缓冲区大小。
单文件流式接口示例:

@GetMapping("/file/{fileName}/base64")
public ResponseEntity<StreamingResponseBody> getFileBase64(@PathVariable String fileName) {
    File file = new File("uploads" + File.separator + "adminfolder" + File.separator + fileName);
    if (!file.exists() || file.isDirectory()) {
        return ResponseEntity.notFound().build();
    }
    String mimeType = BufferedImageUtility.getMimeType(file.getAbsolutePath());
    StreamingResponseBody responseBody = outputStream -> {
        try (InputStream inputStream = new FileInputStream(file);
             OutputStream base64Output = Base64.getEncoder().wrap(outputStream)) {
            byte[] buffer = new byte[8192];
            int read;
            while ((read = inputStream.read(buffer)) != -1) {
                base64Output.write(buffer, 0, read);
            }
        }
    };
    return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(mimeType))
            .body(responseBody);
}

附加优化建议

  • 可适当调大JVM堆内存,启动参数添加-Xms2G -Xmx4G,根据服务器硬件配置调整阈值
  • 若架构允许优先选择二进制流传输文件,相比Base64可减少33%的体积开销,大幅提升传输效率
  • 高频访问的文件可新增本地缓存逻辑,已经生成过的Base64内容不用重复编码,降低CPU和IO开销

内容的提问来源于stack exchange,提问作者Maz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 02:27:03