在本章中,我们将介绍以下内容:
- 获取文件存储信息
- 获取文件系统信息
- 使用 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
对象:
- 获取正在使用的
java.nio.file.FileSystem
实例。 - 使用
FileSystem
类“getFileStores
方法返回可用的文件存储。
-
创建一个新的控制台应用程序。在
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
变量 -
接下来,我们需要使用
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(); } }
-
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
|
如果外部操作使用或释放文件存储上的空间, getUnallocatedSpace
或 getUsableSpace
方法返回的值可能会更改。
FileStore
支持的属性视图使用两种 supportsFileAttributeView
方法之一确定。这些在中有说明,还有更多。。。第三章中确定操作系统对属性视图的支持配方的章节获取文件和目录信息。
文件系统由目录和文件的层次结构组成。关于文件系统,通常有用的信息量是有限的。例如,我们可能想知道文件系统是只读的还是提供者是谁。在本配方中,我们将研究可用于检索文件系统属性的方法。
要访问文件系统的方法,我们需要:
- 获取对
java.nio.file.FileSystem
对象的引用。 - 使用此对象的方法访问文件系统信息。
-
创建一个新的控制台应用程序。在应用程序的
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()); }
-
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
对象,则关闭操作可能会被阻止
直到访问完成。
在使用目录系统时,通常需要遍历文件系统,检查文件层次结构中的每个子目录。通过 java.nio.file.SimpleFileVisitor
课程,这项任务变得容易了。此类实现在访问目录之前和之后执行的方法。此外,在目录中访问文件的每个实例以及发生异常时,都会调用回调方法。
SimpleFileVisitor
类或派生类与 java.nio.file.Files
类的 walkFileTree
方法结合使用。它从特定根目录开始执行深度优先遍历。
要遍历目录,我们需要:
- 创建一个表示根目录的
Path
对象。 - 创建从
SimpleFileVisitor
派生的类的实例。 - 将这些对象用作
Files
类“walkFileTree
方法的参数。
-
创建一个新的控制台应用程序,并使用以下
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(); } }
-
将以下
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); } }
-
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
方法。一个是 Path
和 FileVisitor
对象,如前所示。它不会跟随链接,会访问目录的所有级别。第二个方法接受两个附加参数:一个指定要访问的目录级别数,另一个用于配置遍历。目前,唯一可用的配置选项是 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 类复制目录分别使用本配方中描述的方法删除和复制目录。
某些应用程序需要能够删除目录。这可以通过使用 walkFileTree
方法和 java.nio.file.SimpleFileVisitor
派生类来实现。这个食谱是建立在使用 TimeFieleSistCar 类来遍历文件系统 Po.T3.配方的基础上的。
要删除目录,我们需要:
-
创建一个表示根目录的
Path
对象。 -
创建一个从
SimpleFileVisitor
派生的类实例如下:- 覆盖
visitFile
方法删除文件 - 覆盖
postVisitDirectory
方法删除目录
- 覆盖
-
将这些对象用作
Files
类“walkFileTree
方法的参数。
-
创建一个新的控制台应用程序。在这里,我们将删除
home
目录及其所有元素。在main
方法中增加以下代码:try { Files.walkFileTree(Paths.get("/home"), new DeleteDirectory()); } catch (IOException ex) { ex.printStackTrace(); }
-
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; } } }
-
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 类方法复制目录也提供了这种方法的变体。
某些应用程序需要能够复制目录。这可以通过使用 walkFileTree
方法和 java.nio.file.SimpleFileVisitor
派生类来实现。这个食谱是建立在使用 TimeFieleSistCar 类来遍历文件系统 Po.T3.配方的基础上的。
要删除目录,我们需要:
-
创建一个表示根目录的
Path
对象。 -
创建一个从
SimpleFileVisitor
派生的类实例如下:- 覆盖
visitFile
方法复制文件 - 覆盖
preVisitDirectory
方法复制目录
- 覆盖
-
将这些对象用作
Files
类“walkFileTree
方法的参数。
-
创建一个新的控制台应用程序。在这里,我们将把
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(); }
-
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; } }
-
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
方法中,我们为 home
和 backup
目录创建了 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 类方法删除目录也提供了使用此方法的变体。
确定目录的内容是一个相当常见的要求。有几种方法可以做到这一点。在本配方中,我们将研究如何使用 java.nio.file.DirectoryStream
接口来支持此任务。
目录将由文件或子目录组成。这些文件可能是常规文件,也可能是链接或隐藏的文件。 DirectoryStream
接口将返回所有这些元素类型。我们将使用 java.nio.file.Files
类的 newDirectoryStream
方法获得一个 DirectoryStream
对象。此方法有三个重载版本。首先说明该方法的最简单用法。用于过滤目录内容的版本显示在使用 globbing配方过滤目录和编写自己的目录过滤器配方中。
为了使用 DirectoryStream
,我们需要:
- 获取一个
DirectoryStream
对象的实例。 - 遍历
DirectoryStream
以处理其元素。
-
创建一个新的控制台应用程序,并添加以下
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(); } }
-
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
目录中每个元素的名称。在本例中,只有两个子目录: docs
和 music
。
请注意 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 和 newDirectoryStream
方法来过滤目录的内容。在使用 DirectoryStream 接口处理目录配方的内容的中演示了此方法的使用。
要使用此技术,我们需要:
- 创建符合筛选要求的全局字符串。
- 为感兴趣的目录创建一个
java.nio.file.Path
对象。 - 将这两个对象用作
newDirectoryStream
方法的参数。
-
创建一个新的控制台应用程序,并使用以下
main
方法。在本例中,我们将只列出那些以java
开头并以.exe
结尾的目录元素。我们将使用 Java7bin
目录。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(); }
-
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 循环来显示过滤后的文件。
全局字符串基于模式,模式使用特殊字符匹配字符串序列。这些在 Files
类 getPathMatcher
方法的文档中定义。在这里,我们将更深入地研究这些字符串。下表总结了几个特殊字符:
特殊符号
|
意思
| | --- | --- | | * | 匹配名称组件的零个或多个字符,而不跨越目录边界 | | ** | 匹配零个或多个跨越目录边界的字符 | | ? | 只匹配名称组件的一个字符 | | \ | 用于匹配特殊符号的转义字符 | | [ ] | 匹配括号中的单个字符。A-匹配一个范围。A.意思是否定。*、?、和\字符自身匹配,如果是括号内的第一个字符或!后面的第一个字符,则 a-自身匹配!。 | | { } | 可以同时指定多个子模式。这些图案使用大括号组合在一起,但在大括号内用逗号分隔。 |
匹配通常以依赖于实现的方式执行。这包括匹配是否区分大小写。 **
符号在此不适用,因为 newDirectoryStream
方法返回单个元素。这里没有机会匹配跨越目录边界的序列。其他方法将使用此功能。
下表给出了几个可能有用的全局模式示例:
|球形串
|
将匹配
|
| --- | --- |
| *.java
| 任何以 .java
结尾的文件名 |
| *.{java,class,jar}
| 任何以 .java, .class
或 .jar
结尾的文件 |
| java*[ph].exe
| 只有以 java 开头并以 p.exe
或 h.exe
结尾的文件 |
| j*r.exe
| 以 j
开头并以 r.exe
结尾的文件 |
现在,让我们讨论一下 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 更强大,因为决策可以基于文件名以外的因素。
要使用此技术,我们需要:
- 创建一个符合我们过滤要求的
DirectoryStream.Filter
对象。 - 为感兴趣的目录创建一个
Path
对象。 - 将这两个对象用作
newDirectoryStream
方法的参数。
-
创建一个新的控制台应用程序,并将以下序列添加到
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(); }
-
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
方法确定是否应返回目录元素。此方法返回一个 true
或 false
,分别指示该元素是否应该由 newDirectoryStream
方法返回。因此,它过滤掉了不受欢迎的,在本例中,它们是非隐藏元素。使用 for-each 循环来显示隐藏的元素。在声明 filter
变量时,使用 Path
作为泛型值来声明它。也可以使用扩展 Path
接口的接口。
这种技术过滤单个目录。如果需要过滤多个目录,则可以使用中使用 SimpleFileVisitor 类遍历文件系统方法的示例来处理多个目录。
当应用程序需要知道目录中的更改时,监视服务可以侦听这些更改,然后将这些更改通知应用程序。服务将根据感兴趣的事件类型注册要监视的目录。当事件发生时,观察事件将排队,并可根据应用程序的需要进行后续处理。
要监视目录中的事件,我们需要执行以下操作:
- 创建一个表示目录的
java.nio.file.Path
对象。 - 使用
java.nio.file.FileSystem
类的newWatchService
方法创建一个新的手表服务。 - 确定我们感兴趣监视的事件。
- 向 watch 服务注册目录和事件。
- 在事件发生时对其进行处理。
-
创建一个新的控制台应用程序。我们将在
main
方法中添加代码,创建一个监视服务,确定我们想要监视的事件,向该服务注册docs
目录,然后处理这些事件。让我们首先为目录创建 watch 服务和一个Path
对象。在main
方法中增加以下代码:try { FileSystem fileSystem = FileSystems.getDefault(); WatchService watchService = fileSystem.newWatchService(); Path directory = Paths.get("/home/docs");
-
接下来,创建一个监视事件数组,以监视文件的创建、删除和修改,如下所示:
WatchEvent.Kind<?>[] events = { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY}; directory.register(watchService, events);
-
添加以下 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(); }
-
Execute the application. You should get the following output:
等待观看活动
-
Using a text editor, create a new file called
temp.txt
and save it in thedocs
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
等待观看活动
-
Next, save the file again. You should now get the following output:
正在监视的路径:\home\docs
种类:录入\修改
上下文:temp.txt
计数:1
等待观看活动
-
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
接口具有获取手表钥匙和关闭服务的方法。 poll
和 take
方法检索我们前面看到的下一个 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 文件比 Java7 之前简单得多。本版本中引入的 ZIP 文件系统提供程序将 ZIP 和 JAR 文件当作文件系统来处理,因此,您可以轻松访问文件的内容。可以像处理普通文件一样处理文件,包括复制、删除、移动和重命名文件。您还可以修改文件的某些属性。此配方将向您展示如何创建 ZIP 文件系统的实例并向系统添加目录。
我们必须首先创建一个 java.net.URI
对象的实例来表示我们的 ZIP 文件,然后创建新的 java.nio.file.FileSystem
,然后才能对 ZIP 文件的内容进行任何操作。在本例中,我们还将使用 java.util.HashMap
设置 FileSystem
的可选属性,如下所示:。
- 创建一个
URI
对象来表示 ZIP 文件。 - 创建一个
HashMap
对象,将create
属性指定为true
。 - 使用
newFileSystem
方法创建FileSystem
对象。
-
使用
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(); }
-
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 接口处理目录配方的内容。