概要
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で指定する方法があります。
Path path = Paths.get("C:\\dir\\hoge.txt");
Paths#getは内部でFileSystem#getPathを実行しています。下記のコードは上記のコードと同じ結果になります。
Path path = FileSystems.getDefault().getPath("C:\\dir\\hoge.txt");
ディレクトリ、ファイルを区切って指定することもできます。この場合、ファイルの区切り文字は必要がありません。
Path path = Paths.get("C:", "dir", "hoge.txt");
System.out.println(path.toString());
// → C:\dir1\hoge.txt
Fileとの相互互換
FileからPath
File#toPathを使用します。
File file = new File("hoge.txt");
Path path = file.toPath();
Path から File
Path#toFileを使用します。
Path path = Paths.get("hoge.txt");
File file = path.toFile();
絶対パス、相対パス
実行環境に依存しますがWindows環境ではパスがドライブレターから始まる場合、絶対パスとして解釈されます。
パスが絶対パスかどうかはPath#isAbsoluteで確認できます。
絶対パス
Path path = Paths.get("C:", "dir1", "dir2", "hoge.txt");
System.out.println(path.isAbsolute());
// → true
相対パス
Path path = Paths.get("dir1", "dir2", "hoge.txt");
System.out.println(path.isAbsolute());
// → false
相対パスで表現するディレクトリやファイルを作成する場合user.dir
システムプロパティで指定するディレクトリからの相対位置に作成されます。
下記の例はIDEから実行したときの結果です。(sampleはプロジェクト名です)
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を使用すると冗長な表現を解消することができます。
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を使用します。
Path dir = Paths.get("C:", "dir1");
Files.createDirectory(dir);
NIOでは
File dir = new File("C:\\dir1");
dir.mkdir();
ファイルの作成
Files#createFileを使用します。
Path file = Paths.get("C:", "dir1", "hoge.txt");
Files.createFile(file);
NIOでは
File file = new File("C:\\dir1\\hoge.txt");
file.createNewFile();
ファイルサイズの取得
Files#sizeを使用します。単位はバイトです。
Path file = Paths.get("C:", "dir1", "hoge.txt");
Long size = Files.size(file);
ネストしたディレクトリを再帰的に作成
ネストしたディレクトリを再帰的に作成したい場合はFiles#createDirectoriesを使用します。
Path path = Paths.get("C:", "dir1", "dir2", "dir3");
Files.createDirectories(path);
なお、上記の例のディレクトリをFiles#createDirectoryで作成しようとするとNoSuchFileExceptionがスローされます。
ディレクトリ、ファイルの存在チェック
Files#exists、Files#nonExistsを使用します。
Path path = Paths("C:", "dir1", "hoge.txt");
if (Files.exists(path)) {
// ファイルが存在する場合の処理
}
Path path = Paths("C:", "dir1", "fuga.txt");
if (Files.nonExists(path)) {
// ファイルが存在しない場合の処理
}
存在しないディレクトリ、ファイルの操作
Pathで指定するディレクトリ、ファイルが実際に存在しない場合の実行結果は下記のようになりました。
isXXX系メソッド
// 存在しないファイル
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を使用します。
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を使用します。
指定するディレクトリ内にテンポラリディレクトリを作成する場合
Path root = Paths.get("C:", "temp");
Path tempDir = Files.createTempDirectory(root, "tempDir_");
System.out.println(tempDir.toString());
// → C:\temp\tempDir_1219973832281475457
デフォルトのテンポラリディレクトリ内にテンポラリディレクトリを作成する場合
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を使用します。
指定するディレクトリ内にテンポラリファイルを作成する場合
Path root = Paths.get("C:", "temp");
Path tmpFile = Files.createTempFile(root, "temp_", ".txt");
System.out.println(tmpFile.toAbsolutePath().toString());
// → C:\temp\temp_4672863899169116557.txt
デフォルトのテンポラリディレクトリ内にテンポラリファイルを作成する場合
Path tmpFile = Files.createTempFile("temp_", ".txt");
System.out.println(tmpFile.toAbsolutePath().toString());
// → C:\Users\rubytomato\AppData\Local\Temp\temp_2673655805075198029.txt
NIOでは
File tmpFile = File.createTempFile("temp_", ".txt", "C:\\temp");
ディレクトリ、ファイルの探索
サンプル
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を使った探索
引数で指定したディレクトリ下のディレクトリ、ファイルを探索します。サブディレクトリ以下の再帰的な探索は行いません。
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階層までのディレクトリ、ファイルを探索します。
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のファイルを探索します。
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のファイルを探索します。
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へコピーします。
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を指定します。
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へコピーします。
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で取得します。
FileSystem fs = FileSystems.getDefault();
System.out.println(fs.getClass().getCanonicalName());
// → sun.nio.fs.WindowsFileSystem
ZipFileSystem
FileSystemの実装クラスにzipファイルをファイルシステムとして扱うZipFileSystemというクラスがあります。
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)
Map<String, String> env = Map.of("create", "false",
"encoding", "UTF-8");
FileSystems.newFileSystem(path, env);
ルートディレクトリ
実行環境のルートディレクトリはFileSystem#getRootDirectoriesで確認することができます。
下記の例の実行結果は私のPCのものになります。(光学ドライブもルートディレクトリとして識別されています。)
FileSystem fs = FileSystems.getDefault();
fs.getRootDirectories().forEach(System.out::println);
// → C:\
// → D:\
// → E:\
ファイル区切り文字
FileSystem#getSeparatorを使用します。
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クラスによって定義されているメソッドは通常、このクラスのインスタンスに処理を委譲します。
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日