1. 概要

このチュートリアルでは、InputStreamのコンテンツをKotlinのファイルにコピーするいくつかの方法を理解します。

確かに、標準ライブラリとサードパーティライブラリに関しては、この点でJavaとKotlinの間に重複があります。 ただし、ここでは標準ライブラリで最も慣用的なアプローチのみを取り上げます。これらは最近かなり成熟しているためです。

2. (通常)間違った方法

簡単な解決策の1つは、最初に InputStream からすべてのバイトを読み取り、次にそれをファイルに書き込むことです。

val file: File = // from somewhere
val bytes = inputStream.readBytes()
file.writeBytes(bytes)

このアプローチを使用するように誘惑されたり、相談されたりする可能性があります。 ただし、このアプローチでは、特に入力サイズが不明または大きい場合に、簡単に大きな問題に直面する可能性があります。 これは、ファイルへの書き込みを開始する前にコンテンツ全体をメモリにロードしているためです。 基本的に、私たちはメモリが無制限であるように振る舞っていますが、明らかにそうではありません。

さらに、他のアプローチでは通常、内部バッファーを使用してInputStreamから読み取ります。 したがって、このアプローチは、小さなサイズのストリームに対してもそれほど優れていません。

ですから、この誘惑を避けて、より良い方法に慣れましょう。

3. copyTo()拡張関数

最初のアプローチは、Kotlin のInputStreamでcopyTo(output)拡張関数を使用することです。 この関数は、受信側のInputStreamから指定されたOutputStreamにすべてをコピーします。

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)

上記の例では、最初に一時ファイルを作成してから、InputStreamからそのファイルにすべてをコピーしています。 最後に、コンテンツが期待どおりにコピーされていることも確認しています。 パスから取得したものなど、任意のファイルインスタンスに対して同じ操作を実行することもできます。

val file = File("path/to/file")

File.outputStream()拡張関数は、FileインスタンスをOutputStreamに変換することに注意してください。 これは、 copyTo()関数がOutputStreamのインスタンスを宛先として想定しているために必要です。

明らかに、上記の例で最も重要な部分は input.copyTo(output)であり、これはコピーの重労働を処理します。

さらに、責任を持って行動し、基になるリソースを使い終わったら、閉じる必要があります。 したがって、これらのリソースを自動的に管理するために、 use()関数をかなり広範囲に使用しています。 この手法は、JVMコミュニティではtry-with-resourcesとも呼ばれます。

3.1. バッファリング

内部的には、デフォルトでは、 copyTo()関数は、InputStreamから読み取るたびに8キロバイトをバッファリングします。 これは、特にカーネルが関係している場合に、コピーのパフォーマンスを向上させるための非常に一般的なトリックです(例: 開いているファイルまたはソケットからの読み取り)。

非常に興味深いことに、この拡張関数を使用すると、2番目の引数を使用してバッファーサイズをカスタマイズできます。

input.copyTo(output, 16 * 1024)

上に示したように、デフォルトの8ではなく16キロバイトをバッファリングしています。

4. Files.copy()

Kotlin拡張機能に加えて、 Files.copy(stream、path)ユーティリティを使用して、InputStreamから指定されたPathインスタンスにコピーできます。

inputStream.use { input ->
    Files.copy(input, Paths.get("./copied"))
}

assertThat(File("./copied")).hasContent(content)

ここでは、現在のディレクトリにあるコピーされたという名前のファイルにストリームコンテンツをコピーしています。 同様に、すでに File インスタンスがある場合は、 toPath()メソッドを使用して、 Files.copy()を引き続き使用できます。 ]方法:

val file: File = // from somewhere
Files.copy(input, file.toPath())

同様に、 Files.copy()メソッドも、前のアプローチと同じバッファリング手法を使用しています。

5. transferTo() in Java 9+

Java 9では、 InputStream.transferTo(output)という新しいメソッドが導入され、InputStreamのコンテンツがOutputStreamに転送されます。

val file = createTempFile()
val inputStream = ByteArrayInputStream(content.toByteArray())

inputStream.use { input ->
    file.outputStream().use { output ->
        input.transferTo(output)
    }
}

assertThat(file).hasContent(content)

このメソッドは宛先ファイルにOutputStreamを必要とするため、同じ File.outputStream()拡張関数を再度使用しています。 非常に興味深いことに、 Files.copy()メソッドはこのメソッドを内部で使用しています

6. 結論

このチュートリアルでは、InputStreamのコンテンツをKotlinのファイルにコピーするためのいくつかのアプローチを学びました。 APIレベルでは異なりますが、すべて同じバッファリングアプローチを使用していたため、実装の詳細は非常に似ていました。

いつものように、すべての例はGitHubから入手できます。