介绍

文件处理是大多数应用程序的核心功能之一,也是任何编程语言中的基础知识点。

为了操作文件,我们首先需要知道它们的位置。因此,能够概览目录中的文件结构至关重要,尤其是当我们希望通过迭代对这些文件执行操作时。在 Java 中,有多种方法可以实现这一目标,本文将逐一介绍。

为了便于理解,本文所有示例均基于以下文件树结构:

Programming
|-- minimax.c
|-- super_hack.py
|-- TODO.txt
`-- CodingMusic
    |-- Girl Talk - All Day.mp3
    |-- Celldweller - Frozen.mp3
    |-- Lim Taylor - Isn't It Wonderful.mp3
    `-- Radiohead - Everything in Its Right Place.mp3

File.list()

在不遍历子目录的情况下,列出给定目录中文件和文件夹名称的最简单方法是使用 File 类的辅助方法 .list()。该方法返回一个 String 数组。

我们可以通过在 File 实例上调用 .list() 方法来执行此操作:

public class Pathnames {

    public static void main(String[] args) {
        // 创建一个数组用于存储文件和目录的名称
        String[] pathnames;

        // 通过将给定的路径名字符串转换为抽象路径名,创建一个新的 File 实例
        File f = new File("D:/Programming");

        // 用文件和目录的名称填充数组
        pathnames = f.list();

        // 遍历 pathnames 数组中的每个路径名
        for (String pathname : pathnames) {
            // 打印文件和目录的名称
            System.out.println(pathname);
        }
    }
}

使用简单的 foreach 循环,我们遍历数组并打印出 String 数组中的内容。

CodingMusic
minimax.c
super_hack.py
TODO.txt

使用这种方法时,CodingMusic 目录中的所有子项都不会显示出来。这种方法的局限性在于我们只能获取文件名,无法直接对文件对象本身进行操作。当我们仅需查看文件表面信息(如文件名)时,这种方法非常有用。

FilenameFilter

利用 .list() 方法,我们还可以创建一个 FilenameFilter 来仅返回符合特定条件的文件:

File f = new File("D:/Programming");

// 此过滤器仅包含以 .py 结尾的文件
FilenameFilter filter = new FilenameFilter() {
        @Override
        public boolean accept(File f, String name) {
            return name.endsWith(".py");
        }
    };

// 应用过滤器
pathnames = f.list(filter);

运行这段代码将产生以下输出:

super_hack.py

File.listFiles()

与前一种方法类似,该方法也可用于返回文件和目录的名称。不同的是,这次我们将它们作为 File 对象数组获得,这使我们能够直接操作文件对象本身:

public class Pathnames {
    public static void main(String args[]) {

        // 使用 try-catch 块处理异常
        try {
            File f = new File("D:/Programming");

            FilenameFilter filter = new FilenameFilter() {
                @Override
                public boolean accept(File f, String name) {
                    // 我们只想查找 .c 文件
                    return name.endsWith(".c");
                }
            };

            // 注意这次我们使用的是 File 类数组,而不是 String
            File[] files = f.listFiles(filter);

            // 使用 .getName() 方法获取文件名
            for (int i = 0; i < files.length; i++) {
                System.out.println(files[i].getName());
            }
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
}

输出:

minimax.c

现在,让我们使用递归遍历文件系统,并在 File 对象上使用更多方法(如获取文件大小):

public class ListFilesRecursively {
    public void listFiles(String startDir) {
        File dir = new File(startDir);
        File[] files = dir.listFiles();

        if (files != null && files.length > 0) {
            for (File file : files) {
                // 检查文件是否为目录
                if (file.isDirectory()) {
                    // 不打印目录名,仅将其作为新的起点继续列出文件
                    listFiles(file.getAbsolutePath());
                } else {
                    // 我们可以使用 .length() 获取文件大小
                    System.out.println(file.getName() + " (size in bytes: " + file.length() + ")");
                }
            }
        }
    }

    public static void main(String[] args) {
        ListFilesRecursively test = new ListFilesRecursively();
        String startDir = "D:/Programming";
        test.listFiles(startDir);
    }
}

输出:

Girl Talk - All Day.mp3 (size in bytes: 8017524)
Celldweller - Frozen.mp3 (size in bytes: 12651325)
Lim Taylor - Isn't It Wonderful.mp3 (size in bytes: 6352489)
Radiohead - Everything in Its Right Place.mp3 (size in bytes: 170876098)
minimax.c (size in bytes: 20662)
super_hack.py (size in bytes: 114401)
TODO.txt (size in bytes: 998)

Files.walk()

在 Java 8 及更高版本中,我们可以使用 java.nio.file.Files 类填充一个 Stream(流),并利用它遍历文件和目录,同时递归访问所有子目录。

请注意,在此示例中我们将使用 Lambda 表达式

public class FilesWalk {
    public static void main(String[] args) {
        try (Stream<Path> walk = Files.walk(Paths.get("D:/Programming"))) {
            // 我们只想查找普通文件
            List<String> result = walk.filter(Files::isRegularFile)
                    .map(x -> x.toString()).collect(Collectors.toList());

            result.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这里,我们使用 .walk() 方法填充了一个 Stream,并传递了一个 Paths 参数。Paths 类通过静态方法返回基于 URI 字符串的 Path 对象——使用 Path,我们可以更便捷地定位文件。

PathPathsFiles 等类属于 java.nio 包,这是 Java 7 中引入的用于表示文件系统的更现代方式(NIO.2)。

随后,我们使用 Collections Framework 生成一个列表。

运行这段代码将产生:

D:\Programming\Coding Music\Radiohead - Everything in Its Right Place.mp3
D:\Programming\Coding Music\Lim Taylor - Isn't It Wonderful.mp3
D:\Programming\Coding Music\Celldweller - Frozen.mp3
D:\Programming\Coding Music\Girl Talk - All Day.mp3
D:\Programming\minimax.c
D:\Programming\super_hack.py
D:\Programming\TODO.txt

结论

以某种方式处理文件是大多数编程语言的核心任务,这包括在文件系统中列出和查找文件的能力。为了操作文件,我们需要知道它们的位置。如果要完成此任务,那么对目录中的文件进行概述是至关重要的,特别是如果我们可以通过迭代对其执行操作的话。

在本文中,我们展示了使用单层遍历、递归方法以及 Java 8 Stream API 在 Java 中列出文件系统上文件的多种不同方法。

说明

  1. File 类属于早期 Java IO API,而 FilesPath 属于 Java 7 引入的 NIO.2 API。
  2. Files.walk() 方法需要 Java 8 或更高版本。
  3. 示例中的路径格式(如 D:/Programming)适用于 Windows 系统,在 Linux 或 macOS 上需调整为相应路径格式。