Java

NIO.2でファイルを扱う

最近ファイル扱う時はbetter-filesを使っている。
better-files自体はJava NIO周りのScala wrapperなんだけど、そういえばJava NIO.2で追加されたファイル操作周りのAPIをあまり知らなかったから使ってみる。

NIO.2

そもそもNIO.2って?ところで。
Java SE7で導入されたAPIでJava 1.4で導入されたNIOを強化するもの。具体的には以下。

  • ファイルシステムインタフェース
  • 非同期I/O
  • ソケットチャネル機能の完成

NIOに取り込めなかった機能を取り込んでいる。
この後は最初に述べた通りファイル、つまりファイルシステムインタフェースについて見ていきます。

実行環境はこんな感じ。Scalaで書きます。

  • Java: 1.8.0_131
  • Scala: 2.12.3

以下をimportをしていることが前提です。

scala> import java.nio.file._
scala> import java.io.File
scala> import java.nio.file.attribute._

java.nio.file.Path

ファイルやディレクトリの場所を示すインタフェース。
Pathが今回のファイルシステムインタフェースのキモなんじゃないかと思う。
Fileがディレクトリも表すって違和感あったから、パスって考え方ができたのは良い感じ。

インスタンス生成

  • java.nio.file.Pathsを使う
scala> val path1 = Paths.get("dir/file.txt")
path1: java.nio.file.Path = dir/file.txt
  • java.nio.file.FileSystemsを使う
scala> val path2 = FileSystems.getDefault.getPath("dir", "file.txt")
path2: java.nio.file.Path = dir/file.txt
  • java.io.Fileからの変換
scala> val file = new File("dir/file.txt")
file: java.io.File = dir/file.txt

scala> val path3 = file.toPath
path3: java.nio.file.Path = dir/file.txt

絶対パス/相対パス

今までの例は相対パスで書いていた。絶対パスで生成したいなら以下な感じ。

scala> val absolutePath1 = Paths.get("/tmp/dir/file.txt")
absolutePath1: java.nio.file.Path = /tmp/dir/file.txt

scala> val absolutePath2 = Paths.get("/", "tmp", "dir", "file.txt")
absolutePath2: java.nio.file.Path = /tmp/dir/file.txt

java.io.Fileへの変換

scala> val path = Paths.get("dir/file.txt")
path: java.nio.file.Path = dir/file.txt

scala> val file = path.toFile
file: java.io.File = dir/file.txt

java.nio.file.FileSystem

ファイルシステムへのインタフェース。
通常のファイルシステム、ZIPファイル、Jarファイルをこのインタフェースで透過的に扱えるみたい。

java.nio.file.FileSystems

FileSystemを生成するファクトリですね。
Pathを生成する例ではgetDefalutメソッドによりプラットフォームデフォルトのファイルシステムを取得する。

scala> val file = path1.toFile
file: java.io.File = dir/file.txt

java.nio.file.Paths

さっき使ったけどPathを生成するメソッドしかないんで、Pathのファクトリですね。
中でFileSystems.getDefault.getPathを呼んでいるだけなので、こっち使うほうが短くて良いですね。

java.nio.file.Files

ファイルとかディレクトリを扱うインタフェース。
基本的にはPathを渡して、そのPathの実体のファイルとかディクレ取りに対して何かをやる感じ。
commons IOのFileUtilsに似ている。

きっと使う頻度が多いcopy/move/deleteを使ってみる。

copy

scala> val src = Paths.get("dir/src.txt")
src: java.nio.file.Path = dir/src.txt

scala> val dest = Paths.get("dir/dest.txt")
dest: java.nio.file.Path = dir/dest.txt

scala> val path = Files.copy(src, dest)
path: java.nio.file.Path = dir/dest.txt

戻り値は引数で渡したdestをそのまま返します。
srcのファイルが存在しない場合はNoSuchFileExceptionが発生する。
destのファイルが存在している場合はFileAlreadyExistsExceptionが発生する。

copyメソッドには第3引数でCopyOptionというインタフェースを受け取れます。これでどういう感じでコピーするかを決めることができます。
例えばStandardCopyOption.REPLACE_EXISTINGでファイルが存在している場合は置換するって指定ができます。
詳しくは以下を見ると良いです。

move

scala> val src = Paths.get("dir/src.txt")
src: java.nio.file.Path = dir/src.txt

scala> val path = Files.move(src, dest)
path: java.nio.file.Path = dir/dest.txt

ほぼcopyと一緒ですね。

destファイルが存在する場合はFileAlreadyExistsExceptionが発生します。

delete

scala> val path = Paths.get("dir/file.txt")
path: java.nio.file.Path = dir/file.txt

scala> Files.delete(path)

deleteの場合は戻り値がないです。これはPathを削除したら存在しないモノになるから何も返さないんでしょうね。

削除対象のファイルが存在しない場合はNoSuchFileExceptionが発生します。

ここではファイルで試したけど、ディレクトリももちろん扱えます。

PathFilesに渡して操作するAPIはいけている。
他にもFilesからリーダー、ライター、チャネル、ストリームを取得できるメソッドがあって、Fileよりも扱いやすくなっている。

ファイル属性

Fileの場合はOS共通の属性しか扱えなかったけど、NIO.2ではOS毎の属性も扱えるようになっている。
属性とはファイルのオーナー、作成日、更新日、権限とか。

実際に取得してみる。

scala> val path = Paths.get("dir/file.txt")
path: java.nio.file.Path = dir/file.txt

scala> val attributes = Files.getFileAttributeView(path, classOf[PosixFileAttributeView]).readAttributes()
attributes: java.nio.file.attribute.PosixFileAttributes = sun.nio.fs.UnixFileAttributes@364beb26


scala> attributes.creationTime
res0: java.nio.file.attribute.FileTime = 2017-08-28T22:45:16Z

scala> attributes.owner
res1: java.nio.file.attribute.UserPrincipal = asmasa

設定ももちろんできる。

scala> Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis()))
res2: java.nio.file.Path = dir/file.txt

scala> val owner = FileSystems.getDefault.getUserPrincipalLookupService .lookupPrincipalByName("test")
owner: java.nio.file.attribute.UserPrincipal = test

scala> Files.setOwner(path, owner)
res3: java.nio.file.Path = dir/file.txt

こんな感じでもできる。

scala> Files.setAttribute(path, "lastModifiedTime", FileTime.fromMillis(System.currentTimeMillis()))
res4: java.nio.file.Path = dir/file.txt

今更ながらNIO.2のファイルインタフェースを見てみたけど、Fileの時と比べると使いやすくなっていますね。
Filesを通して操作をするっていうのがわかりやすくて良いです。