Java:nioのPath、Filesその他

2019年1月22日

これも頭が悪くて覚えきれないためチートシートを作成しておく。ネットにはいくらでも解説記事があるのだが、説明はそれらに任せることにして、ここでは簡単な使い方だけに専念する。

基本的なやり方

Pathの生成

  Path path = Paths.get("sample"); // 相対パス
  Path path = Paths.get("c:\\users\\admin\\desktop"); // 絶対パス、Windowsの場合
  Path path = Paths.get("c:/users/admin/desktop"); // これでもよい
  Path path = Paths.get("/users/admin/desktop"); // これでもよい。カレントドライブがCの場合

FileとPathの相互変換

  File file = path.toFile();
  Path path = file.toPath();

Pathから別のPathを作成

  /* 親ディレクトリを取得。ただし、先の("sample")ような相対パスに適用するとnullが返る。
   * つまり、実際に存在するかどうかは無関係で、単純に字面上の仮想的なパスを返す。
   */
  path.getParent(); 

  Path child = path.resolve("foobar"); // 既存のパスの下にあるファイルを表す。

  String fileName = path.getFileName().toString(); // パス末端のファイル名称を取得する

Pathの種類のチェック

  path.isAbsolute(); // 絶対パスか
  Files.exists(path); // 存在するか
  Files.isDirectory(path); // ディレクトリか
  Files.isRegularFile(path); // 通常ファイルか
  Files.isWritable(path); // 書き込み可能か

ディレクトリの作成

  Files.createDirectory(path); // 単一のディレクトリを作成する
  Files.createDirectories(path); // そこに至るまでの途中のディレクトリが無ければそれも作成する

コピー

  Files.copy(path1, path2); // path1からpath2へコピー
  Files.copy(inputStream, path); // inputStreamをpathへコピー
  Files.copy(path, outputStream); // path内容をoutputStreamへコピー

読み込み

  bytes[]bytes = Files.readAllBytes(path); // バイナリファイルの内容をbyte配列に
  // ファイルをテキストファイルとして読み込み文字列リストに。
  // エンコーディングはUTF-8でOSデフォルトではない
  List<String>lines = Files.readAllLines(path); 
  List<String>lines = Files.readAllLines(path, StandardCharsets.UTF_8); // 明示的なUTF-8指定
  List<String>lines = Files.readAllLines(path, Charset.forName("MS932")); // シフトJISで読み込み

書き込み

  Files.write(path, bytes); // バイト配列を書き込み
  Files.write(path, lines); // 行リストを書き込み、UTF-8であって、OSデフォルトではない。
  Files.write(path, lines, StandardCharsets.UTF_8); // 行リストを書き込み、明示的なUTF-8指定
  Files.write(path, lines, Charset.forName("MS932")); // 行リストを書き込み、シフトJIS指定

なぜか改行コードを指定する方法は無いようだ。つまり、Windowsの場合は常に”\r\n”になる。

もしUnix的な”\n”にしたい場合は、そのような文字列を作成してバイナリとして書き込む他に無いと思われる。

  String a = "A\nB\nC";
  Files.write(path, a.getBytes("UTF-8"));

ファイルのサイズ

long size = Files.size(path);

削除

  Files.delete(path);

ファイルのリスト

指定ディレクトリ直下のファイルを取得する。

  Files.list(path).forEach(System.out::println);

指定ディレクトリも含め、その下のすべてのファイルを取得する。

  Files.walk(path).forEach(System.out::println);

この他にwalkFileTreeというメソッドがあり、様々な条件を指定できるようだ。

指定されたファイルあるいはディレクトリを完全に削除するコード、その1

指定されたファイルなり、ディレクトリなりを有無を言わさず完全に削除する。

StackOverflowか何かにあったコードをアレンジしてみた。

  /**
   * 指定されたファイルまたはディレクトリを削除する。
   * ディレクトリの場合は、それ以下もすべて削除される。
   */
  public static void delete(Path target) throws IOException {
    if (!Files.exists(target)) return;
    if (Files.isRegularFile(target)) {
      deleteOne(target);
      return;
    }
    Files.walkFileTree(target, new SimpleFileVisitor<Path>() {
      @Override
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        deleteOne(file);
        return FileVisitResult.CONTINUE;
      }
      @Override
      public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        deleteOne(dir);
        return FileVisitResult.CONTINUE;
      }
   });
  }

  /** 単一のファイルもしくはディレクトリを削除。
      ディレクトリの場合は空であること。readonlyになっていても削除する。 */
  private static void deleteOne(Path path) throws IOException {    
    if (!Files.isWritable(path)) {
      // nio的ではないが、面倒なのでこちらを使う。
      path.toFile().setWritable(true);
    }
    Files.delete(path);
  }

指定されたファイルあるいはディレクトリを完全に削除するコード、その2

もっと簡単な方法があった。

  void delete(Path doc) throws IOException {
    if (Files.exists(doc)) Files.walk(doc).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
  }

ファイルあるいはディレクトリを再帰的にコピーするコード

LambdaExceptionUtilの最後に掲載した改良バージョンを使用している。

  static void duplicate(Path src, Path dst) throws IOException {
    if (!Files.exists(src)) return;  
    Files.walk(src).forEach(rethrowC(s -> {
      Path d = dst.resolve(src.relativize(s));
      Files.copy(s, d);
      //d.toFile().setLastModified(s.toFile().lastModified()); タイムスタンプもコピーしたいとき
    }));
  }