1. 概述
本文将介绍在 Kotlin 中如何将 InputStream
的内容复制到文件中的几种常用方法。
虽然 Java 和 Kotlin 在 I/O 处理上共享大量标准库和第三方工具(如 input stream 转文件),但本文仅聚焦于 Kotlin 标准库中最符合语言习惯(idiomatic)的方式。如今这些 API 已相当成熟,足够应对大多数场景。
我们不会引入额外依赖,而是利用 Kotlin 自带的扩展函数与 JVM NIO 提供的能力,帮助你在实际开发中写出更简洁、安全的代码。同时也会指出一些容易“踩坑”的反模式。
2. 常见但不推荐的做法 ❌
最直观的想法是:先把整个流读进内存,再一次性写入文件:
val file: File = // 来自某处
val bytes = inputStream.readBytes()
file.writeBytes(bytes)
这种写法看似简单,但在生产环境中 ✅ 极易引发 OOM(OutOfMemoryError),尤其是当输入流来自网络或大文件时。
⚠️ 核心问题在于:readBytes()
会将整个流加载到内存中 —— 这意味着如果你处理的是一个 1GB 的文件,JVM 就需要至少 1GB 堆空间来容纳这个字节数组。
即使对于小数据量,这种方式也不一定更高效,因为其他方案通常使用缓冲机制(buffering),而这种方式反而失去了流式处理的优势。
✅ 正确做法是:始终采用 边读边写 的流式传输,避免中间全量加载。
3. 使用 copyTo()
扩展函数 ✅
Kotlin 标准库为 InputStream
提供了一个非常实用的扩展函数:copyTo(output)
,它能自动完成从输入流到输出流的数据拷贝。
示例代码如下:
val content = "Hello World".repeat(1000)
val file: File = createTempFile()
val inputStream = ByteArrayInputStream(content.toByteArray())
inputStream.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
assertThat(file).hasContent(content)
关键点说明:
file.outputStream()
是 Kotlin 的扩展函数,返回一个OutputStream
实例。copyTo()
接收OutputStream
类型参数,因此两者天然配合。- 使用
use {}
确保资源自动关闭,等价于 Java 的 try-with-resources,防止资源泄漏。
3.1 缓冲机制与性能优化
copyTo()
内部默认使用 8KB 缓冲区进行分块读取,减少系统调用次数,提升 I/O 性能(尤其涉及磁盘或网络时)。
源码参考:IOStreams.kt#L103
你还可以通过第二个参数自定义缓冲区大小:
input.copyTo(output, bufferSize = 16 * 1024) // 使用 16KB 缓冲
📌 建议根据实际场景调整缓冲区大小:
- 小文件(<1MB):保持默认即可
- 大文件或高吞吐场景:可尝试 32KB 或 64KB
4. 使用 Files.copy()
方法 ✅
除了 Kotlin 扩展函数,也可以直接使用 Java NIO 的 Files.copy()
工具方法:
inputStream.use { input ->
Files.copy(input, Paths.get("./copied"))
}
assertThat(File("./copied")).hasContent(content)
该方法直接接受 InputStream
和 Path
对象,无需手动管理 OutputStream
。
如果已有 File
对象,可通过 .toPath()
转换后使用:
val file: File = // 来自配置或参数
Files.copy(input, file.toPath())
🔗 参考文档:Files.copy(InputStream, Path)
⚠️ 注意事项:
- 若目标路径已存在文件,默认行为会抛出异常(可通过
StandardCopyOption.REPLACE_EXISTING
控制) - 同样基于缓冲机制实现,性能良好(源码实现)
优点:
- 更贴近底层,适合与 NIO 路径体系集成
- 支持更多复制选项(如替换、原子操作等)
5. Java 9+ 的 transferTo()
方法 ✅
从 Java 9 开始,InputStream
新增了原生方法 transferTo(OutputStream)
,用于高效传输数据:
val file = createTempFile()
val inputStream = ByteArrayInputStream(content.toByteArray())
inputStream.use { input ->
file.outputStream().use { output ->
input.transferTo(output)
}
}
assertThat(file).hasContent(content)
特性对比:
特性 | transferTo() |
copyTo() |
---|---|---|
底层支持 | JDK 原生 | Kotlin 扩展封装 |
缓冲策略 | 默认平台优化 | 固定 8KB(可调) |
兼容性 | Java 9+ | 所有支持 Kotlin 的 JVM |
💡 实际上,Files.copy(InputStream, Path)
在内部就是调用了 transferTo()
(源码链接),所以它是目前最接近“零拷贝”理念的实现之一。
✅ 推荐在 Java 9+ 环境中优先考虑 transferTo()
,特别是在高性能服务中处理大文件上传、代理转发等场景。
6. 总结
方法 | 是否推荐 | 适用场景 |
---|---|---|
readBytes() + writeBytes() |
❌ 不推荐 | 仅限极小数据、测试用途 |
InputStream.copyTo() |
✅ 推荐 | Kotlin 项目通用选择,简洁安全 |
Files.copy(InputStream, Path) |
✅ 推荐 | 需要 NIO 路径控制或高级选项 |
InputStream.transferTo() |
✅ 强烈推荐(Java 9+) | 高性能场景,底层最优 |
📌 最佳实践建议:
- ✅ 始终使用流式处理,避免全量加载内存
- ✅ 使用
use {}
确保资源释放 - ✅ 在 Java 9+ 环境下优先使用
transferTo()
- ✅ 合理设置缓冲区大小以平衡内存与性能
所有示例代码均已托管至 GitHub:Baeldung Kotlin 教程仓库
欢迎 clone 学习或作为项目模板复用。