Java でファイルやディレクトリのリスティングをする際に再帰処理なんかを使うと、対象が多すぎる場合、FileSystemException
がスローされて、Too many open files in system
なんてメッセージを貰うことがある。
これの原因はそもそも、OS レベルの制約によるもので、Linux でプロセスが開けるファイルディスクリプタの上限に達した場合に発生するエラーが、JVM に伝播されて例外としてスローされるものになります。
Java 1.7 以降では、これを回避可能になっていたので、方法をメモ。
ただし、以下コードサンプルは Java 1.8 によるものなので、1.7 を使用する場合は適宜読み替えてください。
環境
- Mac OS X 10.10.3 Yosemite
- Java 1.8.0_45
$ mvn --version
Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T20:57:37+09:00)
Maven home: /usr/local/Cellar/maven/3.3.3/libexec
Java version: 1.8.0_45, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.10.3", arch: "x86_64", family: "mac"
例外の発生するコード
まずは、ファイルディスクリプタの上限に達してしまうコード。
昔ながらの再帰処理によるリスティングです。
package com.yo1000.egg.filedescriptor;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class FileRecursion {
public List<String> scan(Path directory) throws IOException {
List<String> items = new ArrayList<>();
Files.list(directory).forEach(path -> {
items.add(path.toString());
if (!path.toFile().isDirectory()) {
return;
}
try {
items.addAll(scan(path));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
return items;
}
public static void main(String[] args) throws IOException {
new FileRecursion().scan(Paths.get("../../")).forEach(s -> System.out.println(s));
}
}
作業ディレクトリの2つ上の階層配下を、リスティングしています。
大抵の場合、ファイルが多すぎて、FileSystemException
がスローされます。
解決したコード
解決編。
Java 1.7 で、JSR 203 (NIO.2 API) として追加された、java.nio.file.FileVisitor
を使用したリスティングです。
package com.yo1000.egg.filedescriptor;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
public class FileWalker {
public List<String> scan(Path directory) throws IOException {
List<String> items = new ArrayList<>();
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
items.add(dir.toString());
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
items.add(file.toString());
return super.visitFile(file, attrs);
}
});
return items;
}
public static void main(String[] args) throws IOException {
new FileWalker().scan(Paths.get("../../")).forEach(s -> System.out.println(s));
}
}
実行後しばらく待機していると、今度は例外をスローせず、無事に結果が返ってきました。
Too many open files in system
が発生して、どうしても手詰まりなときには、NIO.2 API を使おう。
参考
今回使用したコード。
https://github.com/yo1000/egg.filedescriptor.git
詳しくはこのへんを参考に。
https://docs.oracle.com/javase/tutorial/essential/io/walk.html
http://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-SO12-Architect-ponge.pdf