1. 概述

本文将深入探讨 Java 7 NIO.2 文件系统 API 的高级特性——文件属性 API。如果你需要先了解基础概念,可以参考我们之前关于 File APIPath API 的文章。

所有文件系统操作所需的类都打包在 java.nio.file 包中:

import java.nio.file.*;

2. 基础文件属性

首先了解所有文件系统共有的基础属性,由 BasicFileAttributeView 提供,它存储所有强制性和可选的可见文件属性。

我们可以通过创建 HOME 路径并获取其基础属性视图来探索当前机器用户主目录的基础属性:

String HOME = System.getProperty("user.home");
Path home = Paths.get(HOME);
BasicFileAttributeView basicView = 
  Files.getFileAttributeView(home, BasicFileAttributeView.class);

执行上述步骤后,我们可以在一次批量操作中读取该路径指向的所有属性:

BasicFileAttributes basicAttribs = basicView.readAttributes();

现在我们可以探索不同的通用属性,这些属性在应用程序中特别有用,尤其是在条件语句中。

我们可以从基础属性容器中查询文件大小:

@Test
public void givenPath_whenGetsFileSize_thenCorrect() {
    long size = basicAttribs.size();
    assertTrue(size > 0);
}

也可以检查是否为目录:

@Test
public void givenPath_whenChecksIfDirectory_thenCorrect() {
    boolean isDir = basicAttribs.isDirectory();
    assertTrue(isDir);
}

或常规文件:

@Test
public void givenPath_whenChecksIfFile_thenCorrect() {
    boolean isFile = basicAttribs.isRegularFile();
    assertFalse(isFile);
}

Java NIO.2 现在可以处理文件系统中的符号链接(软链接)。这些是我们通常称为快捷方式的文件或目录。

检查文件是否为符号链接:

@Test
public void givenPath_whenChecksIfSymLink_thenCorrect() {
    boolean isSymLink = basicAttribs.isSymbolicLink();
    assertFalse(isSymLink);
}

在少数情况下,我们可以调用 isOther API 检查文件是否不属于常规文件、目录或符号链接的常见类别:

@Test
public void givenPath_whenChecksIfOther_thenCorrect() {
    boolean isOther = basicAttribs.isOther();
    assertFalse(isOther);
}

获取文件创建时间:

FileTime created = basicAttribs.creationTime();

获取最后修改时间:

FileTime modified = basicAttribs.lastModifiedTime();

获取最后访问时间:

FileTime accessed = basicAttribs.lastAccessTime();

以上示例都返回 FileTime 对象,这比单纯的时间戳更实用。

例如,我们可以轻松比较两个文件时间,以确定哪个事件发生在前或后:

@Test
public void givenFileTimes_whenComparesThem_ThenCorrect() {
    FileTime created = basicAttribs.creationTime();
    FileTime modified = basicAttribs.lastModifiedTime();
    FileTime accessed = basicAttribs.lastAccessTime();

    assertTrue(0 >= created.compareTo(accessed));
    assertTrue(0 <= modified.compareTo(created));
    assertTrue(0 == created.compareTo(created));
}

compareTo API 的工作方式与 Java 中其他可比较对象相同。如果调用对象小于参数,则返回负值(如第一个断言中,创建时间肯定早于访问时间)。在第二个断言中,我们得到正整数值,因为修改只能在创建事件之后发生。当比较的时间相等时,返回 0。

拥有 FileTime 对象后,我们可以根据需要将其转换为大多数其他单位(天、小时、分钟、秒、毫秒等)。通过调用适当的 API 实现:

accessed.to(TimeUnit.SECONDS);
accessed.to(TimeUnit.HOURS);
accessed.toMillis();

我们还可以调用其 toString API 打印人类可读的文件时间:

accessed.toString();

输出 ISO 时间格式的有用信息:

2016-11-24T07:52:53.376Z

我们还可以通过调用视图的 setTimes(modified, accessed, created) API 更改时间属性。传入新的 FileTime 对象表示要更改的时间,传入 null 表示不更改。

要将最后访问时间更改为一分钟后:

FileTime newAccessTime = FileTime.fromMillis(
  basicAttribs.lastAccessTime().toMillis() + 60000);
basicView.setTimes(null, newAccessTime , null);

此更改将持久保存在实际文件中,机器上使用文件系统的任何其他应用程序都能看到。

3. 文件空间属性

在 Windows、Linux 或 Mac 上打开"我的电脑"时,通常可以看到存储驱动器的空间信息图形分析。

Java NIO.2 使这种高级功能变得非常简单。它与底层文件系统交互以检索此信息,而我们只需调用简单的 API。

我们可以使用 FileStore 类检查存储驱动器并获取重要信息,如其大小、已用空间和未用空间。

要获取文件系统中任意文件位置的 FileStore 实例,我们使用 Files 类的 getFileStore API:

Path file = Paths.get("file");
FileStore store = Files.getFileStore(file);

FileStore 实例专门表示指定文件所在的文件存储,而不是文件本身。获取总空间:

long total = store.getTotalSpace();

获取已用空间:

long used = store.getTotalSpace() - store.getUnallocatedSpace();

我们不太可能采用这种方法,而更可能采用下一种方法。

更常见的是,我们可能需要获取所有文件存储的存储信息。要在程序中模拟"我的电脑"的图形驱动器空间信息,我们可以使用 FileSystem 类枚举文件存储:

Iterable<FileStore> fileStores = FileSystems.getDefault().getFileStores();

然后我们可以遍历返回的值,并根据需要对信息执行任何操作,例如更新图形用户界面:

for (FileStore fileStore : fileStores) {
    long totalSpace = fileStore.getTotalSpace();
    long unAllocated = fileStore.getUnallocatedSpace();
    long usable = fileStore.getUsableSpace();
}

注意,所有返回值都以字节为单位。我们可以使用基本算术将其转换为合适的单位以及计算其他信息(如已用空间)。

未分配空间和可用空间的区别在于 JVM 的可访问性。

可用空间是 JVM 可用的空间,而未分配空间是底层文件系统看到的可用空间。因此,可用空间有时可能小于未分配空间。

4. 文件所有者属性

要检查文件所有权信息,我们使用 FileOwnerAttributeView 接口。它为我们提供了所有权信息的高级视图。

我们可以创建 FileOwnerAttributeView 对象,如下所示:

Path path = Paths.get(HOME);
FileOwnerAttributeView ownerView = Files.getFileAttributeView(
  attribPath, FileOwnerAttributeView.class);

从上述视图获取文件所有者:

UserPrincipal owner = ownerView.getOwner();

除了获取所有者名称用于其他任意目的外,我们无法以编程方式对此对象执行太多操作:

String ownerName = owner.toString();

5. 用户定义文件属性

在某些情况下,文件系统中定义的文件属性无法满足你的需求。如果遇到这种情况并需要在文件上设置自己的属性,那么 UserDefinedFileAttributeView 接口将派上用场:

Path path = Paths.get("somefile");
UserDefinedFileAttributeView userDefView = Files.getFileAttributeView(
  attribPath, UserDefinedFileAttributeView.class);

要检索已为上述视图表示的文件定义的用户定义属性列表:

List<String> attribList = userDefView.list();

要在文件上设置用户定义属性,我们使用以下惯用法:

String name = "attrName";
String value = "attrValue";
userDefView.write(name, Charset.defaultCharset().encode(value));

当需要访问用户定义属性时,可以遍历视图返回的属性列表,并使用以下惯用法检查它们:

ByteBuffer attrValue = ByteBuffer.allocate(userView.size(attrName));
userDefView.read(attribName, attribValue);
attrValue.flip();
String attrValue = Charset.defaultCharset().decode(attrValue).toString();

要从文件中删除用户定义属性,我们只需调用视图的 delete API:

userDefView.delete(attrName);

6. 结论

本文探讨了 Java 7 NIO.2 文件系统 API 中一些不太常用的功能,特别是文件属性 API。

本文使用的示例的完整源代码可在 GitHub 项目 中获取。


原始标题:A Guide To NIO2 File Attribute APIs