1. 概要

この記事では、Javaプラットフォームの新しいI / O API – NIO2 –に焦点を当てて、基本的なファイル操作を実行します。

NIO2のファイルAPIは、Java 7に同梱されたJavaプラットフォームの主要な新しい機能領域の1つであり、特にパスAPIと並んで新しいファイルシステムAPIのサブセットです。

2. 設定

ファイルAPIを使用するようにプロジェクトを設定するには、次のインポートを行うだけです。

import java.nio.file.*;

この記事のコードサンプルはおそらくさまざまな環境で実行されるため、ユーザーのホームディレクトリのハンドルを取得しましょう。これは、すべてのオペレーティングシステムで有効です。

private static String HOME = System.getProperty("user.home");

Files クラスは、java.nio.fileパッケージの主要なエントリポイントの1つです。 このクラスは、ファイルとディレクトリの読み取り、書き込み、および操作のための豊富なAPIセットを提供します。 Files クラスのメソッドは、Pathオブジェクトのインスタンスで機能します。

3. ファイルまたはディレクトリの確認

ファイルまたはファイルシステム上のディレクトリを表すPathインスタンスを持つことができます。 それが指しているファイルまたはディレクトリが存在するかどうか、アクセス可能かどうかは、ファイル操作によって確認できます。

簡単にするために、 file という用語を使用する場合は、特に明記されていない限り、ファイルとディレクトリの両方を指します。

ファイルが存在するかどうかを確認するには、 examplesAPIを使用します。

@Test
public void givenExistentPath_whenConfirmsFileExists_thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.exists(p));
}

ファイルが存在しないことを確認するには、 notExistsAPIを使用します。

@Test
public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() {
    Path p = Paths.get(HOME + "/inexistent_file.txt");

    assertTrue(Files.notExists(p));
}

また、ファイルが myfile.txt のような通常のファイルであるか、単なるディレクトリであるかを確認することもできます。 isRegularFileAPIを使用します。

@Test
public void givenDirPath_whenConfirmsNotRegularFile_thenCorrect() {
    Path p = Paths.get(HOME);

    assertFalse(Files.isRegularFile(p));
}

ファイルのパーミッションをチェックする静的な方法もあります。 ファイルが読み取り可能かどうかを確認するには、 isReadableAPIを使用します。

@Test
public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.isReadable(p));
}

書き込み可能かどうかを確認するには、 isWritableAPIを使用します。

@Test
public void givenExistentDirPath_whenConfirmsWritable_thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.isWritable(p));
}

同様に、実行可能かどうかを確認するには、次のようにします。

@Test
public void givenExistentDirPath_whenConfirmsExecutable_thenCorrect() {
    Path p = Paths.get(HOME);
    assertTrue(Files.isExecutable(p));
}

2つのパスがある場合、それらが両方とも基盤となるファイルシステム上の同じファイルを指しているかどうかを確認できます。

@Test
public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() {
    Path p1 = Paths.get(HOME);
    Path p2 = Paths.get(HOME);

    assertTrue(Files.isSameFile(p1, p2));
}

4. ファイルの作成

ファイルシステムAPIは、ファイルを作成するための1行の操作を提供します。 通常のファイルを作成するには、 createFile APIを使用して、作成するファイルを表すPathオブジェクトを渡します。

パス内のすべての名前要素は、ファイル名を除いて存在する必要があります。存在しない場合、 IOException:が発生します。

@Test
public void givenFilePath_whenCreatesNewFile_thenCorrect() {
    String fileName = "myfile_" + UUID.randomUUID().toString() + ".txt";
    Path p = Paths.get(HOME + "/" + fileName);
    assertFalse(Files.exists(p));

    Files.createFile(p);

    assertTrue(Files.exists(p));
}

上記のテストでは、最初にパスをチェックすると、パスは存在しません。次に、 createFile 操作の後で、パスが存在することがわかります。

ディレクトリを作成するには、 createDirectoryAPIを使用します。

@Test
public void givenDirPath_whenCreatesNewDir_thenCorrect() {
    String dirName = "myDir_" + UUID.randomUUID().toString();
    Path p = Paths.get(HOME + "/" + dirName);
    assertFalse(Files.exists(p));

    Files.createDirectory(p);

    assertTrue(Files.exists(p));
    assertFalse(Files.isRegularFile(p));
    assertTrue(Files.isDirectory(p));
}

この操作では、パス内のすべての名前要素が存在する必要があります。存在しない場合は、IOExceptionも発生します。

@Test(expected = NoSuchFileException.class)
public void givenDirPath_whenFailsToCreateRecursively_thenCorrect() {
    String dirName = "myDir_" + UUID.randomUUID().toString() + "/subdir";
    Path p = Paths.get(HOME + "/" + dirName);
    assertFalse(Files.exists(p));

    Files.createDirectory(p);
}

ただし、1回の呼び出しでディレクトリの階層を作成する場合は、createDirectoriesメソッドを使用します。 前の操作とは異なり、パスで名前要素が欠落している場合は、 IOException をスローせず、最後の要素に至るまで再帰的に作成します。

@Test
public void givenDirPath_whenCreatesRecursively_thenCorrect() {
    Path dir = Paths.get(
      HOME + "/myDir_" + UUID.randomUUID().toString());
    Path subdir = dir.resolve("subdir");
    assertFalse(Files.exists(dir));
    assertFalse(Files.exists(subdir));

    Files.createDirectories(subdir);

    assertTrue(Files.exists(dir));
    assertTrue(Files.exists(subdir));
}

5. 一時ファイルの作成

多くのアプリケーションは、実行時にファイルシステムに一時ファイルの軌跡を作成します。 その結果、ほとんどのファイルシステムには、そのようなアプリケーションによって生成された一時ファイルを格納するための専用ディレクトリがあります。

新しいファイルシステムAPIは、この目的のための特定の操作を提供します。 createTempFileAPIがこの操作を実行します。 パスオブジェクト、ファイルプレフィックス、およびファイルサフィックスを取ります。

@Test
public void givenFilePath_whenCreatesTempFile_thenCorrect() {
    String prefix = "log_";
    String suffix = ".txt";
    Path p = Paths.get(HOME + "/");

    Files.createTempFile(p, prefix, suffix);
        
    assertTrue(Files.exists(p));
}

これらのパラメーターは、この操作を必要とする要件には十分です。 ただし、ファイルの特定の属性を指定する必要がある場合は、4番目の変数argumentsパラメーターがあります。

上記のテストでは、 HOME ディレクトリに一時ファイルを作成し、指定されたプレフィックス文字列とサフィックス文字列をそれぞれプリペンディングおよびアペンディングします。 最終的にはlog_8821081429012075286.txtのようなファイル名になります。 長い数値文字列はシステムによって生成されます。

ただし、プレフィックスとサフィックスを指定しない場合、ファイル名には長い数値文字列とデフォルトの.tmp拡張子のみが含まれます。

@Test
public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() {
    Path p = Paths.get(HOME + "/");

    Files.createTempFile(p, null, null);
        
    assertTrue(Files.exists(p));
}

上記の操作により、8600179353689423985.tmpのような名前のファイルが作成されます。

最後に、パス、プレフィックス、サフィックスのいずれも指定しない場合、操作は全体を通してデフォルトを使用します。 作成されたファイルのデフォルトの場所は、ファイルシステムが提供する一時ファイルディレクトリになります。

@Test
public void givenNoFilePath_whenCreatesTempFileInTempDir_thenCorrect() {
    Path p = Files.createTempFile(null, null);

    assertTrue(Files.exists(p));
}

Windowsでは、これはデフォルトで C:\ Users \ user \ AppData \ Local \ Temp \6100927974988978748.tmpのようになります。

上記のすべての操作は、createTempFileの代わりにcreateTempDirectoryを使用することにより、通常のファイルではなくディレクトリを作成するように適合させることができます。

6. ファイルの削除

ファイルを削除するには、 deleteAPIを使用します。 わかりやすくするために、次のテストでは、最初にファイルがまだ存在しないことを確認し、次にファイルを作成して現在存在することを確認し、最後にファイルを削除して、ファイルがもう存在しないことを確認します。

@Test
public void givenPath_whenDeletes_thenCorrect() {
    Path p = Paths.get(HOME + "/fileToDelete.txt");
    assertFalse(Files.exists(p));
    Files.createFile(p);
    assertTrue(Files.exists(p));

    Files.delete(p);

    assertFalse(Files.exists(p));
}

ただし、ファイルがファイルシステムに存在しない場合、削除操作はIOExceptionで失敗します。

@Test(expected = NoSuchFileException.class)
public void givenInexistentFile_whenDeleteFails_thenCorrect() {
    Path p = Paths.get(HOME + "/inexistentFile.txt");
    assertFalse(Files.exists(p));

    Files.delete(p);
}

deleteIfExists を使用すると、このシナリオを回避できます。このシナリオは、ファイルが存在しない場合にサイレントに失敗します。 これは、複数のスレッドがこの操作を実行していて、スレッドが失敗した現在のスレッドよりも早く操作を実行したという理由だけで失敗メッセージが表示されないようにする場合に重要です。

@Test
public void givenInexistentFile_whenDeleteIfExistsWorks_thenCorrect() {
    Path p = Paths.get(HOME + "/inexistentFile.txt");
    assertFalse(Files.exists(p));

    Files.deleteIfExists(p);
}

通常のファイルではなくディレクトリを処理する場合、デフォルトでは削除操作が再帰的に機能しないことを覚えておく必要があります。 したがって、ディレクトリが空でない場合、IOExceptionで失敗します。

@Test(expected = DirectoryNotEmptyException.class)
public void givenPath_whenFailsToDeleteNonEmptyDir_thenCorrect() {
    Path dir = Paths.get(
      HOME + "/emptyDir" + UUID.randomUUID().toString());
    Files.createDirectory(dir);
    assertTrue(Files.exists(dir));

    Path file = dir.resolve("file.txt");
    Files.createFile(file);

    Files.delete(dir);

    assertTrue(Files.exists(dir));
}

7. ファイルのコピー

copy APIを使用して、ファイルまたはディレクトリをコピーできます。

@Test
public void givenFilePath_whenCopiesToNewLocation_thenCorrect() {
    Path dir1 = Paths.get(
      HOME + "/firstdir_" + UUID.randomUUID().toString());
    Path dir2 = Paths.get(
      HOME + "/otherdir_" + UUID.randomUUID().toString());

    Files.createDirectory(dir1);
    Files.createDirectory(dir2);

    Path file1 = dir1.resolve("filetocopy.txt");
    Path file2 = dir2.resolve("filetocopy.txt");

    Files.createFile(file1);

    assertTrue(Files.exists(file1));
    assertFalse(Files.exists(file2));

    Files.copy(file1, file2);

    assertTrue(Files.exists(file2));
}

REPLACE_EXISTING オプションが指定されていない限り、ターゲットファイルが存在する場合、コピーは失敗します。

@Test(expected = FileAlreadyExistsException.class)
public void givenPath_whenCopyFailsDueToExistingFile_thenCorrect() {
    Path dir1 = Paths.get(
      HOME + "/firstdir_" + UUID.randomUUID().toString());
    Path dir2 = Paths.get(
      HOME + "/otherdir_" + UUID.randomUUID().toString());

    Files.createDirectory(dir1);
    Files.createDirectory(dir2);

    Path file1 = dir1.resolve("filetocopy.txt");
    Path file2 = dir2.resolve("filetocopy.txt");

    Files.createFile(file1);
    Files.createFile(file2);

    assertTrue(Files.exists(file1));
    assertTrue(Files.exists(file2));

    Files.copy(file1, file2);

    Files.copy(file1, file2, StandardCopyOption.REPLACE_EXISTING);
}

ただし、ディレクトリをコピーする場合、内容は再帰的にコピーされません。 つまり、 /baeldung/articles.dbファイルと/authors.dbファイルが含まれている場合、 /baeldungを新しい場所にコピーすると空のディレクトリを作成します。

8. ファイルの移動

move APIを使用して、ファイルまたはディレクトリを移動できます。 これは、ほとんどの点でcopy操作に似ています。 コピー操作がGUIベースのシステムでのコピーアンドペースト操作に類似している場合、moveカットアンドペースト操作に類似しています。

@Test
public void givenFilePath_whenMovesToNewLocation_thenCorrect() {
    Path dir1 = Paths.get(
      HOME + "/firstdir_" + UUID.randomUUID().toString());
    Path dir2 = Paths.get(
      HOME + "/otherdir_" + UUID.randomUUID().toString());

    Files.createDirectory(dir1);
    Files.createDirectory(dir2);

    Path file1 = dir1.resolve("filetocopy.txt");
    Path file2 = dir2.resolve("filetocopy.txt");
    Files.createFile(file1);

    assertTrue(Files.exists(file1));
    assertFalse(Files.exists(file2));

    Files.move(file1, file2);

    assertTrue(Files.exists(file2));
    assertFalse(Files.exists(file1));
}

copy 操作の場合と同様に、 REPLACE_EXISTING オプションが指定されていない限り、ターゲットファイルが存在する場合、move操作は失敗します。

@Test(expected = FileAlreadyExistsException.class)
public void givenFilePath_whenMoveFailsDueToExistingFile_thenCorrect() {
    Path dir1 = Paths.get(
      HOME + "/firstdir_" + UUID.randomUUID().toString());
    Path dir2 = Paths.get(
      HOME + "/otherdir_" + UUID.randomUUID().toString());

    Files.createDirectory(dir1);
    Files.createDirectory(dir2);

    Path file1 = dir1.resolve("filetocopy.txt");
    Path file2 = dir2.resolve("filetocopy.txt");

    Files.createFile(file1);
    Files.createFile(file2);

    assertTrue(Files.exists(file1));
    assertTrue(Files.exists(file2));

    Files.move(file1, file2);

    Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);

    assertTrue(Files.exists(file2));
    assertFalse(Files.exists(file1));
}

9. 結論

この記事では、Java 7の一部として出荷された新しいファイルシステムAPI(NIO2)のファイルAPIについて学び、重要なファイル操作のほとんどが実際に動作していることを確認しました。

この記事で使用されているコードサンプルは、記事のGithubプロジェクトにあります。