Akka の FileIO でファイルを読み書き
Akka (akka-stream) の FileIO
を使ってファイルの読み書きを行ってみます。
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20170131/
はじめに
akka-stream では Java 用の API は akka.stream.javadsl
パッケージに、Scala 用の API は akka.stream.scaladsl
パッケージに定義されています。
主要なクラスやメソッドは javadsl と scaladsl で概ね共通化されているようですが、Source
の recover
メソッドは deprecated の有無が違っていました。
Source の recover/recoverWith メソッドの deprecated 状況
パッケージ | deprecated 有り | deprecated 無し | 備考 |
---|---|---|---|
akka.stream.scaladsl | recoverWith | recover | ソースは akka/stream/scaladsl/Flow.scala |
akka.stream.javadsl | recover | recoverWith | ソースは akka/stream/javadsl/Source.scala |
ここで、deprecated の理由が @deprecated("Use recoverWithRetries instead.", "2.4.4")
のようなので、scaladsl の方が正しく、javadsl の方は deprecated するメソッドが違っているのだと思います ※。
※ recoverWith の代わりに recoverWithRetries を使えるが (引数が 1つ増えただけなので) recover は引数の型がそもそも違う
Scala の場合
まずは Scala で実装してみます。
ファイル用の Sink は FileIO.toPath
で、Source は FileIO.fromPath
で取得できます。
ファイルの入出力の型は akka.util.ByteString
となっており、以下では map を使って String との変換を行っています。
Source の recover
メソッドを使えば、例外が発生していた場合に代用の値へ差し替えて処理を繋げられます。
今回は、ファイルが存在しない等で IOException が発生した際に、"invalid file, ・・・" という文字列へ差し替えるために recover を使っています。
src/main/scala/SampleApp.scala
import akka.actor.ActorSystem import akka.stream.ActorMaterializer import akka.stream.scaladsl.{FileIO, Source} import akka.util.ByteString import java.nio.file.Paths import java.io.IOException object SampleApp extends App { implicit val system = ActorSystem() import system.dispatcher // ExecutionContext を implicit implicit val materializer = ActorMaterializer() // ファイルへの書き込み(sample1.txt へ "sample data" を出力) val res1 = Source.single("sample data") .map(ByteString.fromString) .runWith(FileIO.toPath(Paths.get("sample1.txt"))) // ファイルの読み込み(sample2.txt の内容を println) val res2 = FileIO.fromPath(Paths.get("sample2.txt")) .map(_.utf8String) .recover { case e: IOException => s"invalid file, ${e}" } .runForeach(println) res1.flatMap(_ => res2).onComplete(_ => system.terminate) }
ビルドと実行
Gradle のビルド定義ファイルは以下の通りです。
build.gradle
apply plugin: 'scala' apply plugin: 'application' mainClassName = 'SampleApp' repositories { jcenter() } dependencies { compile 'org.scala-lang:scala-library:2.12.1' compile 'com.typesafe.akka:akka-stream_2.12:2.5-M1' }
sample2.txt ファイルの無い状態で実行します。
実行結果1
> gradle -q run invalid file, java.nio.file.NoSuchFileException: sample2.txt
sample2.txt を作成して実行します。
実行結果2
> echo %time% > sample2.txt > gradle -q run 22:11:48.03
Java の場合
次に Java で実装してみます。
Scala と概ね同じですが、akka.stream.javadsl.Source でも recover
の引数が scala.PartialFunction
となっており、多少の工夫が必要です。
recover の引数に合う scala.PartialFunction は Akka の akka.japi.pf.PFBuilder
で作れるので、以下では Match.match
から PFBuilder を取得して使っています。
なお、Akka では scala.PartialFunction を組み立てるための Java 用の API が akka.japi.pf
パッケージにいくつか用意されています。
src/main/java/SampleApp.java
import akka.actor.ActorSystem; import akka.japi.pf.Match; import akka.japi.pf.PFBuilder; import akka.stream.ActorMaterializer; import akka.stream.Materializer; import akka.stream.javadsl.FileIO; import akka.stream.javadsl.Source; import akka.util.ByteString; import java.io.IOException; import java.nio.file.Paths; import java.util.concurrent.CompletableFuture; public class SampleApp { public static void main(String... args) { final ActorSystem system = ActorSystem.create(); final Materializer materializer = ActorMaterializer.create(system); // ファイルへの書き込み(sample1.txt へ "sample data" を出力) CompletableFuture<?> res1 = Source.single("sample data") .map(ByteString::fromString) .runWith(FileIO.toPath(Paths.get("sample1.txt")), materializer) .toCompletableFuture(); // scala.PartialFunction のビルダー定義 PFBuilder<Throwable, String> pfunc = Match.match(IOException.class, e -> "invalid file, " + e); // ファイルの読み込み(sample2.txt の内容を println) CompletableFuture<?> res2 = FileIO.fromPath(Paths.get("sample2.txt")) .map(ByteString::utf8String) .recover(pfunc.build()) .runForeach(System.out::println, materializer) .toCompletableFuture(); CompletableFuture.allOf(res1, res2) .handle((v, e) -> system.terminate()) .join(); } }
ビルドと実行
Gradle のビルド定義ファイルは以下の通りです。
build.gradle
apply plugin: 'application' mainClassName = 'SampleApp' repositories { jcenter() } dependencies { compile 'com.typesafe.akka:akka-stream_2.12:2.5-M1' }
sample2.txt ファイルの無い状態で実行します。
実行結果1
> gradle -q run ・・・\src\main\java\SampleApp.javaは非推奨のAPIを使用またはオーバーライドしています。 ・・・ invalid file, java.nio.file.NoSuchFileException: sample2.txt
recover が deprecated されている件で警告メッセージが出力されますが、上述したように recover を deprecated しているのが誤りだと思われるので無視します。
sample2.txt を作成して実行します。
実行結果2
> echo %time% > sample2.txt > gradle -q run ・・・ 22:14:16.00