
本文介绍如何将 spring 的 `multipartfile` 流式压缩为 gzip 格式,并**可靠获取压缩后的精确字节数**,避免依赖未明确文档化的 api 行为(如 `ioutils.copylarge` 返回值语义模糊),推荐使用临时文件或内存缓冲的健壮方案。
在实际开发中(例如微服务间传输大文件、日志归档、API 响应压缩等场景),我们常需对 MultipartFile 进行 GZIP 压缩后再发送,同时必须提前知道压缩后的数据长度——这通常用于设置 HTTP Content-Length 头、校验完整性,或对接要求预知 payload 大小的第三方系统。
但直接使用 OutputStream.nullOutputStream()(如问题中所示)无法回溯数据,因为该流会丢弃所有写入内容;而 IOUtils.copyLarge() 的返回值虽常被误认为“压缩后字节数”,但其 Javadoc 仅说明 “returns the number of bytes copied”,并未明确是“读取字节数”还是“写入字节数”。实测发现:它返回的是输入流读取的原始字节数(即未压缩大小),而非 GZIP 压缩后的实际输出字节数。因此该方法不可靠,不应作为压缩尺寸依据。
✅ 推荐方案:使用 ByteArrayOutputStream 缓存压缩结果(适用于中小文件,内存可控)
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.zip.GZIPOutputStream;
public byte[] compressToGzipBytes(MultipartFile file) throws IOException {
try (InputStream is = file.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(baos)) {
IOUtils.copyLarge(is, gzos); // 此处 copyLarge 写入的是压缩数据到 baos
gzos.finish(); // 必须调用 finish() 确保 gzip trailer 写入
return baos.toByteArray();
}
}
// 使用示例
byte[] compressedBytes = compressToGzipBytes(file);
int compressedSize = compressedBytes.length; // ✅ 精确、可靠
InputStream compressedInputStream = new ByteArrayInputStream(compressedBytes);
sendCompressed(compressedInputStream, compressedSize);⚠️ 注意事项:
立即学习“Java免费学习笔记(深入)”;
- GZIPOutputStream 必须显式调用 .finish()(而非仅 close()),否则可能遗漏 gzip 尾部校验信息(如 CRC32、ISIZE),导致解压失败;
- ByteArrayOutputStream 将全部压缩数据暂存于堆内存,若文件原始体积过大(如 >50MB),需评估 JVM 内存压力,此时应改用临时文件方案;
- 若必须流式处理(如超大文件 + 限内存),可借助 PipedInputStream/PipedOutputStream,但需额外线程协调,复杂度显著上升,一般不推荐。
? 替代方案:使用临时文件(适用于大文件、内存敏感场景)
import java.nio.file.*;
Path tempFile = Files.createTempFile("gzip_", ".tmp");
try (InputStream is = file.getInputStream();
OutputStream os = Files.newOutputStream(tempFile);
GZIPOutputStream gzos = new GZIPOutputStream(os)) {
IOUtils.copyLarge(is, gzos);
gzos.finish();
}
long compressedSize = Files.size(tempFile); // ✅ 文件系统级精确大小
InputStream compressedInputStream = Files.newInputStream(tempFile);
// 记得在 sendCompressed 完成后清理:Files.deleteIfExists(tempFile);? 总结:
- ❌ 避免 nullOutputStream() + copyLarge() 返回值推断压缩大小(行为未保证,易出错);
- ✅ 优先选用 ByteArrayOutputStream + toByteArray().length(简洁、高效、适合多数场景);
- ✅ 超大文件时切换至 Files.createTempFile(),通过 Files.size() 获取磁盘上真实压缩体积;
- ⚠️ 始终调用 GZIPOutputStream.finish(),确保 gzip 数据结构完整。
这样即可在保证正确性的同时,灵活适配不同规模的文件压缩与尺寸感知需求。










