1. 概述
本文将深入探讨 Java 7 NIO.2 文件系统 API 的高级特性——文件属性 API。如果你需要先了解基础概念,可以参考我们之前关于 File API 和 Path 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 项目 中获取。