使用AWS SDK 2.0时,未知InputStream长度如何调用S3 putObject方法
我之前刚从AWS SDK 1.x转到2.x时,也遇到过完全一样的困惑!确实2.x的RequestBody.fromInputStream强制要求传入contentLength,不像旧版那样可以直接传流了事。不过针对你1MB-20MB的文件大小,有几个实用的解决方案:
方案1:将InputStream转为字节数组上传(最简单)
这个方案直接把整个流读取到字节数组里,然后用RequestBody.fromBytes上传,代码实现非常简洁:
import org.apache.commons.io.IOUtils; // 可以用Apache Commons IO,也可以自己实现流读取 InputStream stream = otherApi.get(); try { byte[] fileBytes = IOUtils.toByteArray(stream); s3.putObject( PutObjectRequest.builder() .bucket("你的存储桶名称") .key("文件在S3中的键名") .build(), RequestBody.fromBytes(fileBytes) ); } finally { stream.close(); // 记得关闭流 }
优势:代码简单,上手快;20MB的字节数组在现代JVM中不会造成内存压力,完全适配你的场景。
缺点:需要把整个文件加载到内存,如果文件更大(比如超过100MB)就不推荐了。
方案2:先计算流长度再上传
如果不想一次性加载整个文件到内存,可以先读取流计算长度,再重置流进行上传(前提是你的InputStream支持mark()和reset()方法):
InputStream stream = otherApi.get(); try { stream.mark(Integer.MAX_VALUE); // 标记当前流位置,确保流支持标记操作 ByteArrayOutputStream tempOut = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int readLen; while ((readLen = stream.read(buffer)) != -1) { tempOut.write(buffer, 0, readLen); } long contentLength = tempOut.size(); stream.reset(); // 重置流到标记的起始位置 s3.putObject( PutObjectRequest.builder() .bucket("你的存储桶名称") .key("文件在S3中的键名") .build(), RequestBody.fromInputStream(stream, contentLength) ); } finally { stream.close(); }
注意:如果你的InputStream不支持mark/reset(比如某些网络流),这个方法会抛出IOException,这时可以考虑下面的临时文件方案。
方案3:用临时文件缓存流后上传
这个方案把InputStream写入本地临时文件,再通过文件上传,内存占用最低,也适合更大的文件:
InputStream stream = otherApi.get(); File tempFile = null; try { tempFile = File.createTempFile("s3-upload-", ".tmp"); try (FileOutputStream fos = new FileOutputStream(tempFile)) { byte[] buffer = new byte[1024]; int readLen; while ((readLen = stream.read(buffer)) != -1) { fos.write(buffer, 0, readLen); } } s3.putObject( PutObjectRequest.builder() .bucket("你的存储桶名称") .key("文件在S3中的键名") .build(), RequestBody.fromFile(tempFile) ); } finally { stream.close(); if (tempFile != null) { tempFile.delete(); // 上传完成后删除临时文件 } }
优势:内存占用极低,适合任意大小的文件;即使流不支持mark/reset也能正常工作。
缺点:需要读写本地磁盘,比前两个方案多一点IO开销,但对于20MB的文件来说几乎可以忽略。
为什么SDK 2.x需要contentLength?
其实AWS S3的PUT API本身就要求请求头中携带Content-Length,旧版1.x SDK是在后台帮你悄悄处理了——要么把流缓存到内存/磁盘计算长度,要么自动切换成分块上传。而2.x SDK把这个控制权交给了开发者,让你可以根据自己的场景选择最适合的方式,避免不必要的内存或磁盘开销。
很多刚转2.x的开发者都会遇到这个问题,选个适合你场景的方案就行,你的文件大小用前两个方案都很省心~
内容的提问来源于stack exchange,提问作者mmjan

