最近ファイル扱う時は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
が発生します。
ここではファイルで試したけど、ディレクトリももちろん扱えます。
Path
をFiles
に渡して操作する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
を通して操作をするっていうのがわかりやすくて良いです。