本文完成了我们对Java 7中更多的Java新I / O API(NIO.2)的两部分介绍,就像在第1部分中探讨的异步通道API一样,NIO.2的文件系统API填补了以前的一些重大空白Java版本处理I / O。 根据NIO.2 Java规范要求(JSR 203):
长期以来,Java平台都需要比java.io.File
类更好的文件系统接口。
该类不能以跨平台一致的方式处理文件名,它不支持有效的文件属性访问,它不允许复杂的应用程序在可用时利用文件系统特定的功能(例如,符号链接),以及它的许多方法只是在出错时返回false,而不是抛出信息丰富的异常。
抢救过来的是Java 7 beta中的三个新文件系统软件包:
-
java.nio.file
-
java.nio.file.attribute
-
java.nio.file.spi
本文重点介绍这些软件包中最有用的类:
-
java.nio.file.Files
和java.nio.file.FileVisitor
允许您遍历文件系统,查询文件或目录达到一定深度,并为找到的每一个执行用户实现的回调方法。 -
java.nio.file.Path
和java.nio.file.WatchService
允许您注册以“监视”特定目录。 如果创建,修改或删除了目录中的文件,则监视目录的应用程序会收到通知。 -
java.nio.attribute.*AttributeView
允许您查看以前对Java用户隐藏的文件和目录属性。 这些属性包括文件所有者和组权限,访问控制列表(ACL)和扩展文件属性。
我们将提供示例,说明如何使用这些类。 这些示例处于可运行状态(请参见下载 ),您可以在IBM®和Oracle的Java 7 beta中试用它们(在撰写本文时,这两个都还在开发中;请参阅参考资料 )。
档案访客
我们的第一个示例演示了新的FileVisitor
API。
设想一种情况,您要递归遍历目录树,在该树下的每个文件和目录处停止,并对找到的每个条目调用自己的回调方法。 在以前的Java版本中,这将是一个痛苦的过程,涉及递归地列出目录,检查目录项并自己调用回调。 在Java 7中,所有这些都是通过FileVisitor
API提供的,使用起来再简单不过了。
第一步是实现自己的FileVisitor
类。 此类包含文件访问者引擎在遍历文件系统时将调用的回调方法。 FileVisitor
接口包含五个方法,在这里按在遍历期间将被调用的典型顺序列出(这里T
表示java.nio.file.Path
或超类):
- 在访问该目录中的条目之前,将调用
FileVisitResult preVisitDirectory( T dir)
。 它返回FileVisitResult
的枚举值之一,以告诉文件访问者API下一步该做什么。 - 当由于某种原因而无法访问目录时
FileVisitResult preVisitDirectoryFailed( T dir, IOException exception)
调用FileVisitResult preVisitDirectoryFailed( T dir, IOException exception)
。 第二个参数中指定了导致访问失败的异常。 - 当访问当前目录中的文件时,将调用
FileVisitResult visitFile( T file, BasicFileAttributes attribs)
。 该文件的属性传递到第二个参数中。 (您将在本文的文件属性部分中了解有关文件属性的更多信息。) - 当文件访问失败时,调用
FileVisitResult visitFileFailed( T file, IOException exception)
。 第二个参数指定导致访问失败的异常。 - 访问目录及其所有子目录后,将调用
FileVisitResult postVisitDirectory( T dir, IOException exception)
。 当目录访问成功时,exception参数为null,或者它包含导致目录访问过早结束的异常。
为了帮助开发人员节省时间,NIO.2提供了FileVisitor
接口的实现: java.nio.file.SimpleFileVisitor
。 此类是最基本的:对于* Failed()
方法,它只抛出异常,对于其他方法,它继续执行而根本不做任何事情! 此类的有用之处在于,您可以使用匿名类来仅覆盖所需的方法。 其余方法默认情况下实现。
清单1显示了如何创建示例FileVisitor
实例:
清单1. FileVisitor
实现
FileVisitor<Path> myFileVisitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir) {
System.out.println("I'm about to visit the "+dir+" directory");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) {
System.out.println("I'm visiting file "+file+" which has size " +attribs.size());
return FileVisitResult.CONTINUE;
}
};
清单1中的FileVisitor
实现应该为它访问的每个目录和文件打印一条消息,并从其BasicFileAttributes
给出文件的大小。
接下来,我们要创建一个Path
从该Path
开始我们的文件访问。 这是使用java.nio.Paths
类完成的:
Path headDir = Paths.get("headDir");
我们可以在java.nio.Files
类上使用以下两种方法之一来开始遍历树:
-
public static void walkFileTree(Path head, FileVisitor<? super Path> fileVisitor)
在head目录下public static void walkFileTree(Path head, FileVisitor<? super Path> fileVisitor)
文件树,同时调用在fileVisitor
实现的回调方法。 -
public static void walkFileTree(Path head, Set<FileVisitOption> options, int depth, FileVisitor<? super Path> fileVisitor)
与前面的方法类似,但是为您提供了两个附加参数来指定访问选项以及深入文件树的目录数量遍历应该走。
我们将使用简单版本的walkFileTree()
方法来开始遍历文件树的过程:
Files.walkFileTree(headDir, myFileVisitor);
假设目录结构如下所示:
headDir
|--- myFile1
|--- mySubDirectory1
| \myFile2
\--- mySubDirectory2
|--- myFile3
\--- mySubdirectory3
\---myFile4
清单2显示了此示例的输出:
清单2. FileVisitor
输出
I'm about to visit the headDir directory
I'm about to visit the headDir\mySubDirectory2 directory
I'm about to visit the headDir\mySubDirectory2\mySubDirectory3 directory
I'm visiting file headDir\mySubDirectory2\mySubDirectory3\myFile4 which has size 2
I'm visiting file headDir\mySubDirectory2\myFile3 which has size 2
I'm about to visit the headDir\mySubDirectory1 directory
I'm visiting file headDir\mySubDirectory1\myFile2 which has size 2
I'm visiting file headDir\myFile1 which has size 2
如您所见,文件遍历是深度优先,但不一定在目录中以任何字母顺序进行。 我们的回调方法已按预期方式调用,并且可以看到树中的所有文件都已列出,并且所有目录都已被访问。
仅用大约15行,我们就创建了一个文件访问器,该访问器将遍历您提供的任何文件树并检查其中包含的文件。 此示例是基本示例,但是可以将回调实现为所需的复杂程度。
目录查看
第二个示例介绍了新的WatchService
API及其相关类的激动人心的世界。
此示例的场景很简单:您想跟踪是否正在创建,修改或删除特定目录(或多个目录)中的任何文件或目录。 您可以使用此信息来更新GUI显示中的文件列表,或者检测可以随后重新加载的配置文件的修改。 在以前的Java版本中,您必须实现一个在单独的线程中运行的代理,该代理跟踪您要观看的目录的所有内容,并不断轮询文件系统以查看是否发生了任何相关的事情。 在Java 7中, WatchService
API提供了监视目录的功能。 它消除了编写自己的文件系统轮询器的所有复杂性,并且在可能的情况下,它基于现有的本机系统API以获得更好的性能。
第一步是通过java.nio.file.FileSystems
类创建WatchService
实例。 我们不会在本文中讨论文件系统的详细信息,因此请知道,在大多数情况下,您将需要获取默认文件系统,然后调用其newWatchService()
方法:
WatchService watchService = FileSystems.getDefault().newWatchService();
现在我们有了监视服务实例,我们想要注册一个监视路径。 我们为希望查看的目录创建一个Path
对象,其方式与文件访问者示例稍有不同,以便稍后可以再次使用其File
实例:
File watchDirFile = new File("watchDir");
Path watchDirPath = watchDirFile.toPath();
Path
类实现java.nio.file.Watchable
接口,并且该接口定义了我们将在此示例中使用的register()
方法。 WatchKey register(WatchService watchService, WatchEvent.Kind<?>... events)
注册使用指定watchService
针对给定的特定事件调用此方法的Path
。 仅当在注册调用中指定事件时,事件才会触发通知。
对于默认的WatchService
实现, java.nio.file.StandardWatchEventKind
类定义了watchEvent.Kind
三个静态实现,可以在register()
调用中使用它们:
-
StandardWatchEventKind.ENTRY_CREATE
表示已在注册的Path
创建了文件或目录。 当文件被重命名或移入该目录时,也会触发ENTRY_CREATE
事件。 -
StandardWatchEventKind.ENTRY_MODIFY
表示已修改注册Path
中的文件或目录。 确切地说,构成修改的事件在某种程度上是特定于平台的,但是可以说,实际上修改文件的内容始终会触发修改事件。 在某些平台上,更改文件的属性也会触发此事件。 -
StandardWatchEventKind.ENTRY_DELETE
指示已从注册的Path
删除了文件或目录。 当文件被重命名或移出该目录时,也会触发ENTRY_DELETE
事件。
对于我们的示例,让我们看一下ENTRY_CREATE
和ENTRY_MODIFY
事件,而不是ENTRY_DELETE
:
WatchKey watchKey = watchDirPath.register(watchService,
StandardWatchEventKind.ENTRY_CREATE, StandardWatchEventKind.ENTRY_MODIFY);
现在,我们的Path
已注册以供观看,并且WatchService
将在后台静默运行,并专心观看该目录。 相同的WatchService
实例可以使用我们上面显示的相同的Path
创建和register()
调用来监视多个目录。
你们中间的观察者可能已经发现register()
方法调用返回了一个我们之前没有遇到过的类: WatchKey
。 此类表示您在WatchService
的注册。 是否WatchService
此引用取决于您,因为WatchService
在触发事件时会向您返回相关的WatchKey
。 然而,做笔记,有没有方法调用,以找出哪些目录WatchKey
与注册,所以如果你看你不妨跟踪哪些多个目录WatchKey
与一起去Path
秒。 完成特定的WatchKey
及其注册的WatchService
只需调用其cancel()
方法即可取消其在WatchService
注册。
现在,我们的Path
已注册,我们可以在方便时使用WatchService
进行签入,以查看是否发生了我们感兴趣的任何事件。 WatchService
提供了三种方法来检查是否发生了令人兴奋的事情:
-
WatchKey poll()
返回发生了某些事件的下一个WatchKey
,如果没有发生已注册的事件,则返回null
。 -
WatchKey poll(long timeout, TimeUnit unit)
采用一个超时和时间单位(java.util.concurrent.TimeUnit
)。 如果在指定时间段内发生事件,则此方法退出,并返回相关的WatchKey
。 如果在超时结束之前没有要返回的WatchKeys
,则此方法返回null
。 -
WatchKey take()
与前面的方法类似,除了它会无限期地等待,直到有一个WatchKey
可以返回为止。
一旦这三个方法之一返回了WatchKey
,即使调用了reset()
方法,也不会再通过poll()
或take()
调用返回它,即使发生注册事件。 WatchService
返回WatchKey
WatchService
,您可以检查其事件,方法是调用WatchKey
的pollEvents()
方法触发该事件,该方法返回一个WatchEvent
的List。
为了说明这一点,清单3中的简单示例延续了我们先前注册的WatchKey
:
清单3.使用pollEvents()
// Create a file inside our watched directory
File tempFile = new File(watchDirFile, "tempFile");
tempFile.createNewFile();
// Now call take() and see if the event has been registered
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
System.out.println(
"An event was found after file creation of kind " + event.kind()
+ ". The event occurred on file " + event.context() + ".");
}
An event was found after file creation of kind ENTRY_CREATE. The event occurred
on file tempFile.
An event was found after file creation of kind ENTRY_MODIFY. The event occurred
on file tempFile.
如您所见,正如预期的那样,我们为新创建的tempFile
获得了ENTRY_CREATE
事件,但还得到了另一个事件。 在某些操作系统上,文件创建或删除有时也可能会产生ENTRY_MODIFY
事件。 如果我们没有注册接收修改事件,那么无论什么操作系统,我们都只会得到ENTRY_CREATE
事件。
示例代码下载中包含扩展的示例代码(在本节的示例中演示了对已注册WatchKey
文件修改和删除)。
文件属性
我们的第三个也是最后一个示例涵盖了使用java.nio.file.attribute
包下的java.nio.file.attribute
获取和设置文件属性的新API。
新的API提供对您无法想象的更多文件属性的访问。 在以前的Java版本中,您只能获得一组基本的文件属性(大小,修改时间,文件是否被隐藏以及它是文件还是目录)。 要获取或修改任何其他文件属性,您必须使用要在其上运行的平台专用的本机代码自己实现此目的-并不容易。 精采的是,Java 7应该允许您通过java.nio.file.attribute
类以简单的方式阅读并在可能的情况下修改扩展的属性集,从而完全抽象出这些操作的特定于平台的性质。
新的API提供了七个属性视图 ,其中一些特定于操作系统。 这些“视图”类允许您获取和设置与之关联的任何属性,并且每个类都有一个对应的属性类,其中包含实际的属性信息。 让我们依次看看它们。
AclFileAttributeView
和AclEntry
AclFileAttributeView
允许您获取并设置特定文件的ACL和文件所有者属性。 它的getAcl()
方法返回一个AclEntry
对象的List
, AclEntry
对象对应于文件上设置的每个权限。 它的setAcl(List<AclEntry>)
方法允许您修改该访问列表。 此属性视图仅适用于Microsoft®Windows®系统。
BasicFileAttributeView
和BasicFileAttributes
此类视图类使您可以基于以前的Java版本中的可用属性获得一组(当然也没有)基本文件属性。 它的readAttributes()
方法返回一个BasicFileAttributes
实例,其中包含上次修改时间,上次访问时间,创建时间,大小和文件类型(常规文件,目录,符号链接或其他)的详细信息。 此属性视图在所有平台上均可用。
让我们看一下这种视图的一个例子。 为了获得特定文件的文件属性视图,我们一如既往地通过为我们感兴趣的文件创建Path
对象来开始:
File attribFile = new File("attribFile");
Path attribPath = attribFile.toPath();
要获取所需的文件属性视图,我们将在Path
上使用getFileAttributeView(Class viewClass)
方法。 要获取attribPath
的BasicFileAttributeView
,我们只需调用:
BasicFileAttributeView basicView
= attribPath.getFileAttributeView(BasicFileAttributeView.class);
如前所述,获得BasicFileAttributes
从BasicFileAttributeView
,我们只需要调用它的readAttributes()
方法:
BasicFileAttributes basicAttribs = basicView.readAttributes();
就是这样。 现在,您具有该文件的所有基本文件属性,可以执行您希望执行的任何操作。 对于BasicFileAttributes
,只能更改创建,最后修改和最后访问的时间(因为更改文件的大小或类型没有意义)。 要更改这些设置,我们可以使用java.nio.file.attribute.FileTime
类创建一个新的时间,然后在BasicFileAttributeView
上调用setTimes()
方法。 例如,我们可以将文件的上次修改时间向后移动一分钟:
FileTime newModTime
= FileTime.fromMillis(basicAttribs.lastModifiedTime().toMillis() + 60000);
basicView.setTimes(newModTime, null, null);
两个null
表示我们不想更改此文件的上次访问时间或创建时间。 如果以与我们之前相同的方式再次检查基本属性,则应该看到上次修改的时间已更改,但是创建时间和上次访问时间保持不变。
DosFileAttributeView
和DosFileAttributes
该视图类允许您获取特定于DOS的属性。 (您可能会猜到,该视图仅适用于Windows系统。)其readAttributes()
方法返回DosFileAttributes
实例,该实例包含有关文件是否为只读,隐藏,系统文件和存档的详细信息。 该视图还具有针对每个这些属性的set*(boolean)
方法。
FileOwnerAttributeView
和UserPrincipal
该视图类允许您获取和设置特定文件的所有者。 它的getOwner()
方法返回一个UserPrincipal
(同样在java.nio.file.attribute
包中),后者又具有getName()
方法,该方法返回包含所有者名称的String
。 该视图还提供了setOwner(UserPrincipal)
方法,允许您更改文件的所有者。 该视图在所有平台上均可用。
FileStoreSpaceAttributeView
和FileStoreSpaceAttributes
这个名称易记的类使您可以获取有关特定文件存储的信息。 它的readAttributes()
方法返回一个FileStoreSpaceAttributes
实例,该实例包含文件存储上的总空间,未分配空间和可用空间的详细信息。 该视图在所有平台上均可用。
PosixFileAttributeView
和PosixFileAttributes
该视图类仅在UNIX®系统上可用,允许您获取和设置特定于POSIX(便携式操作系统接口)的属性。 它的readAttributes()
方法返回一个PosixFileAttributes
实例,其中包含该文件的所有者,组所有者和文件许可权的详细信息(通常使用UNIX chmod
命令进行设置)。 该视图还提供setOwner(UserPrincipal)
, setGroup(GroupPrincipal)
和setPermissions(Set<PosixFilePermission>)
方法来修改这些属性。
UserDefinedFileAttributeView
和String
该视图类仅在Windows上可用,允许您获取和设置文件的扩展属性 。 这些属性与其他属性不同,它们只是名称-值对,可以设置为您想要的任何值。 如果要在不更改文件内容的情况下向文件中添加一些隐藏的元数据,这将很有用。 该视图提供了一个list()
方法,该方法返回相关文件的扩展属性的String
名称List
。
要在获得特定属性的名称后获取其内容,该视图具有一个size(String name)
方法以返回属性值的大小,以及一个read(String name, ByteBuffer dest)
方法以将该属性值读取到ByteBuffer
。 该视图还提供了write(String name, ByteBuffer source)
方法来创建或更改属性,还提供了delete(String name)
方法来完全删除现有属性。
这可能是最有趣的新属性视图,因为它允许您将具有任意String
名称和ByteBuffer
值的属性添加到文件。 没错-它的值是ByteBuffer
,因此您可以在其中存储任何二进制数据。
首先,我们将获得属性视图:
UserDefinedFileAttributeView userView
= attribPath.getFileAttributeView(UserDefinedFileAttributeView.class);
要获取此文件的用户定义属性名称的list()
,我们在视图上调用list()
方法:
List<String> attribList = userView.list();
一旦有了特定的属性名称,我们希望为其获取关联的值,则为该值分配一个合适大小的ByteBuffer
,然后调用视图的read(String, ByteBuffer)
方法:
ByteBuffer attribValue = ByteBuffer.allocate(userView.size(attribName));
userView.read(attribName, attribValue);
现在, attribValue
包含为该特定属性存储的所有数据。 要设置自己的属性,您只需要创建ByteBuffer
并用所需的数据填充它,然后在视图上调用write(String, ByteBuffer)
方法:
userView.write(attribName, attribValue);
编写属性可以创建该属性,也可以覆盖具有相同名称的现有属性。
这样,我们得出了第三个也是最后一个例子。 示例代码下载中包含演示四个属性视图( BasicFileAttributeView
, FileOwnerAttributeView
, FileStoreSpaceAttributeView
和UserDefinedAttributeView
)的完整示例代码。
结论
除了本文涵盖的主题之外,还有许多其他的NIO.2文件API。 Java 7附带了创建,检查和修改符号链接的新功能。 还有一些新类允许访问有关文件系统的低级信息,甚至允许提供程序(称为FileSystem
和FileStore
)访问所需的任何类型的文件系统。
总之,NIO.2为Java开发人员提供了一组简单,一致且功能强大的API,用于与文件系统进行交互。 其目的是抽象一些处理文件和目录的复杂平台特定细节,并为程序员提供更大的功能和灵活性,并且它已经做到了这一点。
翻译自: https://www.ibm.com/developerworks/java/library/j-nio2-2/index.html