Skip to content

Files

Latest commit

c4e46db · Oct 12, 2021

History

History
1077 lines (707 loc) · 48.5 KB

File metadata and controls

1077 lines (707 loc) · 48.5 KB

五、管理文件系统

在本章中,我们将介绍以下内容:

  • 获取文件存储信息
  • 获取文件系统信息
  • 使用 SimpleFileVisitor 类遍历文件系统
  • 使用 SimpleFileVisitor 类删除目录
  • 使用 SimpleFileVisitor 类复制目录
  • 使用 DirectoryStream 接口处理目录内容,如使用 globbing 方法过滤目录中所述
  • 编写自己的目录过滤器
  • 使用 WatchEvents 监视文件事件
  • 了解 ZIP 文件系统提供程序

导言

文件系统是一个或多个包含文件层次结构的顶级根目录。文件系统由文件存储支持,文件存储是文件存储的提供者。本章涉及获取有关这些实体和典型文件系统任务的信息,如确定目录内容或监视文件系统事件。

文件存储表示一个存储单元。例如,它可能表示一个设备,例如 C驱动器、驱动器分区或卷。 java.nio.file.FileStore类支持文件存储,并为此提供了多种方法。获取文件存储信息配方包括如何获取特定文件存储的基本信息。

文件系统支持访问目录和文件的层次结构。它在 Java7 中用 java.nio.file.FileSystem类表示。获取文件系统信息配方中介绍了获取文件系统的一般信息。这包括如何获取文件系统和底层文件存储的根目录列表。

遍历目录层次结构对于许多应用程序都很有用。使用 SimpleFileVisitor 类遍历文件系统配方详细介绍了基本方法。此方法用于使用 SimpleFileVisitor 类删除目录和使用 SimpleFileVisitor 类复制目录。

当一个操作仅限于一个目录时, java.nio.file.DirectoryStream接口提供了一种方便的技术,可以将目录中的每个元素作为 java.nio.file.Path对象进行检查。使用 for each 循环处理这些路径非常容易。使用 DirectoryStream 接口处理目录配方的内容,探讨了这种方法。

有时,我们不需要目录的全部内容,而是需要其元素的子集。Java 7 提供了几种过滤目录内容的方法,如使用 globbing 和编写自己的目录过滤器配方的过滤目录中所述。Globbing是一种模式匹配技术,类似于正则表达式,但更易于使用。

使用 WatchEvents方法监控文件事件中,我们了解了 Java 7 如何支持外部进程检测目录中的文件创建、修改和删除。当需要知道何时对目录进行更改时,这可能非常有用。

使用 Java7,现在可以将 ZIP 文件的内容视为文件系统。这使得管理 ZIP 文件的内容和操作 ZIP 文件中包含的文件更加容易。这项技术在理解 zip 文件系统提供程序配方中得到了演示。

获取文件存储信息

每个文件系统都支持一种文件存储机制。这可能是一个设备,比如一个 C驱动器、一个驱动器的分区、一个卷,或者其他组织文件系统空间的方式。 java.nio.file.FileStore类表示这些存储分区之一。此配方详细说明了获取文件存储信息的可用方法。

准备好了吗

要获取并使用 FileStore对象:

  1. 获取正在使用的 java.nio.file.FileSystem实例。
  2. 使用 FileSystem类“ getFileStores方法返回可用的文件存储。

怎么做。。。

  1. 创建一个新的控制台应用程序。在 main方法中,我们将使用 FileStore类的几个方法来演示该类提供的支持。让我们从添加 main方法的第一部分开始,在这里我们显示一个初始头并得到一个 FileSystem对象。另外,定义一个名为 kiloByte:

    static final long kiloByte = 1024;
    public static void main(String[] args) throws IOException {
    String format = "%-16s %-20s %-8s %-8s %12s %12s %12s\n";
    System.out.printf(format,"Name", "Filesystem", "Type",
    "Readonly", "Size(KB)", "Used(KB)",
    "Available(KB)");
    FileSystem fileSystem = FileSystems.getDefault();
    }

    long变量

  2. 接下来,我们需要使用 getFileStores方法检索可用的文件存储,然后显示它们。在块的第一部分中,我们使用几种 FileStore方法来获取相关信息。在最后一部分中,我们显示如下信息:

    for (FileStore fileStore : fileSystem.getFileStores()) {
    try {
    long totalSpace = fileStore.getTotalSpace() / kiloByte;
    long usedSpace = (fileStore.getTotalSpace() -
    fileStore.getUnallocatedSpace()) / kiloByte;
    long usableSpace = fileStore.getUsableSpace() / kiloByte;
    String name = fileStore.name();
    String type = fileStore.type();
    boolean readOnly = fileStore.isReadOnly();
    NumberFormat numberFormat = NumberFormat.getInstance();
    System.out.printf(format,
    name, fileStore, type, readOnly,
    numberFormat.format(totalSpace),
    numberFormat.format(usedSpace),
    numberFormat.format(usableSpace));
    }
    catch (IOException ex) {
    ex.printStackTrace();
    }
    }
  3. Execute the application. Your output will differ from the following, but should reflect the drives on your system:

    名称文件系统类型已用只读大小(KB)可用大小(KB)

    HP(C:)NTFS 假 301531984 1630414213849564

    工厂图片工厂图片(D:)NTFS 假 11036652 9488108 1548544

    HP_PAVILION HP_PAVILION(E:)NTFS 假 312568640 66489184 246079456

    东芝东芝(H:)FAT32 假 15618080 3160768 12457312

它是如何工作的。。。

创建了一个格式字符串以简化文件存储信息的显示。这个字符串在两个 printf方法中都使用过。两次使用同一字符串可确保输出的间距一致。使用此字符串显示了一个简单的标题。

使用 FileSystems类的 getDefault方法获得 FileSystem对象。对该对象执行 getFileStores方法以获取 FileStore对象的列表。

在循环中,try 块用于捕获可能抛出的异常。调用了几个方法,详见下表。创建了一个 NumberFormat类的实例来格式化文件存储大小信息。最后一个 printf方法显示每个文件存储的文件存储信息:

|

方法

|

意思

| | --- | --- | | getTotalSpace | 文件存储上可用的总空间(字节) | | getUnallocatedSpace | 未分配的字节数 | | getUsableSpace | JVM 可用的可用字节数 | | name | 表示文件存储名称的特定于实现的字符串 | | type | 表示文件存储类型的特定于实现的字符串 | | isReadOnly | 如果该方法返回 true,则尝试创建文件或打开文件进行写入将导致抛出 IOException |

如果外部操作使用或释放文件存储上的空间, getUnallocatedSpacegetUsableSpace方法返回的值可能会更改。

另见

FileStore支持的属性视图使用两种 supportsFileAttributeView方法之一确定。这些在中有说明,还有更多。。第三章确定操作系统对属性视图的支持配方的章节获取文件和目录信息

获取文件系统信息

文件系统由目录和文件的层次结构组成。关于文件系统,通常有用的信息量是有限的。例如,我们可能想知道文件系统是只读的还是提供者是谁。在本配方中,我们将研究可用于检索文件系统属性的方法。

准备好了吗

要访问文件系统的方法,我们需要:

  1. 获取对 java.nio.file.FileSystem对象的引用。
  2. 使用此对象的方法访问文件系统信息。

怎么做。。。

  1. 创建一个新的控制台应用程序。在应用程序的 main方法中添加以下代码。此序列显示多个 fileSystem属性,包括文件系统提供程序、文件打开状态、文件是否可只读、根目录以及文件存储的名称:

    FileSystem fileSystem = FileSystems.getDefault();
    FileSystemProvider provider = fileSystem.provider();
    System.out.println("Provider: " + provider.toString());
    System.out.println("Open: " + fileSystem.isOpen());
    System.out.println("Read Only: " + fileSystem.isReadOnly());
    Iterable<Path> rootDirectories = fileSystem.getRootDirectories();
    System.out.println();
    System.out.println("Root Directories");
    for (Path path : rootDirectories) {
    System.out.println(path);
    }
    Iterable<FileStore> fileStores = fileSystem.getFileStores();
    System.out.println();
    System.out.println("File Stores");
    for (FileStore fileStore : fileStores) {
    System.out.println(fileStore.name());
    }
  2. Execute the application. Your output will depend upon the configuration of your system. However, it should mimic the output that follows:

    提供者:sun.nio.fs。WindowsFileSystemProvider@7b60e796

    打开:正确

    只读:假

    根目录

    *C:*

    *D:*

    *E:*

    *F:*

    *G:*

    *H:*

    *I:*

    *J:*

    *K:*

    *L:*

    文件存储

    HP

    工厂图片

    惠普展馆

    东芝

它是如何工作的。。。

getDefault方法返回 JVM 使用的默认文件系统。接下来,针对这个对象调用了几个方法:

  • provider方法返回提供者,即文件系统的实现者。在本例中,它是与 JVM 捆绑在一起的 Windows 文件系统提供程序。
  • isOpen方法表示文件系统已打开,可以使用。
  • isReadOnly方法返回 false,表示可以对系统进行读写操作。
  • 我们使用 getRootDirectories方法创建了一个 Iterable对象,它允许我们列出每个根目录。
  • getFileStores方法返回另一个 Iterable对象,用于显示文件存储的名称。

还有更多。。。

虽然我们通常不需要关闭文件系统,但可以使用 close方法来关闭文件系统。对文件系统执行的任何后续方法都将导致抛出一个 ClosedFileSystemException。与文件系统关联的任何开放通道、目录流和监视服务也将被关闭。请注意,无法关闭默认文件系统。

FileSystems类“ getFileSystem方法可用于访问特定的文件系统。此外,重载的 newFileSystem方法将创建新的文件系统。 close方法可用于这些实例。

文件系统是线程安全的。但是,如果一个线程试图关闭文件系统,而另一个线程正在访问 filesystem对象,则关闭操作可能会被阻止

直到访问完成。

使用 SimpleFileVisitor 类遍历文件系统

在使用目录系统时,通常需要遍历文件系统,检查文件层次结构中的每个子目录。通过 java.nio.file.SimpleFileVisitor课程,这项任务变得容易了。此类实现在访问目录之前和之后执行的方法。此外,在目录中访问文件的每个实例以及发生异常时,都会调用回调方法。

SimpleFileVisitor类或派生类与 java.nio.file.Files类的 walkFileTree方法结合使用。它从特定根目录开始执行深度优先遍历。

准备好了吗

要遍历目录,我们需要:

  1. 创建一个表示根目录的 Path对象。
  2. 创建从 SimpleFileVisitor派生的类的实例。
  3. 将这些对象用作 Files类“ walkFileTree方法的参数。

怎么做。。。

  1. 创建一个新的控制台应用程序,并使用以下 main方法。在这里,我们将遍历 home目录,并按如下方式列出其每个元素:

    public static void main(String[] args) {
    try {
    Path path = Paths.get("/home");
    ListFiles listFiles = new ListFiles();
    Files.walkFileTree(path, listFiles);
    }
    catch (IOException ex) {
    ex.printStackTrace();
    }
    }
  2. 将以下 ListFiles类添加到项目中。它说明了每种 SimpleFileVisitor方法的使用:

    class ListFiles extends SimpleFileVisitor<Path> {
    private final int indentionAmount = 3;
    private int indentionLevel;
    public ListFiles() {
    indentionLevel = 0;
    }
    private void indent() {
    for(int i=0 ; i<indentionLevel; i++) { {
    System.out.print(' ');
    }
    }
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
    indent();
    System.out.println("Visiting file:" + file.getFileName());
    return FileVisitResult.CONTINUE;
    }
    @Override
    public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
    indentionLevel -= indentionAmount;
    indent();
    System.out.println("Finished with the directory: " + directory.getFileName());
    return FileVisitResult.CONTINUE;
    }
    @Override
    public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attributes) throws IOException {
    indent();
    System.out.println("About to traverse the directory: " + directory.getFileName());
    indentionLevel += indentionAmount;
    return FileVisitResult.CONTINUE;
    }
    SimpleFileVisitor classusing, for filesystem traverse@Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
    System.out.println("A file traversal error ocurred");
    return super.visitFileFailed(file, exc);
    }
    }
  3. Execute the application. Depending on the structure of your home directory, you may get results different from the following:

    即将遍历目录:home

    即将遍历目录:docs

    访问文件:users.bak

    访问文件:users.txt

    已完成目录:docs

    即将穿越目录:音乐

    访问文件:未来设置 A.mp3

    访问文件:机器人大脑 A.mp3

    访问文件:太空机 A.mp3

    目录已完成:音乐

    已完成目录:home

检查 backup目录以验证它是否已成功创建。

它是如何工作的。。。

main方法中,我们为 home目录创建了一个 Path对象。接下来,创建了一个 ListFiles类的实例。这些对象被用作 walkFileTree方法的参数。此方法影响了 home目录的遍历,并根据需要调用 ListFiles类的方法。

walkFileTree方法从根目录开始,并对目录层次结构执行深度优先遍历。在遍历目录之前,调用了 preVisitDirectory方法。接下来,处理目录的每个元素。如果它是一个文件,则调用 visitFile方法。一旦处理完目录的所有元素,就会调用 postVisitDirectory方法。如果发生异常,则会调用 visitFileFailed方法。

添加了私有助手方法,使输出更具可读性。 indentionAmount变量控制每个压痕的深度。当访问每个子目录时, indentionLevel变量会递增或递减。 indent方法预成形了实际压痕。

还有更多。。。

有两种重载的 walkFileTree方法。一个是 PathFileVisitor对象,如前所示。它不会跟随链接,会访问目录的所有级别。第二个方法接受两个附加参数:一个指定要访问的目录级别数,另一个用于配置遍历。目前,唯一可用的配置选项是 FileVisitOption.FOLLOW_LINKS,它指示方法遵循符号链接。

默认情况下不跟随符号链接。如果在 walkFileTree方法的参数指定时遵循它们,则应注意检测循环链接。如果检测到循环链接,则将其视为错误条件。

要访问的目录级别数由整数参数控制。值 0 将导致仅访问顶级目录。值 Integer.MAX_VALUE表示将访问所有级别。值为 2 表示只遍历前两个目录级别。

当下列情况之一发生时,遍历将终止:

  • 所有文件都已被遍历
  • visit方法返回 FileVisitResult.TERMINATE
  • visit方法以 IOException终止,或者其他异常被传回

任何不成功的操作通常会导致调用 visitFileFailed方法并抛出 IOException

当遇到文件时,如果它不是目录,则尝试读取其 BasicFileAttributes。如果成功,则将该属性传递给 visitFile方法。如果不成功,则调用 visitFileFailed方法,除非得到处理,否则将抛出 IOException

如果文件是一个目录,并且可以打开该目录,则调用 preVisitDirectory并访问该目录的元素及其子体。

如果文件是目录,且目录无法打开,则调用 visitFileFailed方法并抛出 IOException。但是,深度优先搜索将继续进行到下一个兄弟。

下表总结了遍历过程。

|

遇到的元素

|

可以打开

|

无法打开

| | --- | --- | --- | | 文件 | visitFile被调用 | visitFileFailed被调用 | | 目录 | preVisitDirectory被称为处理目录元素postVisitDirectory被调用 | visitFileFailed被调用 |

为方便起见,枚举 FileVisitResult的枚举常数如下:

|

价值

|

意思

| | --- | --- | | CONTINUE | 继续遍历 | | SKIP_SIBLINGS | 继续而不访问此文件或目录的同级 | | SKIP_SUBTREE | 继续而不访问此目录中的条目 | | TERMINATE | 终止 |

另见

使用 SimpleFileVisitor 类删除目录和使用 SimpleFileVisitor 类复制目录分别使用本配方中描述的方法删除和复制目录。

使用 SimpleFileVisitor 类删除目录

某些应用程序需要能够删除目录。这可以通过使用 walkFileTree方法和 java.nio.file.SimpleFileVisitor派生类来实现。这个食谱是建立在使用 TimeFieleSistCar 类来遍历文件系统 Po.T3.配方的基础上的。

准备好了吗

要删除目录,我们需要:

  1. 创建一个表示根目录的 Path对象。

  2. 创建一个从 SimpleFileVisitor派生的类实例如下:

    • 覆盖 visitFile方法删除文件
    • 覆盖 postVisitDirectory方法删除目录
  3. 将这些对象用作 Files类“ walkFileTree方法的参数。

怎么做。。。

  1. 创建一个新的控制台应用程序。在这里,我们将删除 home目录及其所有元素。在 main方法中增加以下代码:

    try {
    Files.walkFileTree(Paths.get("/home"), new DeleteDirectory());
    }
    catch (IOException ex) {
    ex.printStackTrace();
    }
  2. DeleteDirectory类如下所示。当删除每个文件和目录时,会显示一条消息,大意是:

    public class DeleteDirectory extends SimpleFileVisitor<Path> {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
    throws IOException {
    System.out.println("Deleting " + file.getFileName());
    Files.delete(file);
    return FileVisitResult.CONTINUE;
    }
    @Override
    public FileVisitResult postVisitDirectory(Path directory, IOException exception)
    throws IOException {
    if (exception == null) {
    System.out.println("Deleting " + directory.getFileName());
    Files.delete(directory);
    return FileVisitResult.CONTINUE;
    }
    else {
    throw exception;
    }
    }
    }
  3. Back up the home directory and then execute the application. You should get the following output depending on the actual directory structure:

    删除 users.bak

    删除 users.txt

    删除单据

    删除未来设置 A.mp3

    删除机器人大脑 A.mp3

    删除太空机 A.mp3

    删除音乐

    删除主页

验证目录是否已删除。

它是如何工作的。。。

main方法中,我们创建了一个表示 home目录的 Path对象。接下来,我们创建了一个 DeleteDirectory类的实例。这两个对象被用作启动遍历过程的 walkFileTree方法的参数。

当遇到文件时,执行 visitFile方法。在这个方法中,我们显示一条消息,指示文件正在被删除,然后使用 Files类的 delete方法删除文件。当遇到目录时,会调用 postVisitDirectory方法。进行测试以确保没有发生错误,然后显示一条消息,指示目录正在被删除,然后调用该目录的 delete方法。两个方法都返回了 FileVisitResult.CONTINUE,继续删除过程。

另见

使用 SimpleFileVisitor 类遍历文件系统方法提供了有关 walkFileTree方法和 SimpleFileVisitor类使用的更多详细信息。使用 SimpleFileVisitor 类方法复制目录也提供了这种方法的变体。

使用 SimpleFileVisitor 类复制目录

某些应用程序需要能够复制目录。这可以通过使用 walkFileTree方法和 java.nio.file.SimpleFileVisitor派生类来实现。这个食谱是建立在使用 TimeFieleSistCar 类来遍历文件系统 Po.T3.配方的基础上的。

准备好了吗

要删除目录,我们需要:

  1. 创建一个表示根目录的 Path对象。

  2. 创建一个从 SimpleFileVisitor派生的类实例如下:

    • 覆盖 visitFile方法复制文件
    • 覆盖 preVisitDirectory方法复制目录
  3. 将这些对象用作 Files类“ walkFileTree方法的参数。

怎么做。。。

  1. 创建一个新的控制台应用程序。在这里,我们将把 home目录及其所有元素复制到 backup目录。在 main方法中增加以下代码:

    try {
    Path source = Paths.get("/home");
    Path target = Paths.get("/backup");
    Files.walkFileTree(source,
    EnumSet.of(FileVisitOption.FOLLOW_LINKS),
    Integer.MAX_VALUE,
    new CopyDirectory(source, target));
    }
    catch (IOException ex) {
    ex.printStackTrace();
    }
  2. CopyDirectory类如下所示。当删除每个文件和目录时,会显示一条消息,大意是:

    public class CopyDirectory extends SimpleFileVisitor<Path> {
    private Path source;
    private Path target;
    public CopyDirectory(Path source, Path target) {
    this.source = source;
    this.target = target;
    }
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
    SimpleFileVisitor classusing, for directory copySystem.out.println("Copying " + source.relativize(file));
    Files.copy(file, target.resolve(source.relativize(file)));
    return FileVisitResult.CONTINUE;
    }
    @Override
    public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attributes) throws IOException {
    Path targetDirectory = target.resolve(source.relativize(directory));
    try {
    System.out.println("Copying " + source.relativize(directory));
    Files.copy(directory, targetDirectory);
    }
    catch (FileAlreadyExistsException e) {
    if (!Files.isDirectory(targetDirectory)) {
    throw e;
    }
    }
    return FileVisitResult.CONTINUE;
    }
    }
  3. Execute the application. The exact output is dependent on the source file structure you used, but should be similar to the following:

    复制

    复制单据

    复制 docs\users.bak

    复制 docs\users.txt

    临摹音乐

    复制音乐\未来设置 A.mp3

    复制音乐\机器人大脑 A.mp3

    复制音乐\太空机 A.mp3

它是如何工作的。。。

main方法中,我们为 homebackup目录创建了 Path对象。我们使用这些对象创建了一个 CopyDirectory对象。我们使用了一个双参数 CopyDirectory构造函数,这样它的方法就可以直接访问这两条路径。

使用源 Path调用了 walkFileTree方法。它也被作为第二个参数传递,一个 EnumSet,它指定不遵循符号链接。此参数需要一组选项。 EnumSet类的静态方法创建了集合。

walkFileTree方法的第三个参数是一个值,指示要遵循多少级别。我们传递了一个值 Integer.MAX_VALUE,这将导致复制 home目录的所有级别。最后一个参数是 CopyDirectory对象的一个实例。

在遍历过程中遇到文件时,会调用 CopyDirectory类的 visitFile方法。此时会显示一条消息,指示正在复制文件,然后使用 copy方法将源文件复制到目标目录。 relativize方法用于获取到源的相对路径,该路径用作 resolve方法的参数。结果是一个 Path对象,用源文件名表示目标目录。这些方法在第 2 章使用路径定位文件和目录中的使用路径解析组合路径和在两个位置之间创建路径配方中进行了讨论。

在遍历过程中遇到目录时,会调用 preVisitDirectory方法。它的工作方式与 visitFile方法相同,只是我们复制了一个目录而不是一个文件。两个方法都返回了 FileVisitResult.CONTINUE,继续复制过程。仍然需要复制目录中的各个文件,因为 copy方法只复制单个文件。

注意, CopyDirectory类使用 Path作为泛型值扩展了 SimpleFileVisitor类。 walkFileTree方法需要一个实现 Path接口的对象。因此,我们必须使用 Path或扩展 Path的接口。

另见

使用 SimpleFileVisitor 类遍历文件系统方法提供了有关 walkFileTree方法和 SimpleFileVisitor类使用的更多详细信息。使用 SimpleFileVisitor 类方法删除目录也提供了使用此方法的变体。

使用 DirectoryStream 接口处理目录内容

确定目录的内容是一个相当常见的要求。有几种方法可以做到这一点。在本配方中,我们将研究如何使用 java.nio.file.DirectoryStream接口来支持此任务。

目录将由文件或子目录组成。这些文件可能是常规文件,也可能是链接或隐藏的文件。 DirectoryStream接口将返回所有这些元素类型。我们将使用 java.nio.file.Files类的 newDirectoryStream方法获得一个 DirectoryStream对象。此方法有三个重载版本。首先说明该方法的最简单用法。用于过滤目录内容的版本显示在使用 globbing配方过滤目录和编写自己的目录过滤器配方中。

准备好了吗

为了使用 DirectoryStream,我们需要:

  1. 获取一个 DirectoryStream对象的实例。
  2. 遍历 DirectoryStream以处理其元素。

怎么做。。。

  1. 创建一个新的控制台应用程序,并添加以下 main方法。我们创建一个新的 DirectoryStream对象,然后使用 for-each 循环遍历目录元素,如下所示:

    public static void main(String[] args) {
    Path directory = Paths.get("/home");
    try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
    for (Path file : directoryStream) {
    System.out.println(file.getFileName());
    }
    }
    catch (IOException | DirectoryIteratorException ex) {
    ex.printStackTrace();
    }
    }
  2. Execute the application. Your output should reflect the contents of your home directory and should be similar to the following:

    文件

    音乐

它是如何工作的。。。

home目录创建了一个 Path对象。此对象与 newDirectoryStream方法一起使用,该方法为目录返回一个 DirectoryStream对象。 DirectoryStream接口扩展了 Iterable接口。这使得 DirectoryStream对象可以与 for-each 语句一起使用,该语句只打印 home目录中每个元素的名称。在本例中,只有两个子目录: docsmusic

请注意 try with 资源块的使用。这对于 Java 7 来说是新的,在中讨论了使用 try with resource 块来改进异常处理代码的方法,可以在第 1 章Java 语言改进中找到。这保证了目录流将被关闭。如果未使用这种类型的 try 块,则在不再需要该流后关闭该流非常重要。

使用的 Iterable对象不是通用 iterator。它在以下几个重要方面有所不同:

  • 只支持一个 Iterator
  • hasNext方法至少提前读取一个元素
  • 不支持 remove方法

DirectoryStream接口有一个方法 iterator,返回一个 Iterator类型的对象。第一次调用该方法时,返回一个 Iterator对象。随后调用该方法将抛出一个 IllegalStateException

hasNext方法将至少提前读取一个元素。如果该方法返回 true,则其下一个方法的下一次调用保证返回一个元素。未指定返回元素的顺序。此外,许多操作系统都有到它们自己和/或它们的父系统的链接,如许多 shell 中的 "."..所示。这些条目不会被返回。

返回的 iterator有时被称为弱一致。这意味着尽管 iterator是线程安全的,但在 iterator返回后对目录的任何更新都不会导致对 iterator的更改。

还有更多。。。

有两种重载的 newDirectoryStream方法,它们允许通过全局模式或 DirectoryStream.Filter对象过滤该方法的结果。全局模式是一个字符串,包含定义模式的一系列字符。该模式用于确定要返回的目录元素。 DirectoryStream.Filter接口有一个方法 accept,它返回一个布尔值,指示是否应返回目录元素。

另见

使用 globbing方法过滤目录说明了 globbing 模式的使用。编写自己的目录过滤器配方说明了如何创建并使用 DirectoryStream.Filter对象来过滤目录的内容。

使用 globbing 过滤目录

全局模式类似于正则表达式,但更简单。与正则表达式一样,它可以用于匹配特定的字符序列。我们可以结合使用 globbing 和 newDirectoryStream方法来过滤目录的内容。在使用 DirectoryStream 接口处理目录配方的内容的中演示了此方法的使用。

准备好了吗

要使用此技术,我们需要:

  1. 创建符合筛选要求的全局字符串。
  2. 为感兴趣的目录创建一个 java.nio.file.Path对象。
  3. 将这两个对象用作 newDirectoryStream方法的参数。

怎么做。。。

  1. 创建一个新的控制台应用程序,并使用以下 main方法。在本例中,我们将只列出那些以 java开头并以 .exe结尾的目录元素。我们将使用 Java7 bin目录。 globbing字符串使用特殊字符 *表示零个或多个字符,如下所示:

    Path directory = Paths.get("C:/Program Files/Java/jdk1.7.0/bin");
    try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory,"java*.exe")) {
    for (Path file : directoryStream) {
    System.out.println(file.getFileName());
    }
    }
    catch (IOException | DirectoryIteratorException ex) {
    ex.printStackTrace();
    }
  2. Execute the application. The output should be similar to the following:

    java-rmi.exe

    java.exe

    javac.exe

    javadoc.exe

    javah.exe

    javap.exe

    javaw.exe

    javaws.exe

它是如何工作的。。。

首先,创建了一个表示 bin目录的 Path对象。然后,它被用作 newDirectoryStream方法的第一个参数。第二个参数是 globbing字符串。在本例中,它匹配了一个目录元素,该元素以 java开头,以 .exe结尾。允许任何数量的中间字符。然后使用 for-each 循环来显示过滤后的文件。

还有更多。。。

全局字符串基于模式,模式使用特殊字符匹配字符串序列。这些在 FilesgetPathMatcher方法的文档中定义。在这里,我们将更深入地研究这些字符串。下表总结了几个特殊字符:

|

特殊符号

|

意思

| | --- | --- | | * | 匹配名称组件的零个或多个字符,而不跨越目录边界 | | ** | 匹配零个或多个跨越目录边界的字符 | | ? | 只匹配名称组件的一个字符 | | \ | 用于匹配特殊符号的转义字符 | | [ ] | 匹配括号中的单个字符。A-匹配一个范围。A.意思是否定。*、?、和\字符自身匹配,如果是括号内的第一个字符或!后面的第一个字符,则 a-自身匹配!。 | | { } | 可以同时指定多个子模式。这些图案使用大括号组合在一起,但在大括号内用逗号分隔。 |

匹配通常以依赖于实现的方式执行。这包括匹配是否区分大小写。 **符号在此不适用,因为 newDirectoryStream方法返回单个元素。这里没有机会匹配跨越目录边界的序列。其他方法将使用此功能。

下表给出了几个可能有用的全局模式示例:

|

球形串

|

将匹配

| | --- | --- | | *.java | 任何以 .java结尾的文件名 | | *.{java,class,jar} | 任何以 .java, .class.jar结尾的文件 | | java*[ph].exe | 只有以 java 开头并以 p.exeh.exe结尾的文件 | | j*r.exe | 以 j开头并以 r.exe结尾的文件 |

现在,让我们讨论一下 PathMatcher接口的使用。

使用 PathMatcher 接口过滤目录

java.nio.file.PathMatcher接口提供了一种使用glob匹配文件名的方法。它有一个方法 matches,它接受一个 Path参数。如果文件与 glob 模式匹配,则返回 true。否则返回 false

在下面的代码序列中,我们通过使用 glob 模式创建一个 PathMatcher对象来修改前面的示例: glob:java?.exe。在 for 循环中,我们使用 matches方法进一步过滤文件的子集,该子集以 java开头,后跟单个字符,然后以 .exe:结尾

Path directory = Paths.get("C:/Program Files/Java/jdk1.7.0/bin");
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:java?.exe");
try (DirectoryStream<Path> directoryStream =
Files.newDirectoryStream(directory,"java*.exe")) {
for (Path file : directoryStream) {
if(pathMatcher.matches(file.getFileName())) {
System.out.println(file.getFileName());
}
}
}
catch (IOException | DirectoryIteratorException ex) {
ex.printStackTrace();
}

执行此序列时,应获得以下输出:

javac.exe

javah.exe

javap.exe

javaw.exe

注意与 matches方法一起使用的**glob:**前缀。此方法需要使用它,但不适用于 newDirectoryStream方法。此外, matches方法采用 Path参数。但是,请注意,我们使用了从 Path类的 getFileName方法返回的 String。仅使用 Path对象或使用 String文本不起作用。

我们可以使用正则表达式代替 glob:前缀。为此,请使用**reg:**前缀,后跟正则表达式。

通常,对于目录的简单筛选,我们将使用更严格的 glob 模式作为 newDirectoryStream方法的一部分。我们在这里使用它是为了说明目的。但是,如果我们想在循环中执行多个过滤操作,那么使用模式作为 newDirectoryStream方法的一部分,然后使用一个或多个 matches方法调用是可行的策略。

另见

编写自己的目录过滤器配方探索了如何创建更强大的过滤器,以便根据文件名以外的属性匹配文件名。

编写自己的目录过滤器

当使用 java.nio.file.Files类的 newDirectoryStream方法时,目录过滤器用于控制返回哪些目录元素。当我们需要限制流的输出时,这很有用。例如,我们可能只对那些超过一定大小或上次修改日期后的文件感兴趣。如本配方所述, java.nio.file.DirectoryStream.Filter接口将限制流的输出。它比使用 globbing 方法过滤目录中所述的 globbing 更强大,因为决策可以基于文件名以外的因素。

准备好了吗

要使用此技术,我们需要:

  1. 创建一个符合我们过滤要求的 DirectoryStream.Filter对象。
  2. 为感兴趣的目录创建一个 Path对象。
  3. 将这两个对象用作 newDirectoryStream方法的参数。

怎么做。。。

  1. 创建一个新的控制台应用程序,并将以下序列添加到 main方法中。在这个例子中,我们将只过滤掉那些隐藏的目录元素。我们将使用 Windows 系统目录。但是,任何其他合适的目录都可以使用:

    DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
    public boolean accept(Path file) throws IOException {
    return (Files.isHidden(file));
    }
    };
    Path directory = Paths.get("C:/Windows");
    try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory,filter)){
    own directory filterwritingfor (Path file : directoryStream) {
    System.out.println(file.getFileName());
    }
    }
    catch (IOException | DirectoryIteratorException ex) {
    ex.printStackTrace();
    }
  2. When executed, your output should list only those files that are hidden. The following is one possible output:

    SwSys1.bmp

    SwSys2.bmp

    WindowsShell.Manifest

它是如何工作的。。。

首先,我们创建了一个匿名内部类来定义一个实现 DirectoryStream.Filter接口的对象。在 accept方法中,使用 isHidden方法确定元素文件是否隐藏。 DirectoryStream.Filter接口使用其 accept方法确定是否应返回目录元素。此方法返回一个 truefalse,分别指示该元素是否应该由 newDirectoryStream方法返回。因此,它过滤掉了不受欢迎的,在本例中,它们是非隐藏元素。使用 for-each 循环来显示隐藏的元素。在声明 filter变量时,使用 Path作为泛型值来声明它。也可以使用扩展 Path接口的接口。

另见

这种技术过滤单个目录。如果需要过滤多个目录,则可以使用中使用 SimpleFileVisitor 类遍历文件系统方法的示例来处理多个目录。

使用 WatchEvents 监控文件事件

当应用程序需要知道目录中的更改时,监视服务可以侦听这些更改,然后将这些更改通知应用程序。服务将根据感兴趣的事件类型注册要监视的目录。当事件发生时,观察事件将排队,并可根据应用程序的需要进行后续处理。

准备好了吗

要监视目录中的事件,我们需要执行以下操作:

  1. 创建一个表示目录的 java.nio.file.Path对象。
  2. 使用 java.nio.file.FileSystem类的 newWatchService方法创建一个新的手表服务。
  3. 确定我们感兴趣监视的事件。
  4. 向 watch 服务注册目录和事件。
  5. 在事件发生时对其进行处理。

怎么做。。。

  1. 创建一个新的控制台应用程序。我们将在 main方法中添加代码,创建一个监视服务,确定我们想要监视的事件,向该服务注册 docs目录,然后处理这些事件。让我们首先为目录创建 watch 服务和一个 Path对象。在 main方法中增加以下代码:

    try {
    FileSystem fileSystem = FileSystems.getDefault();
    WatchService watchService = fileSystem.newWatchService();
    Path directory = Paths.get("/home/docs");
  2. 接下来,创建一个监视事件数组,以监视文件的创建、删除和修改,如下所示:

    WatchEvent.Kind<?>[] events = {
    StandardWatchEventKinds.ENTRY_CREATE,
    StandardWatchEventKinds.ENTRY_DELETE,
    StandardWatchEventKinds.ENTRY_MODIFY};
    directory.register(watchService, events);
  3. 添加以下 while 循环以监视和处理任何目录事件:

    while (true) {
    System.out.println("Waiting for a watch event");
    WatchKey watchKey = watchService.take();
    System.out.println("Path being watched: " + watchKey.watchable());
    System.out.println();
    if (watchKey.isValid()) {
    for (WatchEvent<?> event : watchKey.pollEvents()) {
    System.out.println("Kind: " + event.kind());
    System.out.println("Context: " + event.context());
    System.out.println("Count: " + event.count());
    System.out.println();
    }
    boolean valid = watchKey.reset();
    if (!valid) {
    // The watchKey is not longer registered
    }
    }
    }
    }
    catch (IOException ex) {
    ex.printStackTrace();
    }
    catch (InterruptedException ex) {
    ex.printStackTrace();
    }
  4. Execute the application. You should get the following output:

    等待观看活动

  5. Using a text editor, create a new file called temp.txt and save it in the docs directory. The application should then display output similar to the following. Your output may differ if this is the first time you created the file in the directory. These entries indicate that the file has been created and its contents are then saved:

    正在监视的路径:\home\docs

    种类:条目\创建

    上下文:temp.txt

    计数:1

    等待观看活动

    正在监视的路径:\home\docs

    种类:录入\修改

    上下文:temp.txt

    计数:2

    等待观看活动

  6. Next, save the file again. You should now get the following output:

    正在监视的路径:\home\docs

    种类:录入\修改

    上下文:temp.txt

    计数:1

    等待观看活动

  7. From file manager, delete the file. Your output should reflect its deletion:

    种类:条目\删除

    上下文:temp1.txt

    计数:1

    等待观看活动

它是如何工作的。。。

我们首先需要的是一个物体。这是通过获取默认文件系统,然后对其应用 newWatchService方法获得的。接下来,我们创建了一个表示 docs目录的 Path对象和一个包含创建、删除和修改类型事件的事件数组。

然后进入一个无限循环来监视和处理 docs目录中发生的文件事件。循环开始时显示一条消息,指示它正在等待事件。执行了 WatchService类的 take方法。此方法将一直阻止,直到事件发生。

当事件发生时,它返回一个包含事件信息的 WatchKey对象。它的 watchable方法返回被监视的对象,然后显示该对象以供参考。

使用 isValid方法验证了手表键的有效性,并将其 pollEvents方法用作每个循环的一部分。 pollEvents方法返回了所有未决事件的列表。显示了与事件关联的类型、上下文和计数值。

我们监视的事件的上下文是目标目录和导致事件的条目之间的相对路径。计数值取决于事件,将在下一节中讨论。

最后一个活动是重置手表键。这需要将密钥重新置于就绪状态,直到再次需要它。如果该方法返回 false,则该密钥不再有效。

还有更多。。。

WatchService接口具有获取手表钥匙和关闭服务的方法。 polltake方法检索我们前面看到的下一个 watch 键。如果不存在,则 poll方法将返回 null。然而, take方法将被阻止,直到有手表键可用。有一个重载的 poll方法,它需要额外的参数来指定在返回之前等待事件的时间。这些参数包括超时值和 TimeUnit值。在第 4 章管理文件和目录中的了解文件或目录配方的设置时间相关属性部分中讨论了 TimeUnit枚举的使用。

Path类“ register方法将注册一个由其所针对的 Path对象指定的文件。该方法接受的参数为:

  • 指定监视服务
  • 要监视的事件类型
  • 确定如何注册 Path对象的修饰符

WatchEvent.Modifier接口指定如何向 watch 服务注册 Path对象。在这个 Java 版本中,没有定义修饰符。

java.nio.file.StandardWatchEventKinds类定义了标准事件类型。此接口字段汇总如下表所示:

|

友善的

|

意思

|

计数

| | --- | --- | --- | | ENTRY_CREATE | 已创建目录项 | 总是一个 1 | | ENTRY_DELETE | 已删除目录项 | 总是一个 1 | | ENTRY_MODIFY | 目录项已修改 | 1 或以上 | | OVERFLOW | 表示事件可能已丢失或丢弃的特殊事件 | 大于 1 |

当事件发生时,watch 服务将返回表示该事件的 WatchKey对象。对于同一事件类型的多个事件,将重用此键。当该类型的事件发生时,与该事件关联的计数将递增。如果该类型的多个事件在处理事件之前发生,则每次计数值都会增加一定量。金额取决于事件的类型。

在上一个示例中使用 reset方法将使 watch 键重新排队,并将计数重置为零。对于重复事件,上下文是相同的。对于该事件类型,每个目录条目都有自己的监视键。

可以使用 WatchKey接口的 cancel方法取消事件。这将在 watch 服务中注销事件。队列中的任何挂起事件都将保留在队列中,直到删除为止。如果监视服务关闭,监视事件也将取消。

手表服务是线程安全的。这意味着,如果多个线程正在访问事件,那么在使用 reset方法时应该小心。在使用该事件的所有线程完成对该事件的处理之前,不应使用该方法。

可以使用 close方法关闭手表服务。如果多个线程正在使用此服务,则随后检索监视密钥的尝试将导致 ClosedWatchServiceException

文件系统报告事件的速度可能快于监视服务处理事件的速度。监视服务的某些实现可能会对排队的事件数施加限制。如果故意忽略事件,则使用类型为 OVERFLOW的事件报告此问题。溢出事件会自动为目标注册。溢出事件的上下文取决于实现。

watch 服务的许多方面取决于实现,包括:

  • 是否使用或模拟本机事件通知服务
  • 事件排队的时间有多及时
  • 处理事件的顺序
  • 是否报告短期事件

了解 ZIP 文件系统提供程序

处理 ZIP 文件比 Java7 之前简单得多。本版本中引入的 ZIP 文件系统提供程序将 ZIP 和 JAR 文件当作文件系统来处理,因此,您可以轻松访问文件的内容。可以像处理普通文件一样处理文件,包括复制、删除、移动和重命名文件。您还可以修改文件的某些属性。此配方将向您展示如何创建 ZIP 文件系统的实例并向系统添加目录。

准备好了吗

我们必须首先创建一个 java.net.URI对象的实例来表示我们的 ZIP 文件,然后创建新的 java.nio.file.FileSystem,然后才能对 ZIP 文件的内容进行任何操作。在本例中,我们还将使用 java.util.HashMap设置 FileSystem的可选属性,如下所示:。

  1. 创建一个 URI对象来表示 ZIP 文件。
  2. 创建一个 HashMap对象,将 create属性指定为 true
  3. 使用 newFileSystem方法创建 FileSystem对象。

怎么做。。。

  1. 使用 main方法创建控制台应用程序。在 main方法中,添加以下代码序列。我们将在一个 ZIP 文件中创建一个新的文件系统,然后向其中添加一个目录,如下所示:

    Map<String, String> attributes = new HashMap<>();
    attributes.put("create", "true");
    try {
    URI zipFile = URI.create("jar:file:/home.zip");
    try (FileSystem zipFileSys = FileSystems.newFileSystem(zipFile, attributes);) {
    Path path = zipFileSys.getPath("docs");
    Files.createDirectory(path);
    try (DirectoryStream<Path> directoryStream =
    Files.newDirectoryStream(zipFileSys.getPath("/"));) {
    for (Path file : directoryStream) {
    System.out.println(file.getFileName());
    }
    }
    }
    }
    catch (IOException e) {
    e.printStackTrace();
    }
  2. Execute the program. Your output should appear as follows:

    文件/

它是如何工作的。。。

URI对象通过使用 HashMap对象指定 ZIP 文件的位置,我们指定如果 ZIP 文件不存在,则应创建它。 FileSystem对象 zipFileSys是在 try with resources 块中创建的,因此资源将自动关闭,但如果您不希望使用嵌套的 try with resources 块,则必须使用 FileSystem类“ close方法手动关闭资源。资源尝试块在第 1 章Java 语言改进中有详细说明,配方:使用资源尝试块改进异常处理代码

为了演示如何将 ZIP 文件作为 FileSystem对象进行操作,我们调用 createDirectory方法在 ZIP 文件中添加一个文件夹。此时,我们还可以选择执行其他 FileSystem操作,例如复制文件、重命名文件和删除文件。我们使用 java.nio.file.DirectoryStream浏览 ZIP 文件结构并打印 docs目录,但您也可以在计算机上导航到 ZIP 文件的位置以验证其创建。

另见

有关 DirectoryStream类的更多信息,请参见使用 DirectoryStream 接口处理目录配方的内容。