パス操作とファイルシステム

docs

前回の記事では、Javaの入出力処理の基本として、InputStreamOutputStreamを使ったバイトベースの処理と、ReaderWriterを使った文字ベースの処理について学んだね。

テキストファイルの読み書きをマスターしよう! (BufferedReader/BufferedWriter編) | ToolDocs

今回は、それらの処理をより便利に、そして安全に行うためのパス操作ファイルシステムについて見ていこう。

Java 7で導入されたNIO.2(New Input/Output 2)では、java.nio.fileパッケージが登場して、パスやファイルシステムを扱うのが格段に楽になったんだ。これを使えば、ファイルの作成、削除、移動、コピーはもちろん、属性の取得なども直感的にできるようになるよ。

Pathでパスを表現する

まずはパスから。パスっていうのは、ファイルやディレクトリの場所を示す情報のこと。NIO.2ではPathインターフェースを使ってパスを表現するよ。

Pathのインスタンスは、Pathsクラスのgetメソッドを使って生成するのが一般的だよ。

Java

import java.nio.file.Path;
import java.nio.file.Paths;

public class PathExample {
    public static void main(String[] args) {
        // 絶対パスの取得
        Path absolutePath = Paths.get("/Users/yourname/documents/mytext.txt");
        System.out.println("絶対パス: " + absolutePath);

        // 相対パスの取得 (カレントディレクトリからの相対パス)
        Path relativePath = Paths.get("data", "settings.conf");
        System.out.println("相対パス: " + relativePath);

        // Windowsのパスも表現できる
        Path windowsPath = Paths.get("C:", "Program Files", "Java");
        System.out.println("Windowsパス: " + windowsPath);
    }
}

Paths.get()に文字列を渡すだけでPathオブジェクトが作れるんだ。複数の引数を渡すと、それらを結合してパスを生成してくれるから便利だね。

Pathには、パスに関する便利なメソッドがたくさん用意されているよ。

vscodeで実行した結果

コード スニペット

import java.nio.file.Path;
import java.nio.file.Paths;

public class PathInfo {
    public static void main(String[] args) {
        Path path = Paths.get("/Users/yourname/documents/mytext.txt");

        // ファイル名を取得
        System.out.println("ファイル名: " + path.getFileName());

        // 親ディレクトリのパスを取得
        System.out.println("親ディレクトリ: " + path.getParent());

        // ルートディレクトリを取得 (Windowsの場合はドライブレターも含む)
        System.out.println("ルートディレクトリ: " + path.getRoot());

        // 絶対パスかどうか
        System.out.println("絶対パスか: " + path.isAbsolute());

        // パスを正規化 (冗長な要素を取り除く)
        Path normalizedPath = Paths.get("/a/./b/../c").normalize();
        System.out.println("正規化されたパス: " + normalizedPath); // 出力: /a/c

        // 別のパスと結合
        Path basePath = Paths.get("/home/user");
        Path combinedPath = basePath.resolve("downloads/report.pdf");
        System.out.println("結合されたパス: " + combinedPath);

        // 別のパスからの相対パスを取得
        Path fromPath = Paths.get("/home/user/documents");
        Path toPath = Paths.get("/home/user/data/logs");
        Path relative = fromPath.relativize(toPath);
        System.out.println("相対パス: " + relative); // 出力: ../data/logs
    }
}

こんな感じで、ファイル名を抜き出したり、親ディレクトリを見つけたり、パスを結合したりと、パス操作に必要な機能がバッチリ揃っているんだ。

vscodeで実行した結果

Filesクラスでファイルシステムを操作する

Pathがパスを表現するためのものなら、実際にファイルやディレクトリを操作するのが**Filesクラス**だよ。Filesクラスは静的メソッドを提供していて、様々なファイルシステム操作ができるんだ。

ファイルの存在チェック

まずはファイルの存在チェックから。Files.exists()を使えば、指定したパスのファイルやディレクトリが存在するかどうかを確認できるよ。

Java

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileExistence {
    public static void main(String[] args) {
        Path file = Paths.get("test.txt");
        Path directory = Paths.get("my_folder");

        if (Files.exists(file)) {
            System.out.println("test.txtは存在します。");
        } else {
            System.out.println("test.txtは存在しません。");
        }

        if (Files.exists(directory)) {
            System.out.println("my_folderは存在します。");
        } else {
            System.out.println("my_folderは存在しません。");
        }
    }
}

vscodeで実行した結果

ファイルやディレクトリの作成

新しいファイルやディレクトリを作成するのも簡単だよ。Files.createFile()でファイル、Files.createDirectory()でディレクトリを作成できる。

Java

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class CreateFileDir {
    public static void main(String[] args) {
        Path newFile = Paths.get("new_document.txt");
        Path newDir = Paths.get("new_project");
        Path nestedDir = Paths.get("parent_folder/child_folder"); // 存在しない親ディレクトリがあるとエラーになる

        try {
            // ファイルの作成
            Files.createFile(newFile);
            System.out.println("ファイル 'new_document.txt' を作成しました。");

            // ディレクトリの作成
            Files.createDirectory(newDir);
            System.out.println("ディレクトリ 'new_project' を作成しました。");

            // 複数階層のディレクトリを一度に作成 (存在しない親ディレクトリも自動で作成)
            Files.createDirectories(nestedDir);
            System.out.println("ディレクトリ 'parent_folder/child_folder' を作成しました。");

        } catch (IOException e) {
            System.err.println("ファイルまたはディレクトリの作成中にエラーが発生しました: " + e.getMessage());
        }
    }
}

createDirectory()は、作成しようとしているディレクトリの親ディレクトリが存在しないとエラーになるから注意してね。そんな時は、親ディレクトリもまとめて作成してくれるFiles.createDirectories()を使うと便利だよ。

vscodeで実行した結果

ファイルやディレクトリのコピー、移動、削除

ファイルのコピー、移動、削除もFilesクラスで簡単に行えるよ。

Java

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class FileOperations {
    public static void main(String[] args) {
        Path originalFile = Paths.get("original.txt");
        Path copiedFile = Paths.get("copied.txt");
        Path movedFile = Paths.get("moved_document.txt");
        Path sourceDir = Paths.get("source_dir");
        Path targetDir = Paths.get("target_dir");

        try {
            // テスト用のファイルを作成
            Files.createFile(originalFile);
            Files.write(originalFile, "これは元のファイルです。".getBytes());
            Files.createDirectory(sourceDir);
            Files.createFile(sourceDir.resolve("file_in_source.txt"));

            // ファイルのコピー
            Files.copy(originalFile, copiedFile, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("ファイルをコピーしました: " + copiedFile);

            // ファイルの移動 (移動元はなくなる)
            Files.move(originalFile, movedFile, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("ファイルを移動しました: " + movedFile);

            // ディレクトリの移動 (ディレクトリ内のファイルも一緒に移動)
            Files.move(sourceDir, targetDir, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("ディレクトリを移動しました: " + targetDir);

            // ファイルの削除
            Files.delete(copiedFile);
            System.out.println("ファイルを削除しました: " + copiedFile);

            // ディレクトリの削除 (空のディレクトリのみ削除可能)
            Files.delete(targetDir); // targetDirが空じゃないとエラー
            System.out.println("ディレクトリを削除しました: " + targetDir);

            // 存在しないファイルを削除しようとするとエラー
            // Files.delete(Paths.get("non_existent.txt"));

        } catch (IOException e) {
            System.err.println("ファイル操作中にエラーが発生しました: " + e.getMessage());
        }
    }
}

Files.copy()Files.move()には、StandardCopyOptionというオプションを指定できるよ。例えばREPLACE_EXISTINGを指定すると、コピー先や移動先に同名のファイルがあっても上書きしてくれるんだ。

Files.delete()は、ファイルや空のディレクトリを削除できるけど、中身があるディレクトリは削除できないから注意してね。中身があるディレクトリを削除するには、再帰的に削除する処理を自分で書くか、Java 11から追加されたFiles.walk()と組み合わせて使う方法があるよ。

vscodeで実行した結果

ファイル属性の取得

ファイルのサイズや更新日時などの属性も取得できるよ。

Java

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;

public class FileAttributes {
    public static void main(String[] args) {
        Path file = Paths.get("sample.txt");

        try {
            Files.createFile(file); // テスト用にファイルを作成
            Files.write(file, "Hello, world!".getBytes()); // 内容を書き込む

            // ファイルサイズを取得
            long size = Files.size(file);
            System.out.println("ファイルサイズ: " + size + " バイト");

            // ファイルかどうか
            System.out.println("ファイルか: " + Files.isRegularFile(file));

            // ディレクトリかどうか
            System.out.println("ディレクトリか: " + Files.isDirectory(file));

            // 隠しファイルかどうか
            System.out.println("隠しファイルか: " + Files.isHidden(file));

            // 読み込み可能か
            System.out.println("読み込み可能か: " + Files.isReadable(file));

            // 書き込み可能か
            System.out.println("書き込み可能か: " + Files.isWritable(file));

            // 実行可能か
            System.out.println("実行可能か: " + Files.isExecutable(file));

            // 基本的なファイル属性を取得
            BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class);
            System.out.println("作成日時: " + attrs.creationTime());
            System.out.println("最終アクセス日時: " + attrs.lastAccessTime());
            System.out.println("最終更新日時: " + attrs.lastModifiedTime());
            System.out.println("ファイルのサイズ: " + attrs.size() + " バイト");

        } catch (IOException e) {
            System.err.println("ファイル属性の取得中にエラーが発生しました: " + e.getMessage());
        } finally {
            try {
                Files.deleteIfExists(file); // 後処理としてファイルを削除
            } catch (IOException e) {
                System.err.println("ファイルの削除中にエラーが発生しました: " + e.getMessage());
            }
        }
    }
}

ファイルのサイズや、それがファイルなのかディレクトリなのか、隠しファイルなのか、読み書きできるのかといった情報も簡単に取得できるんだ。

vscodeで実行した結果

ファイルの読み書きもFilesクラスで

前回の記事でInputStreamReaderを使ってファイルの読み書きをしたけど、Filesクラスを使えばもっと手軽に読み書きできるよ。短いテキストファイルなら、これを使うのがおすすめ。

Java

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.nio.charset.StandardCharsets;

public class FileReadWriteSimplified {
    public static void main(String[] args) {
        Path textFile = Paths.get("sample_text.txt");
        Path linesFile = Paths.get("multi_line.txt");

        // テキストをファイルに書き込む
        String content = "こんにちは、Javaの世界へようこそ!\nこれはテストの行です。";
        try {
            Files.write(textFile, content.getBytes(StandardCharsets.UTF_8));
            System.out.println("ファイルに書き込みました: " + textFile);

            // ファイルからすべてのバイトを読み込む
            byte[] readBytes = Files.readAllBytes(textFile);
            String readContent = new String(readBytes, StandardCharsets.UTF_8);
            System.out.println("ファイルから読み込んだ内容:\n" + readContent);

            // テキストファイルをまとめて行ごとに読み込む
            Files.write(linesFile, List.of("1行目", "2行目", "3行目"));
            List<String> lines = Files.readAllLines(linesFile, StandardCharsets.UTF_8);
            System.out.println("\n行ごとに読み込んだ内容:");
            for (String line : lines) {
                System.out.println("- " + line);
            }

        } catch (IOException e) {
            System.err.println("ファイルの読み書き中にエラーが発生しました: " + e.getMessage());
        } finally {
            try {
                Files.deleteIfExists(textFile);
                Files.deleteIfExists(linesFile);
            } catch (IOException e) {
                System.err.println("ファイルの削除中にエラーが発生しました: " + e.getMessage());
            }
        }
    }
}

Files.write()でバイト配列やIterable<String>List<String>など)をファイルに書き込めるし、Files.readAllBytes()でファイルの内容をバイト配列として、Files.readAllLines()でファイルの内容を行ごとのList<String>として一括で読み込めるんだ。短いファイルなら、これを使えば記述量がぐっと減らせるよ。

vscodeで実行した結果

まとめ

今回はJavaのパス操作ファイルシステムについて解説したよ。

  • Pathインターフェースは、ファイルやディレクトリのパスを表現するためのもの。
  • Paths.get()Pathインスタンスを生成し、Pathオブジェクトの様々なメソッドでパスに関する情報を取得したり操作したりできる。
  • Filesクラスは、ファイルやディレクトリの作成、削除、コピー、移動、属性の取得、簡単な読み書きなど、実際のファイルシステム操作を行うためのもの。
  • Files.exists()で存在チェック、Files.createFile()Files.createDirectory()Files.createDirectories()で作成、Files.copy()Files.move()Files.delete()で操作、Files.size()などで属性取得ができる。
  • Files.readAllBytes()Files.readAllLines()Files.write()を使えば、手軽にファイルの読み書きもできる。

これらを使いこなせば、Javaでファイルやディレクトリを扱うのがとっても楽になるはずだよ。例外処理を忘れずに、安全なファイル操作を心がけてね。


次回の記事も読んでみてね!

コメント

タイトルとURLをコピーしました