1. asmasa

    Posted

    asmasa
Changes in title
+NIO.2でファイルを扱う
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,202 @@
+最近ファイル扱う時は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
+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
+scala> val path1 = Paths.get("dir/file.txt")
+path1: java.nio.file.Path = dir/file.txt
+```
+
+- `java.nio.file.FileSystems`を使う
+
+```scala
+scala> val path2 = FileSystems.getDefault.getPath("dir", "file.txt")
+path2: java.nio.file.Path = dir/file.txt
+```
+
+- `java.io.File`からの変換
+
+```scala
+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
+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
+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
+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
+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`でファイルが存在している場合は置換するって指定ができます。
+詳しくは以下を見ると良いです。
+
+- [列挙StandardCopyOption](https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/StandardCopyOption.html)
+- [列挙LinkOption](https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/LinkOption.html)
+
+## move
+
+```scala
+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
+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
+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
+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
+scala> Files.setAttribute(path, "lastModifiedTime", FileTime.fromMillis(System.currentTimeMillis()))
+res4: java.nio.file.Path = dir/file.txt
+```
+
+今更ながらNIO.2のファイルインタフェースを見てみたけど、`File`の時と比べると使いやすくなっていますね。
+`Files`を通して操作をするっていうのがわかりやすくて良いです。