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)

该方法直接接受 InputStreamPath 对象,无需手动管理 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+) 高性能场景,底层最优

📌 最佳实践建议:

  1. ✅ 始终使用流式处理,避免全量加载内存
  2. ✅ 使用 use {} 确保资源释放
  3. ✅ 在 Java 9+ 环境下优先使用 transferTo()
  4. ✅ 合理设置缓冲区大小以平衡内存与性能

所有示例代码均已托管至 GitHub:Baeldung Kotlin 教程仓库
欢迎 clone 学习或作为项目模板复用。


原始标题:Writing InputStream to File in Kotlin