Рекурсивный список файлов в Java
Как мне рекурсивно перечислить все файлы в каталоге в Java? Предоставляет ли фреймворк какую-либо полезность?
Я видел много хакерских реализаций. Но ни один из рамок или нио
30 ответов
Java 8 обеспечивает хороший поток для обработки всех файлов в дереве.
Files.walk(Paths.get(path))
.filter(Files::isRegularFile)
.forEach(System.out::println);
Это обеспечивает естественный способ обхода файлов. Поскольку это поток, вы можете выполнять все приятные потоковые операции с результатом, такие как ограничение, группировка, отображение, ранний выход и т. Д.
ОБНОВЛЕНИЕ: Я мог бы указать, что есть также Files.find, который принимает BiPredicate, который может быть более эффективным, если вам нужно проверить атрибуты файла.
Files.find(Paths.get(path),
Integer.MAX_VALUE,
(filePath, fileAttr) -> fileAttr.isRegularFile())
.forEach(System.out::println);
Обратите внимание, что, хотя JavaDoc не допускает, что этот метод может быть более эффективным, чем Files.walk, он фактически идентичен, можно наблюдать разницу в производительности, если вы также извлекаете атрибуты файла в своем фильтре. В конце концов, если вам нужно отфильтровать атрибуты, используйте Files.find, в противном случае используйте Files.walk, в основном из-за перегрузок, и это более удобно.
ИСПЫТАНИЯ: В соответствии с просьбой я дал сравнение производительности многих ответов. Проверьте проект Github, который содержит результаты и контрольный пример.
FileUtils есть iterateFiles
а также listFiles
методы. Дай им попробовать. (из общего достояния)
Изменить: вы можете проверить здесь для сравнения различных подходов. Кажется, что подход commons-io медленный, поэтому выберите некоторые из более быстрых отсюда (если это имеет значение)
// Готов к запуску
import java.io.File;
public class Filewalker {
public void walk( String path ) {
File root = new File( path );
File[] list = root.listFiles();
if (list == null) return;
for ( File f : list ) {
if ( f.isDirectory() ) {
walk( f.getAbsolutePath() );
System.out.println( "Dir:" + f.getAbsoluteFile() );
}
else {
System.out.println( "File:" + f.getAbsoluteFile() );
}
}
}
public static void main(String[] args) {
Filewalker fw = new Filewalker();
fw.walk("c:\\" );
}
}
Java 7 будет иметь Files.walkFileTree:
Если вы укажете начальную точку и посетителя файла, он будет вызывать различные методы для посетителя файла, когда он просматривает файл в дереве файлов. Мы ожидаем, что люди будут использовать это, если они разрабатывают рекурсивную копию, рекурсивное перемещение, рекурсивное удаление или рекурсивную операцию, которая устанавливает разрешения или выполняет другую операцию для каждого из файлов.
В настоящее время есть целый учебник Oracle по этому вопросу.
Внешние библиотеки не нужны.
Возвращает коллекцию, чтобы вы могли делать с ней все, что захотите после звонка.
public static Collection<File> listFileTree(File dir) {
Set<File> fileTree = new HashSet<File>();
if(dir==null||dir.listFiles()==null){
return fileTree;
}
for (File entry : dir.listFiles()) {
if (entry.isFile()) fileTree.add(entry);
else fileTree.addAll(listFileTree(entry));
}
return fileTree;
}
Я хотел бы пойти с чем-то вроде:
public void list(File file) {
System.out.println(file.getName());
File[] children = file.listFiles();
for (File child : children) {
list(child);
}
}
System.out.println просто указывает, что нужно что-то делать с файлом. нет необходимости различать файлы и каталоги, так как обычный файл просто не имеет дочерних элементов.
Я предпочитаю использовать очередь, а не рекурсию для такого простого обхода:
List<File> allFiles = new ArrayList<File>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File("/start/dir/"));
while (!dirs.isEmpty()) {
for (File f : dirs.poll().listFiles()) {
if (f.isDirectory()) {
dirs.add(f);
} else if (f.isFile()) {
allFiles.add(f);
}
}
}
Просто напишите это самостоятельно, используя простую рекурсию:
public List<File> addFiles(List<File> files, File dir)
{
if (files == null)
files = new LinkedList<File>();
if (!dir.isDirectory())
{
files.add(dir);
return files;
}
for (File file : dir.listFiles())
addFiles(files, file);
return files;
}
Этот код готов к запуску
public static void main(String... args) {
File[] files = new File("D:/").listFiles();
if (files != null)
getFiles(files);
}
public static void getFiles(File[] files) {
for (File file : files) {
if (file.isDirectory()) {
getFiles(file.listFiles());
} else {
System.out.println("File: " + file);
}
}
}
С Java 7 вы можете использовать следующий класс:
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 MyFileIterator extends SimpleFileVisitor<Path>
{
public MyFileIterator(String path) throws Exception
{
Files.walkFileTree(Paths.get(path), this);
}
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attributes) throws IOException
{
System.out.println("File: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attributes) throws IOException
{
System.out.println("Dir: " + dir);
return FileVisitResult.CONTINUE;
}
}
Я думаю, что это должно сделать работу:
File dir = new File(dirname);
String[] files = dir.list();
Таким образом, у вас есть файлы и каталоги. Теперь используйте рекурсию и сделайте то же самое для dirs (File
класс имеет isDirectory()
метод).
В Java 8 теперь мы можем использовать утилиту Files для обхода файлового дерева. Очень просто.
Files.walk(root.toPath())
.filter(path -> !Files.isDirectory(path))
.forEach(path -> System.out.println(path));
Помимо рекурсивного обхода можно использовать подход, основанный на посетителях.
В приведенном ниже коде используется подход, основанный на посетителях, для обхода. Ожидается, что вход в программу является корневым каталогом для прохождения.
public interface Visitor {
void visit(DirElement d);
void visit(FileElement f);
}
public abstract class Element {
protected File rootPath;
abstract void accept(Visitor v);
@Override
public String toString() {
return rootPath.getAbsolutePath();
}
}
public class FileElement extends Element {
FileElement(final String path) {
rootPath = new File(path);
}
@Override
void accept(final Visitor v) {
v.visit(this);
}
}
public class DirElement extends Element implements Iterable<Element> {
private final List<Element> elemList;
DirElement(final String path) {
elemList = new ArrayList<Element>();
rootPath = new File(path);
for (File f : rootPath.listFiles()) {
if (f.isDirectory()) {
elemList.add(new DirElement(f.getAbsolutePath()));
} else if (f.isFile()) {
elemList.add(new FileElement(f.getAbsolutePath()));
}
}
}
@Override
void accept(final Visitor v) {
v.visit(this);
}
public Iterator<Element> iterator() {
return elemList.iterator();
}
}
public class ElementWalker {
private final String rootDir;
ElementWalker(final String dir) {
rootDir = dir;
}
private void traverse() {
Element d = new DirElement(rootDir);
d.accept(new Walker());
}
public static void main(final String[] args) {
ElementWalker t = new ElementWalker("C:\\temp");
t.traverse();
}
private class Walker implements Visitor {
public void visit(final DirElement d) {
System.out.println(d);
for(Element e:d) {
e.accept(this);
}
}
public void visit(final FileElement f) {
System.out.println(f);
}
}
}
Вы можете использовать приведенный ниже код для рекурсивного получения списка файлов определенной папки или каталога.
public static void main(String args[]) {
recusiveList("D:");
}
public static void recursiveList(String path) {
File f = new File(path);
File[] fl = f.listFiles();
for (int i = 0; i < fl.length; i++) {
if (fl[i].isDirectory() && !fl[i].isHidden()) {
System.out.println(fl[i].getAbsolutePath());
recusiveList(fl[i].getAbsolutePath());
} else {
System.out.println(fl[i].getName());
}
}
}
Перечисляет все файлы с предоставленными расширениями, с возможностью сканирования вложенных папок (рекурсивно)
public static ArrayList<File> listFileTree(File dir,boolean recursive) {
if (null == dir || !dir.isDirectory()) {
return new ArrayList<>();
}
final Set<File> fileTree = new HashSet<File>();
FileFilter fileFilter = new FileFilter() {
private final String[] acceptedExtensions = new String[]{"jpg", "png", "webp", "jpeg"};
@Override
public boolean accept(File file) {
if (file.isDirectory()) {
return true;
}
for (String extension : acceptedExtensions) {
if (file.getName().toLowerCase().endsWith(extension)) {
return true;
}
}
return false;
}
};
File[] listed = dir.listFiles(fileFilter);
if(listed!=null){
for (File entry : listed) {
if (entry.isFile()) {
fileTree.add(entry);
} else if(recursive){
fileTree.addAll(listFileTree(entry,true));
}
}
}
return new ArrayList<>(fileTree);
}
List<Path> filePaths = Files
.find(Paths.get(dir), Integer.MAX_VALUE, (filePath, fileAttr) -> fileAttr.isRegularFile() || fileAttr.isDirectory())
.collect(Collectors.toList());
filePaths
будет иметь файлы и список папок, которые можно повторить и продолжить.
Я придумал это для рекурсивной печати всех файлов / имен файлов.
private static void printAllFiles(String filePath,File folder) {
if(filePath==null) {
return;
}
File[] files = folder.listFiles();
for(File element : files) {
if(element.isDirectory()) {
printAllFiles(filePath,element);
} else {
System.out.println(" FileName "+ element.getName());
}
}
}
Общепринятый ответ велик, однако он ломается, когда вы хотите сделать IO внутри лямбда.
Вот что вы можете сделать, если ваше действие объявляет исключения IOExceptions.
Вы можете рассматривать отфильтрованный поток как Iterable
, а затем выполните свое действие в обычном цикле for-each. Таким образом, вам не нужно обрабатывать исключения внутри лямбда-выражения.
try (Stream<Path> pathStream = Files.walk(Paths.get(path))
.filter(Files::isRegularFile)) {
for (Path file : (Iterable<Path>) pathStream::iterator) {
// something that throws IOException
Files.copy(file, System.out);
}
}
Нашел этот трюк здесь: /questions/3817566/java-8-kak-mne-rabotat-s-metodami-isklyucheniya-v-potokah/3817585#3817585
Принятый ответ плохой, потому что это может привести к утечке ресурсов.
Files.walk поддерживается DirectoryStreams.
Возвращенный поток инкапсулирует один или несколько DirectoryStreams. Если требуется своевременное удаление ресурсов файловой системы, следует использовать конструкцию try-with-resources, чтобы гарантировать, что метод закрытия потока будет вызываться после завершения операций потока. Работа с закрытым потоком приведет к исключению IllegalStateException.
DirectoryStream должен быть закрыт, как указано в его javadoc:
DirectoryStream открывается при создании и закрывается путем вызова метода close. Закрытие потока каталога освобождает все ресурсы, связанные с потоком. Неспособность закрыть поток может привести к утечке ресурсов. Оператор try-with-resources предоставляет полезную конструкцию, гарантирующую, что поток закрыт:
Path dir = ...
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry: stream) {
...
}
}
В результате правильный ответ:
try (Stream<Path> stream = Files.walk(Paths.get(path))) {
// Do something with the stream.
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
Другой способ можно сделать, даже если кто-то уже предоставил прогулку по Java 8.
Это рекурсивно предоставит вам все файлы
private Stream<File> files(File file) {
return file.isDirectory()
? Arrays.stream(file.listFiles()).flatMap(this::files)
: Stream.of(file);
}
private void fillFilesRecursively(File file, List<File> resultFiles) {
if (file.isFile()) {
resultFiles.add(file);
} else {
for (File child : file.listFiles()) {
fillFilesRecursively(child, resultFiles);
}
}
}
Нерекурсивная BFS с одним списком (конкретный пример - поиск файлов *.eml):
final FileFilter filter = new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory() || file.getName().endsWith(".eml");
}
};
// BFS recursive search
List<File> queue = new LinkedList<File>();
queue.addAll(Arrays.asList(dir.listFiles(filter)));
for (ListIterator<File> itr = queue.listIterator(); itr.hasNext();) {
File file = itr.next();
if (file.isDirectory()) {
itr.remove();
for (File f: file.listFiles(filter)) itr.add(f);
}
}
public static String getExten(String path) {
int i = path.lastIndexOf('.');
if (i > 0) {
return path.substring(i);
}
else return "";
}
public static List<String> GetAllFiles(String path, List<String>fileList){
File file = new File(path);
File[] files = file.listFiles();
for(File folder:files) {
if(extensions.contains(getExten(folder.getPath()))) {
fileList.add(folder.getPath());
}
}
File[] direcs = file.listFiles(File::isDirectory);
for(File dir:direcs) {
GetAllFiles(dir.getPath(),fileList);
}
return fileList;
}
Это простая рекурсивная функция, которая должна предоставить вам все файлы. extension - это список строк, содержащий только принятые расширения. Пример extension = [".txt",". Docx"] и т. Д.
Здесь простое, но отлично работающее решение с использованием recursion
:
public static List<Path> listFiles(String rootDirectory)
{
List<Path> files = new ArrayList<>();
listFiles(rootDirectory, files);
return files;
}
private static void listFiles(String path, List<Path> collectedFiles)
{
File root = new File(path);
File[] files = root.listFiles();
if (files == null)
{
return;
}
for (File file : files)
{
if (file.isDirectory())
{
listFiles(file.getAbsolutePath(), collectedFiles);
} else
{
collectedFiles.add(file.toPath());
}
}
}
Моя версия (конечно, я мог бы использовать встроенную прогулку в Java 8;-)):
public static List<File> findFilesIn(File rootDir, Predicate<File> predicate) {
ArrayList<File> collected = new ArrayList<>();
walk(rootDir, predicate, collected);
return collected;
}
private static void walk(File dir, Predicate<File> filterFunction, List<File> collected) {
Stream.of(listOnlyWhenDirectory(dir))
.forEach(file -> walk(file, filterFunction, addAndReturn(collected, file, filterFunction)));
}
private static File[] listOnlyWhenDirectory(File dir) {
return dir.isDirectory() ? dir.listFiles() : new File[]{};
}
private static List<File> addAndReturn(List<File> files, File toAdd, Predicate<File> filterFunction) {
if (filterFunction.test(toAdd)) {
files.add(toAdd);
}
return files;
}
Котлин имеет FileTreeWalk
для этого. Например:
dataDir.walkTopDown().filter { !it.isDirectory }.joinToString("\n") {
"${it.toRelativeString(dataDir)}: ${it.length()}"
}
Создает текстовый список всех файлов, не являющихся каталогами, в заданном корне, по одному файлу в строке с путем относительно корня и длиной.
На основе ответа @Michael добавьте проверку, возвращает ли listFiles значение null
static Stream<File> files(File file) {
return file.isDirectory()
? Optional.ofNullable(file.listFiles()).map(Stream::of).orElseGet(Stream::empty).flatMap(MainActivity::files)
: Stream.of(file);
}
или используйте https://github.com/aNNiMON/Lightweight-Stream-API, который поддерживает Android5 и Android6
static Stream<File> files(File f) {
return f.isDirectory() ? Stream.ofNullable(f.listFiles()).flatMap(MainActivity::files) : Stream.of(f);
}
Пример выводит файлы *.csv в подкаталоги рекурсивного поиска в каталоге, используя Files.find() из java.nio:
String path = "C:/Daten/ibiss/ferret/";
logger.debug("Path:" + path);
try (Stream<Path> fileList = Files.find(Paths.get(path), Integer.MAX_VALUE,
(filePath, fileAttr) -> fileAttr.isRegularFile() && filePath.toString().endsWith("csv"))) {
List<String> someThingNew = fileList.sorted().map(String::valueOf).collect(Collectors.toList());
for (String t : someThingNew) {
t.toString();
logger.debug("Filename:" + t);
}
}
После публикации этого примера у меня возникли проблемы с пониманием того, как передать параметр имени файла в примере №1, заданном Брайаном, с использованием foreach для Stream-result -
Надеюсь это поможет.
На основании ответа укладчика. Вот решение, работающее в JSP без каких-либо внешних библиотек, поэтому вы можете разместить его практически в любом месте на вашем сервере:
<!DOCTYPE html>
<%@ page session="false" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<%!
public List<String> files = new ArrayList<String>();
/**
Fills files array with all sub-files.
*/
public void walk( File root ) {
File[] list = root.listFiles();
if (list == null) return;
for ( File f : list ) {
if ( f.isDirectory() ) {
walk( f );
}
else {
files.add(f.getAbsolutePath());
}
}
}
%>
<%
files.clear();
File jsp = new File(request.getRealPath(request.getServletPath()));
File dir = jsp.getParentFile();
walk(dir);
String prefixPath = dir.getAbsolutePath() + "/";
%>
Тогда вы просто делаете что-то вроде:
<ul>
<% for (String file : files) { %>
<% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
<li><%=file.replace(prefixPath, "")%></li>
<% } %>
<% } %>
</ul>
import java.io.File;
public class Main {
public static void main(String[] args) {
loopFiles(new File("C:\\Users\\serge.klimkovitch\\Documents"));
}
private static void loopFiles(File element) {
if (element.isDirectory()) {
for (File currentFile : element.listFiles()) {
loopFiles(currentFile);
System.out.println(currentFile);
}
}
}
}