在具有多个目录的目录中搜索文件

8

这是我的目标。我想能够向一个方法传递父目录和文件名,以便在该目录及其所有子目录中搜索特定的文件。以下是我一直在使用但无法完全实现目标的代码。它可以找到我指定的文件,但不会返回任何内容。

private static File findFile(File dir, String name) {
    String file     = "";
    File[] dirlist  = dir.listFiles();

    search:
        for(int i = 0; i < dirlist.length; i++) {
            if(dirlist[i].isDirectory()) {
                findFile(dirlist[i], name);
            } else if(dirlist[i].getName().matches(name)) {
                file = dirlist[i].toString();
                break search;
            }
        }

    return new File(file);
}

我知道当该方法找到一个目录并调用自身时,它会重置存储找到文件的file变量。这就是为什么我得到了空白返回值的原因。我不确定如何实现这个目标,或者是否可能。


顺便提一下:在这里,你可以/应该使用常规的“break”而不是“break label”。(正如@chssPly76所指出的那样,最好还是使用普通的返回。) - Stephen C
2个回答

7
问题在于你没有从递归调用中返回任何内容:
if(dirlist[i].isDirectory()) {
    findFile(dirlist[i], name); // <-- here
} else if(dirlist[i].getName().matches(name)) {

我会按照以下步骤进行:

我会按照以下步骤进行:

private static File findFile(File dir, String name) {
  File result = null; // no need to store result as String, you're returning File anyway
  File[] dirlist  = dir.listFiles();

  for(int i = 0; i < dirlist.length; i++) { 
    if(dirlist[i].isDirectory()) {
      result = findFile(dirlist[i], name);
      if (result!=null) break; // recursive call found the file; terminate the loop
    } else if(dirlist[i].getName().matches(name)) {
      return dirlist[i]; // found the file; return it
    }
  }
  return result; // will return null if we didn't find anything
}

如果目录中不存在该文件,则此方法无法正常工作,会引发异常。 - Erfan

1
事实上,有很多解决方案可以完成这项工作。我假设你想要找到目录树中与文件名匹配的唯一文件(或第一个文件)。这是一个优化问题,因为有多种探索解决方案的方式,我们希望找到一个可接受的解决方案。 1- 使用FileUtils.listFiles的解决方案
public static File searchFileWithFileUtils(final File file, final String fileName) {
    File target = null;
    if(file.isDirectory()) {
        Collection<File> files = FileUtils.listFiles(file, null, true);
        for (File currFile : files) {
            if (currFile.isFile() && currFile.getName().equals(fileName)) {
                target = currFile;
                break;
            }
        }
    }
    return target;
}

使用FileUtils库的解决方案并不适合,因为方法FileUtils#listFiles()会加载整个目录/文件夹树(代价很高!)。 我们不需要知道整个树,可以选择更好的算法,在找到文件时停止搜索。 2- 递归解决方案
public static File searchFileRecursive(final File file, final String search) {
    if (file.isDirectory()) {
        File[] files = file.listFiles();
        for (File f : files) {
            File target = searchFileRecursive(f, search);
            if(target != null) {
                return target;
            }
        }
    } else {
        if (search.equals(file.getName())) {
            return file;
        }
    }
    return null;
}

该算法测试文件是否存在于任何文件夹中。如果不存在,则递归地尝试当前文件夹的子文件夹。如果在当前分支中未找到文件,则尝试另一个子文件夹。
探索是深入的,对于深度为1的任何文件,算法将探索先前子文件夹的全部内容(之前的分支完全被探索!)。该算法在第一分支内深处的文件具有最佳性能。
在大多数情况下,文件位置不深,因此让我们探索另一种在大多数情况下工作的算法。
3- 最快解决方案:按深度进行探索
public static File searchFileByDeepness(final String directoryName, final String fileName) {
    File target = null;
    if(directoryName != null && fileName != null) {
        File directory = new File(directoryName);
        if(directory.isDirectory()) {
            File file = new File(directoryName, fileName);
            if(file.isFile()) {
                target = file;
            }
            else {
                List<File> subDirectories = getSubDirectories(directory);
                do {
                    List<File> subSubDirectories = new ArrayList<File>();
                    for(File subDirectory : subDirectories) {
                        File fileInSubDirectory = new File(subDirectory, fileName);
                        if(fileInSubDirectory.isFile()) {
                            return fileInSubDirectory;
                        }
                        subSubDirectories.addAll(getSubDirectories(subDirectory));
                    }
                    subDirectories = subSubDirectories;
                } while(subDirectories != null && ! subDirectories.isEmpty());
            }
        }
    }
    return target;
}

private static List<File> getSubDirectories(final File directory) {
    File[] subDirectories = directory.listFiles(new FilenameFilter() {
        @Override
        public boolean accept(final File current, final String name) {
            return new File(current, name).isDirectory();
        }
    });
    return Arrays.asList(subDirectories);
}

对于每个深度,该算法在同一级别的所有文件夹中搜索文件。如果未找到文件,则尝试下一个深度(深度 ++)。由于并行探索(对称性),这种解决方案在大多数情况下都是合适的。
比较:
public class FileLocationFinder {

    public static void main(final String[] args) {
        String rootFolder = args[0];
        String fileName = args[1];

        long start = System.currentTimeMillis();
        File target = searchFileWithFileUtils(new File(rootFolder), fileName);
        System.out.println(target.getAbsolutePath());
        System.out.println("Duration: " + (System.currentTimeMillis() - start) + "ms");

        start = System.currentTimeMillis();
        target = searchFileRecursive(new File(rootFolder), fileName);
        System.out.println(target.getAbsolutePath());
        System.out.println("Duration: " + (System.currentTimeMillis() - start) + "ms");

        start = System.currentTimeMillis();
        target = searchFileByDeepness(rootFolder, fileName);
        System.out.println(target.getAbsolutePath());
        System.out.println("Duration: " + (System.currentTimeMillis() - start) + "ms");
    }


    // Solution with FileUtils#listFiles
    //--------------------------------------------

    public static File searchFileWithFileUtils(final File file, final String fileName) {
        File target = null;
        if(file.isDirectory()) {
            Collection<File> files = FileUtils.listFiles(file, null, true);
            for (File currFile : files) {
                if (currFile.isFile() && currFile.getName().equals(fileName)) {
                    target = currFile;
                    break;
                }
            }
        }
        return target;
    }


    // Recursive solution
    //--------------------------------------------

    public static File searchFileRecursive(final File file, final String search) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                File target = searchFileRecursive(f, search);
                if(target != null) {
                    return target;
                }
            }
        } else {
            if (search.equals(file.getName())) {
                return file;
            }
        }
        return null;
    }


    // Fastest solution
    //--------------------------------------------

    public static File searchFileByDeepness(final String directoryName, final String fileName) {
        File target = null;
        if(directoryName != null && fileName != null) {
            File directory = new File(directoryName);
            if(directory.isDirectory()) {
                File file = new File(directoryName, fileName);
                if(file.isFile()) {
                    target = file;
                }
                else {
                    List<File> subDirectories = getSubDirectories(directory);
                    do {
                        List<File> subSubDirectories = new ArrayList<File>();
                        for(File subDirectory : subDirectories) {
                            File fileInSubDirectory = new File(subDirectory, fileName);
                            if(fileInSubDirectory.isFile()) {
                                return fileInSubDirectory;
                            }
                            subSubDirectories.addAll(getSubDirectories(subDirectory));
                        }
                        subDirectories = subSubDirectories;
                    } while(subDirectories != null && ! subDirectories.isEmpty());
                }
            }
        }
        return target;
    }

    private static List<File> getSubDirectories(final File directory) {
        File[] subDirectories = directory.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(final File current, final String name) {
                return new File(current, name).isDirectory();
            }
        });
        return Arrays.asList(subDirectories);
    }
}

结果:

使用FileUtils搜索文件: 20186毫秒 | 递归搜索文件: 1134毫秒 | 按深度搜索文件: 16毫秒


[编辑] 你也可以使用Java 8的文件API来完成这个任务:

public static File searchFileJava8(final String rootFolder, final String fileName) {
    File target = null;
    Path root = Paths.get(rootFolder);
    try (Stream<Path> stream = Files.find(root, Integer.MAX_VALUE, (path, attr) ->
            path.getFileName().toString().equals(fileName))) {
        Optional<Path> path = stream.findFirst();
        if(path.isPresent()) {
            target = path.get().toFile();
        }
    }
    catch (IOException e) {
    }
    return target;
}

但执行时间并没有变得更好(994毫秒)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接