新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。もう新人じゃないかも…

学習記録:Scala逆引きレシピ

第6章:ファイル操作と入出力

標準ライブラリによる入出力

ファイルの読み込み

scala.io.Source#fromFileメソッドにより、ファイルを文字列として読み込むことができる。

import scala.io._

val source = Source.fromFile("hoge.txt", "UTF-8")

try {
  // Iteratorを取得して一行ずつ表示
  source.getLines().foreach { line: String => println(line) }
} finally {
  source.close ()
}

上記のとおり、getLinesメソッドIterator[String]を返す。
これを使用して、filterメソッドで空行を取り除いたり、mapメソッドで変換を行うことができる。

より高度な入出力

scala.ioパッケージの機能はそれほど強力ではなく、バイナリを扱えなかったりファイル出力ができなかったりする。
そのため、Scalaプログラムからファイルの入出力を行う場合、現実的には以下の方法を取る。

  • JavaAPIを使う
    • Javaの標準ライブラリや、Commons IOなどを使用する
    • ただし、Java向けに設計されているので記述が冗長になりがち
  • Scalaベースの入出力ライブラリを使う
    • JavaAPIをラップしてScala用のインタフェースを提供
    • Scala IOなど

Scala IO

使い方

前述のとおり、Scala IOはScalaでファイル操作を行うならば活用したいライブラリである。
Scala IOは以下のAPIで構成されている。

  • Core APIScala IOの基盤となるライブラリ。入出力のためのAPIを提供し、こちらは必須
  • File API:ファイルやディレクトリ操作のAPIを提供。こちらはオプション

使用する場合、build.sbtに以下の依存関係を追加する。

libraryDependencies ++= Seq(
  "com.github.scala-incubator.io" %% "scala-io-core" % "0.4.0",
  "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.0"
)

sbtについては始める sbt — 付録: .scala ビルド定義あたりを参照。

ファイル・ディレクトリを扱う

ファイルやディレクトリのパスはscalax.file.Pathクラスで表す。

import scalax.file.Path

// ファイルのパスを生成
val filePath = Path("hoge.txt")
// ネストしたパスを生成
val dirPath = Path("foo", "bar") // foo/barと同じ意味
var path = Path("foo/bar", '/') // 同上

createFileおよびcreateDirectoryメソッドでファイル・ディレクトリを生成できる。
デフォルトでは親ディレクトリが存在しない場合は自動生成し、また既に存在する場合にはIOExceptionをスローする。

filePath.createFile ( )
dirPath.createDirectory ( )

同様にdelete/deleteRecursivelyメソッドで削除、copyTo/moveToメソッドでコピー・移動を行う。

ファイルに対して読み書きを行う

scalax.io.Input/Outputトレイトを使用する。

import scalax.file.Path

val path = Path("readme.txt")
// ファイルの内容を文字列として読み込み
val str = path.slurpString
// 一行ずつ読み込み
Path.lines ( ).foreach { line =>
  println(line)
}

// ファイルに文字列を書き込み
path.write("書き込む文字列")
// ファイルに文字列を追加
path.append("追加する文字列")

第8章:ツール、ユーティリティ

ユーティリティ

外部プログラムを実行する

scala.sys.processパッケージをインポートすることで、文字列に対して直接コマンドを実行するメソッドを呼び出せる。

import scala.sys.process._

// コマンドの戻り値(終了コード)を取得
val code = "ls -al"!
// コマンドの実行結果を取得
val code = "ls -al"!!

// 複数コマンドを実行
"mkdir test"###"cp *.txt test"!
// 最初のコマンドが成功したら次のコマンドを実行
"mkdir test"#&&"cp *.txt test"!

// 実行結果をリダイレクト
"cat sample.txt" #> new java.io.File("result.txt") !
// ファイルからリダイレクト
"grep name" #< new java.io.File("result.txt") !
ログを出力する

scala.util.logging.Loggedトレイトを使用する。
Loggedトレイトのlogメソッドをログ出力を行うクラスでミックスインしてlogメソッドを使用するが、
このトレイトではlogメソッドの実装は空のため、インスタンス生成時にログ出力を実装したトレイトをミックスインする必要がある。

import scala.util.logging._

class Sample extends Logged {
  def hello ( ): Unit = {
    log("sample#Hello - begin")
    println("hello world")
    log("sample#hello - end")
  }
}

// ログ出力処理を実装したトレイトをミックスインしてインスタンスを生成
val sample = new Sample with ConsoleLogger
sample.hello ( )

なお、標準ライブラリのロギング機能は簡易的なものなので、専用のロギングライブラリを使った方が良い。
専用ライブラリには、slf4sやutil-logging, logulaなどがある。

日付処理を行う

scala-timeはJava用の日付ライブラリJodaTimeをScalaから使用するためのラッパーである。
使用するには、build.sbtに以下の設定をする。

libraryDependencies ++= Seq(
  "org.scala-tools.time" % "time_2.9.1" % "0.5"
)

scala-timeでは、日時をorg.joda.time.DateTimeオブジェクトで表す。
DateTimeオブジェクトは+や-メソッドで日時の減算を行うことができる。

import org.scala_tols.time.imports._

// 現在日時を取得
val dateTime1 = DateTime.now
// 現在の2ヶ月後のDateTimeを取得
val dateTime2 = DateTime.now + 2.months

// 日付のパターンを指定してフォーマット
val formatter = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss")
println(formatter.print(dateTime1)

ツール

scaladoc

Scalaでは、クラスやフィールドなどにscaladocコメントを記述しておくと、HTMLベースのAPIリファレンスを作成できる。
javadocと同様、/**で始まる複数行コメントとして記述する。

/**
 * == 見出し1 ==
 * === 見出し2 ===
 * ==== 見出し3 ====
 * - 箇条書き
 * __下線 __
 * '''ボールド'''
 * @version 1.0
 */
class ScalaDocSample ( ) {
  /**
   * メッセージです
   * 
   * @param name 名前
   * @return メッセージ
   */
   ...
}

クラスファイルに対してscaladocコマンドを実行することで、APIリファレンスを生成できる。
また、sbtを使っている場合は、「sbt doc」で生成できる。