S-JIS[2011-06-05/2012-12-15] 変更履歴

Scala ファイル操作

Scalaでのファイル読み書きについて。


テキストファイルの読み込み

Scalaではテキストファイルを読み込むのにscala.io.Sourceを使用する。
ただ、JavaのReaderをそのまま使うことも出来る。


java.io.BufferedReaderの使用例

Scala Java 備考
import java.io._
import java.io.*;
 
val br = new BufferedReader(
  new InputStreamReader(
    new FileInputStream("ファイル名"), "MS932"
  )
)
BufferedReader br = new BufferedReader(
    new InputStreamReader(
        new FileInputStream("ファイル名"), "MS932"
    )
);
brを作る部分はJavaもScalaもほぼ同じ。
try {
  var line = ""
  while ({ line = br.readLine(); line ne null }) {
    println(line)
  }
} finally {
  br.close()
}
try {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} finally {
  br.close();
}
readLine()がnullだったら終了する。
  breakable {
    while (true) {
      val line = br.readLine()
      if (line eq null) break
      println(line)
    }
  }
    while (true) {
        String line = br.readLine();
        if (line == null) break;
        System.out.println(line);
    }
個人的にはwhileの条件判断部分で変数に値を入れるのは好きではないのだが、
Scalaではbreakを使う方が変。
    val line = try { br.readLine() } catch {
      case e: IOException =>
        throw new RuntimeException(e)
    }
        String line;
        try {
            line = br.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
readLine()の例外をキャッチしたい場合。[2011-06-24]

しかしwhileループで繰り返すのはScalaっぽくないので、コレクションに変換してみる。

Scala 備考
new Iterator[String] {
  var opt = Option(br.readLine)
  def hasNext = opt.nonEmpty
  def next() = {
    val r = opt.orNull; opt = Option(br.readLine); r
  }
}.foreach { line =>
  println(line)
}
本当はIteratorよりStreamの方が向いているのかもしれないが、
定義しないといけないメソッドが多そうなので断念。
(ここで言っているStreamは、ScalaのコレクションのStreamであって、java.io.InputStream等のことではない)
class UntilNullIterator[A](f: =>A) extends Iterator[A] {
  var opt = Option(f)
  def hasNext = opt.nonEmpty
  def next() = {
    val r = opt.get; opt = Option(f); r
  }
}
new UntilNullIterator(br.readLine).foreach { line =>
  println(line)
}
毎回匿名クラスを作っていると長くなってしまうので、専用クラスを作ってみた。
Optionから値を取り出すのにorNullでなくgetを使っているのは、
orNullはコンパイルエラーになってしまう為。
(Optionの型を型引数で指定しているが、AnyVal(Int等)が指定されたらnullが使えない)
 
case class UntilNullIterator[A](f: =>A)
<console>:1: error: `val' parameters may not be call-by-name
       case class UntilNullIterator[A](f: =>A)
                                          ^
newが出てくるのがちょっと嫌なのでcase classにしてみようと思ったが、
f: =>A」の部分がコンパイルエラーになってしまった。
「=>A」は名前渡し(call-by-name)であり、名前渡しはメソッド(関数)の引数にしか使えない(変数として保持できない)為。
(case classだとコンストラクターの引数もフィールドで保持する変数になる)
object untilNullIterator {
  def apply[A](f: =>A) = new Iterator[A] {
    var opt = Option(f)
    def hasNext = opt.nonEmpty
    def next() = {
      val r = opt.get; opt = Option(f); r
    }
  }
}
untilNullIterator(br.readLine).foreach { line =>
  println(line)
}
そこで、単なるobjectにしてみた。
applyは呼び出し時に省略できるので、newが出てこない初期化に見える。
implicit def bufferedReader2scala(br: BufferedReader) = new {
  def asScala = new Iterator[String] {
    var opt = Option(br.readLine)
    def hasNext = opt.nonEmpty
    def next() = {
      val r = opt.orNull; opt = Option(br.readLine); r
    }
  }
}
br.asScala.foreach { line =>
  println(line)
}
さらに、コレクションのJavaConverters(asScala)を真似てみた。

Iterator.continually()を使う方がもっとScalaらしくなるらしい。[2012-12-15]
(参考: kmizuさんのIterator.continually()を使おう

import java.io._
val br = new BufferedReader(new InputStreamReader(new FileInputStream("ファイル名"), "MS932"))
try {
  Iterator.continually(br.readLine()).takeWhile(_ != null).foreach { line =>
    println(line)
  }
} finally {
  br.close()
}

foreachで処理するのではなくてリストにしたい場合は以下のように書く。

val br = new BufferedReader(new InputStreamReader(new FileInputStream("ファイル名"), "MS932"))
val list = try {
    Iterator.continually(br.readLine()).takeWhile(_ != null).toList
  } finally {
    br.close()
  }
println(list) //読み込んだ内容を使う

toListを使うのがポイント。
toSeqを使うと実体がStreamになり、読み込んだ内容を使おうとするまで実際のファイル読み込みが行われない。したがって、内容を使う時点ではファイルが既にクローズされているので、読み込めなくて例外が発生する。


Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま