Java NIO2のおさらいメモ

# 概要

Java 1.7で導入されたNew I/O API (NIO2)をおさらいしたときのメモです。仕事で使いそうな機能を中心に調べたので内容が偏っています。

**環境**

* Windows 10 Professional
* Oracle JDK 1.8.0_144

**参考**

* [パッケージjava.nio.file (Java Platform SE 8)] (https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/package-summary.html)
* [Java I/O、NIO、およびNIO.2] (https://docs.oracle.com/javase/jp/8/docs/technotes/guides/io/index.html)
* [JDK 10ドキュメント] (https://docs.oracle.com/javase/jp/10/index.html)
* [JSR 203: More New I/O APIs for the JavaTM Platform ("NIO.2")] (https://jcp.org/en/jsr/detail?id=203)
* [java.nio.file.Filesのメソッドを一通り使ってみた] (http://kagamihoge.hatenablog.com/entry/20130110/1357810886)


# [Path] (https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/Path.html) / [Paths] (https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/Paths.html)

Pathはインターフェースです。PathのインスタンスはPaths#get、FileSystem#getPath、File#toPathなどで取得できます。
実装クラスはWindows環境(Java 1.8.0_144)では`sun.nio.fs.WindowsPath`です。(確認していませんがUnix/Linux環境では`sun.nio.fs.UnixPath`と思います。)

## Pathのインスタンスの取得

Paths#getを使用すると簡単にPathのインスタンスを取得できます。引数にディレクトリやファイル名を文字列で指定する方法とURIで指定する方法があります。

```java
Path path = Paths.get("C:\\dir\\hoge.txt");
```

Paths#getは内部でFileSystem#getPathを実行しています。下記のコードは上記のコードと同じ結果になります。

```java
Path path = FileSystems.getDefault().getPath("C:\\dir\\hoge.txt");
```

ディレクトリ、ファイルを区切って指定することもできます。この場合、ファイルの区切り文字は必要がありません。

```java
Path path = Paths.get("C:", "dir", "hoge.txt");
System.out.println(path.toString());
// → C:\dir1\hoge.txt
```

## Fileとの相互互換

### FileからPath

File#toPathを使用します。

```java
File file = new File("hoge.txt");
Path path = file.toPath();
```

### Path から File

Path#toFileを使用します。

```java
Path path = Paths.get("hoge.txt");
File file = path.toFile();
```

## 絶対パス、相対パス

実行環境に依存しますがWindows環境ではパスがドライブレターから始まる場合、絶対パスとして解釈されます。
パスが絶対パスかどうかはPath#isAbsoluteで確認できます。

### 絶対パス

```java
Path path = Paths.get("C:", "dir1", "dir2", "hoge.txt");
System.out.println(path.isAbsolute());
// → true
```

### 相対パス

```java
Path path = Paths.get("dir1", "dir2", "hoge.txt");
System.out.println(path.isAbsolute());
// → false
```

相対パスで表現するディレクトリやファイルを作成する場合`user.dir`システムプロパティで指定するディレクトリからの相対位置に作成されます。

下記の例はIDEから実行したときの結果です。(sampleはプロジェクト名です)

```java
String dir = System.getProperty("user.dir");
System.out.println(dir);
// → C:\Users\rubytomato\IdeaProjects\sample

Path file = Paths.get("hoge.txt");
System.out.println(file.toAbsolutePath());
// → C:\Users\rubytomato\IdeaProjects\sample\hoge.txt
```

## 冗長なパス表現の解消

相対パスを表現するときに`.\\dir1`や`..\\dir2`などのようにピリオドを使用することがあります。このようなパス表現を絶対パス化するときにPath#normalizeを使用すると冗長な表現を解消することができます。

```java
Path path = Paths.get("C:\\dir1", "..\\dir2", "hoge.txt");
System.out.println(path.toString());
// → C:\dir1\..\dir2\hoge.txt

Path normalize = path.normalize();
System.out.println(normalize.toString());
// → C:\dir2\hoge.txt
```

# [Files] (https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/Files.html)

Pathで表現するディレクトリ、ファイルの操作を行うクラスです。

## ディレクトリの作成

Files#createDirectoryを使用します。

```java
Path dir = Paths.get("C:", "dir1");
Files.createDirectory(dir);
```

**NIOでは**

```java
File dir = new File("C:\\dir1");
dir.mkdir();
```

## ファイルの作成

Files#createFileを使用します。

```java
Path file = Paths.get("C:", "dir1", "hoge.txt");
Files.createFile(file);
```

**NIOでは**

```java
File file = new File("C:\\dir1\\hoge.txt");
file.createNewFile();
```

## ファイルサイズの取得

Files#sizeを使用します。単位はバイトです。

```java
Path file = Paths.get("C:", "dir1", "hoge.txt");
Long size = Files.size(file);
```

## ネストしたディレクトリを再帰的に作成

ネストしたディレクトリを再帰的に作成したい場合はFiles#createDirectoriesを使用します。

```java
Path path = Paths.get("C:", "dir1", "dir2", "dir3");
Files.createDirectories(path);
```

なお、上記の例のディレクトリをFiles#createDirectoryで作成しようとするとNoSuchFileExceptionがスローされます。

## ディレクトリ、ファイルの存在チェック

Files#exists、Files#nonExistsを使用します。

```java
Path path = Paths("C:", "dir1", "hoge.txt");
if (Files.exists(path)) {
  // ファイルが存在する場合の処理
}
```

```java
Path path = Paths("C:", "dir1", "fuga.txt");
if (Files.nonExists(path)) {
  // ファイルが存在しない場合の処理
}
```

### 存在しないディレクトリ、ファイルの操作

Pathで指定するディレクトリ、ファイルが実際に存在しない場合の実行結果は下記のようになりました。

**isXXX系メソッド**

```java
// 存在しないファイル
Path nonExistsFile = Paths.get("hoge.txt");

System.out.println(Files.isWritable(nonExistsFile));
// → false

System.out.println(Files.isReadable(nonExistsFile));
// → false

System.out.println(Files.isExecutable(nonExistsFile));
// → false

System.out.println(Files.isDirectory(nonExistsFile));
// → false
```

**isHiddenメソッド**

存在しないディレクトリ、ファイルに対して実行するとNoSuchFileExceptionをスローします。

**isSameFileメソッド**

引数で指定するファイルのどちらか、もしくは両方が存在しない場合はNoSuchFileExceptionをスローします。

**deleteメソッド**

存在しないディレクトリ、ファイルに対して実行するとNoSuchFileExceptionをスローします。

**sizeメソッド**

存在しないディレクトリ、ファイルに対して実行するとNoSuchFileExceptionをスローします。

## 2つのディレクトリ、ファイルのパスの比較

Pathで表現する2つのディレクトリまたはファイルが同じものかを比較するにはFiles#isSameFileを使用します。

```java
Path p1 = Paths.get("C:\\dir1\\dir2\\hoge.txt");
Path p2 = Paths.get("C:\\dir1\\dir3\\..\\dir2\\hoge.txt");
System.out.println(Files.isSameFile(p1, p2));
// → true
```

上記の例をequalsメソッドで比較するとfalseが返ります。

## テンポラリディレクトリの作成

Files#createTempDirectoryを使用します。

指定するディレクトリ内にテンポラリディレクトリを作成する場合

```java
Path root = Paths.get("C:", "temp");
Path tempDir = Files.createTempDirectory(root, "tempDir_");
System.out.println(tempDir.toString());
// → C:\temp\tempDir_1219973832281475457
```

デフォルトのテンポラリディレクトリ内にテンポラリディレクトリを作成する場合

```java
String dir = System.getProperty("java.io.tmpdir");
System.out.println(dir);
// → C:\Users\rubytomato\AppData\Local\Temp\

Path tempDir = Files.createTempDirectory("tempDir_");
System.out.println(tempDir.toString());
// → C:\Users\rubytomato\AppData\Local\Temp\tempDir_5189589627657871229
```

### デフォルトのテンポラリディレクトリとは

テンポラリディレクトリ・ファイルの作成は`TempFileHelper`クラスへ委譲されていて、Fileクラスと同様にシステムプロパティ`java.io.tmdir`で指定されるディレクトリをデフォルトとして使用しています。

> [File] 
(https://docs.oracle.com/javase/jp/8/docs/api/java/io/File.html)のJavaDoc
デフォルトの一時ファイル・ディレクトリは、システム・プロパティjava.io.tmpdirで指定されます。UNIXシステムの場合、このプロパティのデフォルト値は"/tmp"または"/var/tmp"、Microsoft Windowsシステムの場合は"C:\\WINNT\\TEMP"です。

## テンポラリファイルの作成

Files#createTempFileを使用します。

指定するディレクトリ内にテンポラリファイルを作成する場合

```java
Path root = Paths.get("C:", "temp");
Path tmpFile = Files.createTempFile(root, "temp_", ".txt");
System.out.println(tmpFile.toAbsolutePath().toString());
// → C:\temp\temp_4672863899169116557.txt
```

デフォルトのテンポラリディレクトリ内にテンポラリファイルを作成する場合

```java
Path tmpFile = Files.createTempFile("temp_", ".txt");
System.out.println(tmpFile.toAbsolutePath().toString());
// → C:\Users\rubytomato\AppData\Local\Temp\temp_2673655805075198029.txt
```

**NIOでは**

```java
File tmpFile = File.createTempFile("temp_", ".txt", "C:\\temp");
```

## ディレクトリ、ファイルの探索

**サンプル**

```text
C:\dir1
    |
    +--- \dir2
    |      |
    |      +--- \dir4
    |      |      |
    |      |      +--- file4.csv
    |      |      +--- file4.dat
    |      |
    |      +--- \dir5
    |      |      |
    |      |      +--- file5.csv
    |      |      +--- file5.txt
    |      |
    |      +--- file2.txt
    |
    +--- \dir3
    |      |
    |      +--- \dir6
    |      |      |
    |      |      +--- file6.txt
    |      |
    |      +--- \dir7
    |      |
    |      +--- file3.csv
    |
    +--- file1.txt
```

### Files#listを使った探索

引数で指定したディレクトリ下のディレクトリ、ファイルを探索します。サブディレクトリ以下の再帰的な探索は行いません。

```java
Path startDir = Paths.get("C:", "dir1");
Files.list(startDir).forEach(System.out::println);
// → C:\dir1\dir2
// → C:\dir1\dir3
// → C:\dir1\file1.txt
```

### Files#walkを使った探索

第1引数で指定したディレクトリ下を第2引数(optional)で指定した階層まで再帰的に探索します。
下記の例ではdir1ディレクトリから第2階層までのディレクトリ、ファイルを探索します。

```java
Path startDir = Paths.get("C:", "dir1");
Files.walk(startDir, 2).forEach(System.out::println);
// → C:\dir1
// → C:\dir1\dir2
// → C:\dir1\dir2\dir4
// → C:\dir1\dir2\dir5
// → C:\dir1\dir2\file2.txt
// → C:\dir1\dir3
// → C:\dir1\dir3\dir6
// → C:\dir1\dir3\dir7
// → C:\dir1\dir3\file3.csv
// → C:\dir1\file1.txt
```

### Files#findを使った探索

第3引数で指定する条件に一致するディレクトリ、ファイルを再帰的に探索します。
下記の例ではdir1ディレクトリから第3階層までの拡張子がcsvのファイルを探索します。

```java
Path startDir = Paths.get("C:", "dir1");

BiPredicate<Path, BasicFileAttributes> matcher = (path, attr) -> {
    if (attr.isRegularFile() && path.getFileName().toString().endsWith(".csv")) {
        return true;
    }
    return false;
};

Files.find(startDir, 3, matcher).forEach(System.out::println);
// → C:\dir1\dir2\dir4\file4.csv
// → C:\dir1\dir2\dir5\file5.csv
// → C:\dir1\dir3\file3.csv
```

### PathMatcherを使った探索

PathMathcerを使うと正規表現に似た記法(glob)で検索条件を記述することができます。
下記の例ではdir4ディレクトリ下の拡張子がcsvのファイルを探索します。

```java
FileSystem fs = FileSystems.getDefault();
PathMatcher matcher = fs.getPathMatcher("glob:**/dir4/*.csv");

Path startDir = Paths.get("C:", "dir1");
Files.walk(startDir).filter(matcher::matches).forEach(System.out::println);
// → C:\dir1\dir2\dir4\file4.csv
```

## ファイルのコピー

a.txtをb.txtへコピーします。

```java
Path source = Paths.get("path", "to", "a.txt");
Path target = Paths.get("path", "to", "b.txt");

Files.copy(srouce, target);
```

コピー先のb.txtファイルが存在している場合はFileAlreadyExistsExceptionがスローされます。

### ファイルのリプレイス

コピー先のファイルをリプレイスする場合は第3引数のコピーオプションにStandardCopyOption.REPLACE_EXISTINGを指定します。

```java
Path source = Paths.get("path", "to", "a.txt");
Path target = Paths.get("path", "to", "b.txt");

Files.copy(srouce, target, StandardCopyOption.REPLACE_EXISTING);
```

### 指定するディレクトリへコピーする

path/to/a.txtをpath/to/other/a.txtへコピーします。

```java
Path source = Paths.get("path", "to", "a.txt");
Path targetDir = Paths.get("path", "to", "other");

Files.copy(srouce, targetDir.resolve(source.getFileName()));
```


# [FileSystem] (https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/FileSystem.html) / [FileSystems] (https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/FileSystems.html)

FileSystemは抽象クラスです。下記に引用したJavaDocの説明の通り実行環境のファイルシステムを操作するための機能を提供します。
実装クラスはWindows環境(Java 1.8.0_144)では、`sun.nio.fs.WindowsFileSystem`です。確認していませんが、その他の実装クラスとしては`sun.nio.fs.LinuxFileSystem `や`sun.nio.fs.SolarisFileSystem`などがあるようです。

> ファイル・システムへのインタフェースを提供し、ファイル・システム内のファイルやその他のオブジェクトにアクセスするためのオブジェクトのファクトリです。

FileSystemのインスタンスはFileSystems#getDefaultで取得します。

```java
FileSystem fs = FileSystems.getDefault();
System.out.println(fs.getClass().getCanonicalName());
// → sun.nio.fs.WindowsFileSystem
```

**ZipFileSystem**

FileSystemの実装クラスにzipファイルをファイルシステムとして扱うZipFileSystemというクラスがあります。

```java
Path path = Paths.get("zip_demo.zip");

try (FileSystem zip = FileSystems.newFileSystem(path)) {
  System.out.println(zip.getClass().getCanonicalName());  
  // → jdk.nio.zipfs.ZipFileSystem

  Path root = zip.getPath("/");
  Files.walk(root).forEach(System.out::println);
}
```

Zipファイルシステムのプロパティー

* 参考: [モジュール jdk.zipfs] (https://docs.oracle.com/javase/jp/13/docs/api/jdk.zipfs/module-summary.html)

```java
Map<String, String> env = Map.of("create", "false",
                                 "encoding", "UTF-8");

FileSystems.newFileSystem(path, env);
```

**ルートディレクトリ**

実行環境のルートディレクトリはFileSystem#getRootDirectoriesで確認することができます。
下記の例の実行結果は私のPCのものになります。（光学ドライブもルートディレクトリとして識別されています。）

```java
FileSystem fs = FileSystems.getDefault();
fs.getRootDirectories().forEach(System.out::println);
// → C:\
// → D:\
// → E:\
```

## ファイル区切り文字

FileSystem#getSeparatorを使用します。

```java
FileSystem fs = FileSystems.getDefault();
System.out.println(fs.getSeparator());
// → \
```

# [FileSystemProvider] (https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/spi/FileSystemProvider.html)

FileSystemProviderは抽象クラスです。下記に引用するJavaDocの説明の通り、Filesクラスで行う操作はFileSystemProviderへ委譲されています。
実装クラスはWindows環境(Java 1.8.0_144)では、`sun.nio.fs.WindowsFileSystemProvider`です。

> ファイル・システムのサービス・プロバイダ・クラスです。Filesクラスによって定義されているメソッドは通常、このクラスのインスタンスに処理を委譲します。

```java
FileSystem fs = FileSystems.getDefault();
FileSystemProvider provider = fs.provider();
System.out.println(provider.getClass().getCanonicalName());
// → sun.nio.fs.WindowsFileSystemProvider
```

# 補足

## Options

* [StandardOpenOption] (https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/StandardOpenOption.html)
* [StandardCopyOption] (http://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)

## In memory File System

* [Jimfs] (https://github.com/google/jimfs)
 * An in-memory file system for Java 7+
* [Commons Virtual File System] (http://commons.apache.org/proper/commons-vfs/index.html)
 * ram : A filesystem which stores all the data in memory (one byte array for each file content).

## 一時ファイルの削除

* [Alternative to File.deleteOnExit() in Java NIO?] (https://stackoverflow.com/questions/28752006/alternative-to-file-deleteonexit-in-java-nio)

# その他のおさらいメモ

* [クラス java.util.Objectsのおさらいメモ] (https://qiita.com/rubytomato@github/items/ba38877ed5a00dd24f16)
    * 2017年08月25日
* [Java Collections Frameworkのおさらいメモ] (https://qiita.com/rubytomato@github/items/554095ae21a2c36f131a)
    * 2017年08月30日
* [パッケージ java.time.temporal のおさらいメモ] (https://qiita.com/rubytomato@github/items/e9325dd46aa11f1b8e2e)
    * 2018年01月30日
* [クラス java.util.Optionalのおさらいメモ] (https://qiita.com/rubytomato@github/items/92ac7944c830e54aa03d)
    * 2018年03月22日