LoginSignup
32
36

More than 5 years have passed since last update.

NIOでフォルダを再帰処理する方法メモ

Last updated at Posted at 2014-03-29

環境

OS

Windows7 64bit

Java

1.7.0_51

基本

フォルダ階層
nio
│  file1.txt
│
└─dir1
    │  file2.txt
    │
    └─dir2
            file3.txt
MyFileVisitor.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

import org.apache.commons.lang3.text.StrBuilder;

public class MyFileVisitor implements FileVisitor<Path> {
    protected int indentSize;

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        print("preVisitDirectory : " + dir.getFileName());
        this.indentSize++;
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        print("visitFile : " + file.getFileName());
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        this.indentSize--;
        print("postVisitDirectory : " + dir.getFileName());
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        String error = String.format(" [exception=%s, message=%s]", exc.getClass(), exc.getMessage());

        print("visitFileFailed : " + file.getFileName() + error);

        return FileVisitResult.CONTINUE;
    }

    protected void print(String message) {
        System.out.println(new StrBuilder().appendPadding(this.indentSize, ' ').append(message));
    }
}
Main.java
package sample.nio;

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

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new MyFileVisitor();

        Files.walkFileTree(start, visitor);
    }
}
実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
   visitFile : file3.txt
  postVisitDirectory : dir2
  visitFile : file2.txt
 postVisitDirectory : dir1
 visitFile : file1.txt
postVisitDirectory : nio
  • Files.walkFileTree(Path, FileVisitor) で指定したフォルダ以下を再帰的に処理できる。
  • 各フォルダの検索前後で preVisitDirectory()postVisitDirectory() がコールバックされる。
  • 各ファイルごとに visitFile() がコールバックされる。

フォルダのリンクがあった場合の処理

フォルダ階層
│  file1.txt
│
├─dir1
│  │  file2.txt
│  │
│  └─dir2
│          file3.txt
│
├─junction_dir2
│      file3.txt
│
└─symbolic_dir2
       file3.txt
実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
   visitFile : file3.txt
  postVisitDirectory : dir2
  visitFile : file2.txt
 postVisitDirectory : dir1
 visitFile : file1.txt
 preVisitDirectory : junction_dir2
  visitFile : file3.txt
 postVisitDirectory : junction_dir2
 visitFile : symbolic_dir2
postVisitDirectory : nio
  • junction_dir2dir1\dir2 のジャンクション、 symbolic_dir2 がシンボリックリンクになっている。
  • ジャンクションは再帰対象になるが、シンボリックリンクは対象にならない。

シンボリックリンクのフォルダも再帰対象にする

フォルダ階層
│  file1.txt
│
├─dir1
│  │  file2.txt
│  │
│  └─dir2
│          file3.txt
│
├─junction_dir2
│      file3.txt
│
└─symbolic_dir2
       file3.txt
Main.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new MyFileVisitor();

        Set<FileVisitOption> options = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        int maxDepth = Integer.MAX_VALUE;

        Files.walkFileTree(start, options, maxDepth, visitor);
    }
}
実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
   visitFile : file3.txt
  postVisitDirectory : dir2
  visitFile : file2.txt
 postVisitDirectory : dir1
 visitFile : file1.txt
 preVisitDirectory : junction_dir2
  visitFile : file3.txt
 postVisitDirectory : junction_dir2
 preVisitDirectory : symbolic_dir2
  visitFile : file3.txt
 postVisitDirectory : symbolic_dir2
postVisitDirectory : nio
  • walkFileTree(Path, Set<FileVisitOption>, int, FileVisitor) メソッドを使い、 Set<FileVisitOption>FileVisitOption.FOLLOW_LINKS を渡すと、シンボリックリンクのフォルダも再帰処理の対象になる。

再帰の深さを指定する

フォルダ階層
└─dir1
    └─dir2
        └─dir3
Main.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        Set<FileVisitOption> options = EnumSet.allOf(FileVisitOption.class);

        int maxDepth = 2; // 深さ 2 を指定
        FileVisitor<Path> visitor = new MyFileVisitor();

        Files.walkFileTree(start, options, maxDepth, visitor);
    }
}
実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  visitFile : dir2
 postVisitDirectory : dir1
postVisitDirectory : nio
  • maxDepth で再帰の深さを指定できる。
  • maxDepth で指定した深さにあるフォルダは、 visitFile() で処理される。

リンクのフォルダを辿った結果、ループが検出された場合の処理

フォルダ階層
│  file1.txt
│
└─dir1
    │  file2.txt
    │
    └─dir2
        │  file3.txt
        │
        └─link_dir1
実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
   visitFile : file3.txt
   visitFileFailed : link_dir1 [exception=class java.nio.file.FileSystemLoopException, message=nio\dir1\dir2\link_dir1]
  postVisitDirectory : dir2
  visitFile : file2.txt
 postVisitDirectory : dir1
 visitFile : file1.txt
postVisitDirectory : nio
  • link_dir1 が、 dir1 のジャンクションになっており、再帰処理をすると無限ループが発生するようになっている。
  • 再帰処理でループが検出された場合、 visitFileFailed() がコールバックされる。
  • このとき、 FileSystemLoopExceptionvisitFileFailed() メソッドに渡される。

任意のタイミングで再帰検索を中断する

フォルダ階層
└─dir1
   ├─dir2
   └─terminate
TerminateFileVisitor.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public class TerminateFileVisitor extends MyFileVisitor {

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        print("preVisitDirectory : " + dir.getFileName());
        this.indentSize++;

        if ("terminate".equals(dir.getFileName().toString())) {
            System.out.println("TERMINATE!!");
            return FileVisitResult.TERMINATE;
        } else {
            return FileVisitResult.CONTINUE;
        }
    }
}
Main.java
package sample.nio;

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

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new TerminateFileVisitor();

        Files.walkFileTree(start, visitor);
    }
}
実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
  postVisitDirectory : dir2
  preVisitDirectory : terminate
TERMINATE!!
  • FileVisitor のメソッドが FileVisitResult.TERMINATE を返すと、その時点で再帰処理は中断される。

特定のフォルダだけ再帰処理をしないようにする

フォルダ階層
├─dir1
│  └─dir2
└─skip
    └─dir3
SkipSubtreeFileVisitor.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public class SkipSubtreeFileVisitor extends MyFileVisitor {

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        print("preVisitDirectory : " + dir.getFileName());
        this.indentSize++;

        if ("skip".equals(dir.getFileName().toString())) {
            System.out.println("SKIP!!");
            this.indentSize--;
            return FileVisitResult.SKIP_SUBTREE;
        } else {
            return FileVisitResult.CONTINUE;
        }
    }
}
Main.java
package sample.nio;

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

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new SkipSubtreeFileVisitor();

        Files.walkFileTree(start, visitor);
    }
}
実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
  postVisitDirectory : dir2
 postVisitDirectory : dir1
 preVisitDirectory : skip
SKIP!!
postVisitDirectory : nio
  • preVisitDirectory() メソッドが FileVisitResult.SKIP_SUBTREE を返した場合、そのフォルダはスキップされる(postVisitDirectory() メソッドも呼ばれない)。

任意のタイミングで、特定のフォルダ以下の処理だけを中断させる

フォルダ階層
├─dir1
│  │  bbb_skip.txt
│  │
│  ├─aaa
│  └─ccc
└─dir2
SkipSiblingsFileVisitor.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public class SkipSiblingsFileVisitor extends MyFileVisitor {

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        print("visitFile : " + file.getFileName());

        if (file.getFileName().toString().contains("skip")) {
            System.out.println("SKIP!!");
            return FileVisitResult.SKIP_SIBLINGS;
        } else {
            return FileVisitResult.CONTINUE;
        }
    }
}
Main.java
package sample.nio;

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

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new SkipSiblingsFileVisitor();

        Files.walkFileTree(start, visitor);
    }
}
実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : aaa
  postVisitDirectory : aaa
  visitFile : bbb_skip.txt
SKIP!!
 postVisitDirectory : dir1
 preVisitDirectory : dir2
 postVisitDirectory : dir2
postVisitDirectory : nio
  • FileVisitResult.SKIP_SIBLINGS を返すと、現在処理中のフォルダに存在する未処理のファイルとフォルダは全て処理をスキップする。

SimpleFileVisitor を使って必要な処理だけ実装する

SimpleFileVisitorFileVisitor インターフェースの実装クラスで、 FileVisitor の各メソッドのデフォルト実装を提供している。

FileVisitor の全てのメソッドを実装する必要がない場合、 SimpleFileVisitor を継承して必要なメソッドだけオーバーライドする。

package sample.nio;

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

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");

        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println(file.getFileName());
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

参考

32
36
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
36