ScalaのI/Oライブラリシリーズ第三弾です。今回試してみたのはbetter-filesという、2015年に開発が開始された比較的新しいものです。名前からもわかる通り、I/Oライブラリといってもファイル操作を重視したライブラリになっています。
特徴はjava.io
に対するユーティリティやimplicit conversionを提供するものではなく、新たにScala用のインターフェースが実装されており、Javaとの相互運用のためのメソッドが提供されているということです。Scala標準のコレクションライブラリと同じような立ち位置と考えるとわかりやすいかもしれません。
better-filesを使用するにはbuild.sbt
に以下の依存関係を追加します。
libraryDependencies += "com.github.pathikrit" %% "better-files" % "2.14.0"
まずはFile
を生成します。様々な生成方法があり、java.io.File
からtoScala
で変換することもできます。
val f1 = File("build.sbt") val f2 = file"src/scala" val f3 = new java.io.File("build.sbt").toScala val f4 = f2/"test/Sample.scala" val f5 = "src"/"scala"/"main"/"test"/"Sample.scala"
読み込み、書き込みを簡単に行うことができます。
// 文字列で読み込み val string = f.contentAsString // バイト配列で読み込み val bytes = f.byteArray // 一行ごとに処理(裏では全体を一気に読み込んでるので巨大ファイルは不可) f.lines.foreach { line => ... } // 文字列を書き込み、追記 f.write("こんにちは").append("世界") // バイト配列を書き込み f.write("こんにちは".getBytes("UTF-8"))(File.OpenOptions.default) // DSLっぽいやつ f < "Hello" << "World" // 反対方向にも書ける(IntelliJだと赤くなる) "World" `>>:` "Hello" `>:` f
文字コードはデフォルトではUTF-8ですが、明示的に指定することもできます。書き込み系のメソッドはFile.OpenOptions
というimplicitパラメータを取り、デフォルトではFile.OpenOptions.default
なのですが、なぜかバイト配列を書き込むメソッドだけはデフォルト値が定義されておらず、明示的に渡す必要があります。
ファイル操作はチェーンさせることができます。また、ワイルドカードを使用した検索も可能です。
// ファイル操作をチェーン f.createIfNotExists() .appendNewLine() .append("Hello World!") .copyTo(File("test.bak")) // ワイルドカードで検索 val files = File("src").glob("**/*.{java,scala}") files.foreach { file => println(file.pathAsString) } // オプションを指定すると正規表現も使える val files = File("src").glob(".*\\.scala$", syntax = "regex")
Javaのクラスにも簡単に変換できるので既存のJavaライブラリとの相互運用も容易です。
val file: java.io.File = f.toJava val in: java.io.InputStream = f.newInputStream val out: java.io.OutputStream = f.newOutputStream val reader: java.io.BufferedReader = f.newBufferedReader val writer: java.io.BufferedWriter = f.newBufferedWriter
ローンパターン的なやつはこんな感じ。JavaのストリームはautoClosed
でbetter-filesのManagedResource
に変換することで自動的にクローズされるようになります。なのでファイル以外の場合でも利用可能です。
// 自動でクローズされる for { in <- f1.inputStream out <- f2.outputStream } in.pipeTo(out) // Javaのストリームの場合 for { in <- new FileInputStream("test.txt").autoClosed out <- new FileOutputStream("test.out").autoClosed } in.pipeTo(out)
もちろんyield
で値を戻すこともできますが、戻り値がTraversable
になってしまうのがちょっと微妙かもしれません。この方法だと仕方ないですが…。
この他にもLinuxコマンド風のDSL、Zipファイルの作成と展開、定型的なファイルの読み込むためのスキャナ、ファイルのモニタリングなどの機能があり、かなり高機能なライブラリです。モナモナしてないので使い勝手はよさそうですが、エラーに関しては全体的に例外方式のようで、この点については賛否両論あるかもしれません。