Java7-新特性秘籍(二)

72 阅读1小时+

Java7 新特性秘籍(二)

原文:zh.annas-archive.org/md5/5FB42CDAFBC18FB5D8DD681ECE2B0206

译者:飞龙

协议:CC BY-NC-SA 4.0

第三章:获取文件和目录信息

在本章中,我们将涵盖以下内容:

  • 确定文件内容类型

  • 使用 getAttribute 方法逐个获取单个属性

  • 获取文件属性的映射

  • 获取文件和目录信息

  • 确定操作系统对属性视图的支持

  • 使用 BasicFileAttributeView 维护基本文件属性

  • 使用 PosixFileAttributeView 维护 POSIX 文件属性

  • 使用 DosFileAttributeView 维护 FAT 表属性

  • 使用 FileOwnerAttributeView 维护文件所有权属性

  • 使用 AclFileAttributeView 维护文件的 ACL

  • 使用 UserDefinedFileAttributeView 维护用户定义的文件属性

介绍

许多应用程序需要访问文件和目录信息。这些信息包括文件是否可以执行,文件的大小,文件的所有者,甚至其内容类型等属性。在本章中,我们将研究获取有关文件或目录信息的各种技术。我们根据所需的访问类型组织了配方。

使用java.nio.file.Files类获取文件和目录信息的五种一般方法如下:

  • 使用Files类的特定方法,如isDirectory方法,逐个获取单个属性。这在获取文件和目录信息配方中有详细说明。

  • 使用Files类的getAttribute方法逐个获取单个属性。这在使用 getAttribute 方法逐个获取单个属性配方中有详细说明。

  • 使用readAttributes方法返回使用String指定要返回的属性的映射。这在获取文件属性的映射配方中有解释。

  • 使用readAttributes方法与BasicFileAttributes派生类返回该属性集的属性类。这在使用 BasicFileAttributeView 维护基本文件属性配方中有详细说明。

  • 使用getFileAttributes方法返回提供对特定属性集的访问的视图。这也在使用 BasicFileAttributeView 方法维护基本文件属性配方中有详细说明。它在配方的*还有更多..*部分中找到。

通过几种方法支持对属性的动态访问,并允许开发人员使用String指定属性。Files类的getAttribute方法代表了这种方法。

Java 7 引入了一些基于文件视图的接口。视图只是关于文件或目录的信息的一种组织方式。例如,AclFileAttributeView提供了与文件的访问控制列表ACL)相关的方法。FileAttributeView接口是提供特定类型文件信息的其他接口的基接口。java.nio.file.attribute包中的子接口包括以下内容:

  • AclFileAttributeView:用于维护文件的 ACL 和所有权属性

  • BasicFileAttributeView:用于访问有关文件的基本信息并设置与时间相关的属性

  • DosFileAttributeView:设计用于与传统磁盘操作系统DOS)文件属性一起使用

  • FileOwnerAttributeView:用于维护文件的所有权

  • PosixFileAttributeView:支持便携式操作系统接口POSIX)属性

  • UserDefinedFileAttributeView:支持文件的用户定义属性

视图之间的关系如下所示:

注意

低级接口继承自它们上面的接口。

介绍

readAttributes 方法的第二个参数指定要返回的属性类型。支持三个属性接口,它们的关系如下图所示。这些接口提供了访问它们对应的视图接口的方法:

Introduction

每个视图都有一个专门的配方。这里不讨论 FileStoreAttributeView,但在 第四章 的 管理文件和目录 中有相关内容。

本章中示例使用的文件和目录结构在 第二章 的介绍中有描述,使用路径定位文件和目录

确定文件内容类型

文件的类型通常可以从其扩展名推断出来。但这可能会误导,具有相同扩展名的文件可能包含不同类型的数据。Files 类的 probeContentType 方法用于确定文件的内容类型(如果可能)。当应用程序需要一些指示文件内容以便处理时,这是很有用的。

准备工作

为了确定内容类型,需要完成以下步骤:

  1. 获取代表文件的 Path 对象。

  2. 使用 Path 对象作为 probeContentType 方法的参数。

  3. 使用结果处理文件。

操作步骤...

  1. 创建一个新的控制台应用程序。将三种不同类型的文件添加到 /home/docs 目录中。使用以下内容作为 main 方法。虽然你可以使用任何你选择的文件,但本示例使用了一个文本文件,一个 Word 文档和一个可执行文件:
public static void main(String[] args) throws Exception {
displayContentType("/home/docs/users.txt");
displayContentType("/home/docs/Chapter 2.doc");
displayContentType("/home/docs/java.exe");
}
static void displayContentType(String pathText) throws Exception {
Path path = Paths.get(pathText);
String type = Files.probeContentType(path);
System.out.println(type);
}

  1. 执行应用程序。你的输出应该如下所示。返回的类型取决于你使用的实际文件:

text/plain

application/msword

application/x-msdownload

工作原理...

创建了一个 java.nio.file.Path 变量,并分配给了三个不同的文件。对每个文件执行了 Files 类的 probeContentPath 方法。返回的结果是一个 String,用于说明目的。probeContentType 方法会抛出一个 java.io.IOException,我们通过让 displayConentType 方法和 main 方法抛出一个基类异常来处理这个异常。probeContentPath 方法也可能会抛出一个 java.lang.SecurityException,但你不需要处理它。

在本示例中使用的文件中,第一个文件是一个文本文件。返回的类型是 text/plain。另外两个是一个 Word 文档和可执行文件 java.exe。返回的类型分别是 application/mswordapplication/x-msdownload

还有更多...

该方法的结果是一个 String,由 多用途互联网邮件扩展 (MIME):RFC 2045:多用途互联网邮件扩展(MIME)第一部分:互联网消息正文的格式 定义。这允许使用 RFC 2045 语法规范解析 String。如果无法识别内容类型,则返回 null。

MIME 类型由类型和子类型以及一个或多个可选参数组成。类型和子类型之间使用斜杠分隔。在前面的输出中,文本文档的类型是 text,子类型是 plain。另外两种类型都是 application 类型,但子类型不同。以 x- 开头的子类型是非标准的。

probeContentType方法的实现取决于系统。该方法将使用java.nio.file.spi.FileTypeDetector实现来确定内容类型。它可能检查文件名或可能访问文件属性以确定文件内容类型。大多数操作系统将维护文件探测器列表。从此列表中加载并用于确定文件类型。FileTypeDetector类没有扩展,并且目前无法确定哪些文件探测器可用。

使用getAttribute方法一次获取一个属性

如果您有兴趣获取单个文件属性,并且知道属性的名称,则Files类的getAttribute方法简单且易于使用。它将返回基于表示属性的String的文件信息。本食谱的第一部分说明了getAttribute方法的简单用法。其他可用的属性列在本食谱的更多内容部分中。

准备就绪

获取单个文件属性值:

  1. 创建一个表示感兴趣的文件的Path对象。

  2. 将此对象用作getAttribute方法的第一个参数。

  3. 使用包含属性名称的String作为方法的第二个参数。

如何做...

  1. 创建一个新的控制台应用程序并使用以下main方法。在此方法中,我们确定文件的大小如下:
public static void main(String[] args) {
try {
Path path = FileSystems.getDefault().getPath("/home/docs/users.txt");
System.out.println(Files.getAttribute(path, "size"));
}
catch (IOException ex) {
System.out.println("IOException");
}
}

  1. 输出将如下所示,并将取决于所使用文件的实际大小:

30

它是如何工作的...

创建了一个表示users.txt文件的Path。然后将此路径用作Files类的getAttribute方法的第一个参数。执行代码时,将显示文件的大小。

更多内容...

Files类的getAttribute方法具有以下三个参数:

  • 一个表示文件的Path对象

  • 包含属性名称的String

  • 在处理符号文件时使用的可选LinkOption

以下表格列出了可以与此方法一起使用的有效属性名称:

属性名称数据类型
lastModifiedTimeFileTime
lastAccessTimeFileTime
creationTimeFileTime
size长整型
isRegularFile布尔值
isDirectory布尔值
isSymbolicLink布尔值
isOther布尔值
fileKey对象

如果使用无效的名称,则会发生运行时错误。这是这种方法的主要弱点。例如,如果名称拼写错误,我们将收到运行时错误。此方法如下所示,指定的属性在属性String末尾有一个额外的s

System.out.println(Files.getAttribute(path, "sizes"));

当应用程序执行时,您应该获得类似以下的结果:

线程"main"中的异常 java.lang.IllegalArgumentException:未识别'sizes'

在 sun.nio.fs.AbstractBasicFileAttributeView$AttributesBuilder.(AbstractBasicFile AttributeView.java:102)

在 sun.nio.fs.AbstractBasicFileAttributeView$AttributesBuilder.create(AbstractBasicFileAttributeView.java:112)

在 sun.nio.fs.AbstractBasicFileAttributeView.readAttributes(AbstractBasicFileAttributeView.java:166)

在 sun.nio.fs.AbstractFileSystemProvider.readAttributes(AbstractFileSystemProvider.java:92)

在 java.nio.file.Files.readAttributes(Files.java:1896)

在 java.nio.file.Files.getAttribute(Files.java:1801)

在 packt.SingleAttributeExample.main(SingleAttributeExample.java:15)

Java 结果:1

可以按照获取文件属性映射食谱中的描述获取文件属性列表。这可以用来避免使用无效名称。

获取文件属性映射

访问文件属性的另一种方法是使用Files类的readAttributes方法。该方法有两个重载版本,在第二个参数和返回的数据类型上有所不同。在本示例中,我们将探讨返回java.util.Map对象的版本,因为它允许在返回的属性上更灵活。该方法的第二个版本在一系列食谱中讨论,每个食谱都专门讨论一类属性。

准备就绪

要获取Map对象形式的属性列表,需要执行以下步骤:

  1. 创建一个表示文件的Path对象。

  2. Files类应用静态的readAttributes方法。

  3. 指定其参数的值:

  • 表示感兴趣文件的Path对象

  • 表示要返回的属性的String参数

  • 可选的第三个参数,指定是否应该跟踪符号链接

如何做...

  1. 创建一个新的控制台应用程序。使用以下main方法:
public static void main(String[] args) throws Exception {
Path path = Paths.getPath("/home/docs/users.txt");
try {
Map<String, Object> attrsMap = Files.readAttributes(path, "*");
Set<String> keys = attrsMap.keySet();
for(String attribute : keys) {
out.println(attribute + ": "
+ Files.getAttribute(path, attribute));
}
}
}

  1. 执行应用程序。您的输出应该类似于以下内容:

lastModifiedTime: 2011-09-06T01:26:56.501665Z

fileKey: null

isDirectory: false

lastAccessTime: 2011-09-06T21:14:11.214057Z

isOther: false

isSymbolicLink: false

isRegularFile: true

creationTime: 2011-09-06T21:14:11.214057Z

大小:30

它是如何工作的...

示例中使用了docs目录中的users.txt文件。声明了一个键类型为String,值类型为ObjectMap对象,然后为其赋予了readAttributes方法的值。使用Map接口的keySet方法创建了一个java.util.Set对象。这使我们可以访问Map的键和值。在 for each 循环中,将集合的每个成员用作getAttribute方法的参数。文件的相应属性和其值将被显示。getAttribute方法在使用 getAttribute 方法逐个获取属性食谱中有解释。

在这个例子中,我们使用字符串字面值"*"作为第二个参数。这个值指示方法返回文件的所有可用属性。正如我们很快将看到的,其他字符串值可以用来获得不同的结果。

readAttributes方法是一个原子文件系统操作。默认情况下,会跟踪符号链接。要指示该方法不要跟踪符号链接,使用java.nio.file包的LinkOption.NOFOLLOW_LINKS枚举常量,如下所示:

Map<String, Object> attrsMap = Files.readAttributes(path, "*", LinkOption.NOFOLLOW_LINKS);

还有更多...

该方法的有趣之处在于它的第二个参数。String参数的语法包括一个可选的viewName,后面跟着一个冒号,然后是属性列表。viewName通常是以下之一:

  • acl

  • 基本

  • 所有者

  • 用户

  • dos

  • posix

每个viewNames对应于一个视图接口的名称。

属性列表是一个逗号分隔的属性列表。属性列表可以包含零个或多个元素。如果使用无效的元素名称,则会被忽略。使用星号将返回与该viewName关联的所有属性。如果不包括viewName,则会返回所有基本文件属性,就像前面所示的那样。

以基本视图为例,以下表格说明了我们如何选择要返回的属性:

String返回的属性
"*"所有基本文件属性
"basic:*"所有基本文件属性
"basic:isDirectory,lastAccessTime"isDirectorylastAccessTime属性
"isDirectory,lastAccessTime"isDirectorylastAccessTime属性
""无 - 会生成java.lang.IllegalArgumentException

String属性在除基本视图以外的视图中使用方式相同。

提示

属性String中不能有嵌入的空格。例如,String, "basic:isDirectory, lastAccessTime",逗号后面有一个空格会导致IllegalArgumentException

获取文件和目录信息

经常需要检索有关文件或目录的基本信息。本教程将介绍java.nio.file.Files类如何提供直接支持。这些方法仅提供对文件和目录信息的部分访问,并以isRegularFile等方法为代表。此类方法的列表可在本教程的更多信息部分找到。

准备就绪

要使用Files类的方法显示信息很容易,因为这些方法大多数(如果不是全部)都是静态的。这意味着这些方法可以轻松地针对Files类名称执行。要使用这种技术:

  1. 创建一个表示文件或目录的Path对象。

  2. Path对象用作适当的Files类方法的参数。

如何做...

  1. 为了演示如何获取文件属性,我们将开发一个方法来显示文件的属性。创建一个包含以下main方法的新控制台应用程序。在该方法中,我们创建一个文件的引用,然后调用displayFileAttribute方法。它使用几种方法来显示有关路径的信息,如下所示:
public static void main(String[] args) throws Exception {
Path path = FileSystems.getDefault().getPath("/home/docs/users.txt");
displayFileAttributes(path);
}
private static void displayFileAttributes(Path path) throws Exception {
String format =
"Exists: %s %n"
+ "notExists: %s %n"
+ "Directory: %s %n"
+ "Regular: %s %n"
+ "Executable: %s %n"
+ "Readable: %s %n"
+ "Writable: %s %n"
+ "Hidden: %s %n"
+ "Symbolic: %s %n"
+ "Last Modified Date: %s %n"
+ "Size: %s %n";
System.out.printf(format,
Files.exists(path, LinkOption.NOFOLLOW_LINKS),
Files.notExists(path, LinkOption.NOFOLLOW_LINKS),
Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS),
Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS),
Files.isExecutable(path),
Files.isReadable(path),
Files.isWritable(path),
Files.isHidden(path),
Files.isSymbolicLink(path),
Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS),
Files.size(path));
}

  1. 执行程序。您的输出应如下所示:

存在:true

不存在:false

目录:false

常规:true

可执行:true

可读:true

可写:true

隐藏:false

符号链接:false

上次修改日期:2011-10-20T03:18:20.338139Z

大小:29

它是如何工作的...

创建了指向users.txt文件的Path。然后将此Path对象传递给displayFileAttribute方法,该方法显示了文件的许多属性。返回这些属性的方法在以下表格中进行了总结:

方法描述
exists如果文件存在则返回true
notExists如果文件不存在则返回true
isDirectory如果路径表示目录则返回true
isRegularFile如果路径表示常规文件则返回true
isExecutable如果文件可执行则返回true
isReadable如果文件可读则返回true
isWritable如果文件可写则返回true
isHidden如果文件是隐藏的且对非特权用户不可见则返回true
isSymbolicLink如果文件是符号链接则返回true
getLastModifiedTime返回文件上次修改的时间
size返回文件的大小

其中几种方法具有第二个参数,指定如何处理符号链接。当存在LinkOption.NOFOLLOW_LINKS时,符号链接不会被跟踪。第二个参数是可选的。如果省略,则不会跟踪符号链接。符号链接在第二章的使用路径定位文件和目录中的管理符号链接教程中进行了讨论。

更多信息...

以下表格总结了抛出的异常以及方法是否为非原子操作。如果调用线程无权读取文件,则可能会抛出SecurityException

注意

当一个方法被称为非原子时,这意味着其他文件系统操作可能会与该方法同时执行。非原子操作可能导致不一致的结果。也就是说,在这些方法执行时,可能会导致对方法目标的并发操作可能修改文件的状态。在使用这些方法时应考虑到这一点。

这些方法标记为过时的结果在返回时不一定有效。也就是说,不能保证任何后续访问都会成功,因为文件可能已被删除或以其他方式修改。

被指定为无法确定的方法表示,如果无法确定结果,则可能返回 false。例如,如果 exists 方法无法确定文件是否存在,则会返回 false。它可能存在,但该方法无法确定它是否存在:

方法SecurityExceptionIOException非原子过时无法确定
exists  
notExists  
isDirectory   
isRegularFile   
isExecutable 
isReadable 
isWritable 
isHidden   
isSymbolicLink   
getLastModifiedTime   
size   

请注意,notExists 方法不是 exists 方法的反义词。使用任一方法,可能无法确定文件是否存在。在这种情况下,两种方法都将返回 false

isRegularFile 确定文件是否为常规文件。如果 isDirectory, isSymbolicLinkisRegularFile 方法返回 false,则可能是因为:

  • 它不是这些类型之一

  • 如果文件不存在或

  • 如果无法确定它是文件还是目录

对于这些方法,它们在 BasicFileAttributes 接口中对应的方法可能会提供更好的结果。这些方法在 使用 BasicFileAttributeView 维护基本文件属性 部分中有介绍。

isExecutable 方法检查文件是否存在,以及 JVM 是否有执行文件的访问权限。如果文件是一个目录,则该方法确定 JVM 是否有足够的权限来搜索该目录。如果:

  • 文件不存在

  • 文件不可执行

  • 如果无法确定是否可执行

隐藏的含义取决于系统。在 UNIX 系统上,如果文件名以句点开头,则文件是隐藏的。在 Windows 上,如果设置了 DOS 隐藏属性,则文件是隐藏的。

确定操作系统对属性视图的支持

操作系统可能不支持 Java 中的所有属性视图。有三种基本技术可以确定支持哪些视图。知道支持哪些视图可以让开发人员避免在尝试使用不受支持的视图时可能发生的异常。

准备工作

这三种技术包括使用:

  • 使用 java.nio.file.FileSystem 类的 supportedFileAttributeViews 方法返回一个包含所有支持的视图的集合。

  • 使用 java.nio.file.FileStore 类的 supportsFileAttributeView 方法和一个类参数。如果该类受支持,则该方法将返回 true

  • 使用 FileStore 类的 supportsFileAttributeView 方法和一个 String 参数。如果该 String 表示的类受支持,则该方法将返回 true

第一种方法是最简单的,将首先进行说明。

如何做...

  1. 创建一个新的控制台应用程序,其中包含以下 main 方法。在这个方法中,我们将显示当前系统支持的所有视图,如下所示:
public static void main(String[] args)
Path path = Paths.get("C:/home/docs/users.txt");
FileSystem fileSystem = path.getFileSystem();
Set<String> supportedViews = fileSystem.supportedFileAttributeViews();
for(String view : supportedViews) {
System.out.println(view);
}
}

  1. 当应用在 Windows 7 系统上执行时,应该会得到以下输出:

acl

basic

owner

user

dos

  1. 当应用在 Ubuntu 10.10 版本下执行时,应该会得到以下输出:

basic

owner

user

unix

dos

posix

请注意,acl视图不受支持,而unixposix视图受支持。在 Java 7 发布版中没有UnixFileAttributeView。但是,该接口可以作为 JSR203-backport 项目的一部分找到。

它是如何工作的...

users.txt文件创建了一个Path对象。接下来使用getFileSystem方法获取了该Path的文件系统。FileSystem类具有supportedFileAttributeViews方法,该方法返回一个表示支持的视图的字符串集合。然后使用 for each 循环显示每个字符串值。

还有更多...

还有另外两种方法可以用来确定支持哪些视图:

  • 使用带有类参数的supportsFileAttributeView方法

  • 使用带有String参数的supportsFileAttributeView方法

这两种技术非常相似。它们都允许您测试特定的视图。

使用带有类参数的 supportsFileAttributeView 方法

重载的supportsFileAttributeView方法接受表示所讨论的视图的类对象。将以下代码添加到上一个示例的main方法中。在这段代码中,我们确定支持哪些视图:

try {
FileStore fileStore = Files.getFileStore(path);
System.out.println("FileAttributeView supported: " + fileStore.supportsFileAttributeView(
FileAttributeView.class));
System.out.println("BasicFileAttributeView supported: " + fileStore.supportsFileAttributeView(
BasicFileAttributeView.class));
System.out.println("FileOwnerAttributeView supported: " + fileStore.supportsFileAttributeView(
FileOwnerAttributeView.class));
System.out.println("AclFileAttributeView supported: " + fileStore.supportsFileAttributeView(
AclFileAttributeView.class));
System.out.println("PosixFileAttributeView supported: " + fileStore.supportsFileAttributeView(
PosixFileAttributeView.class));
System.out.println("UserDefinedFileAttributeView supported: " + fileStore.supportsFileAttributeView(
UserDefinedFileAttributeView.class));
System.out.println("DosFileAttributeView supported: " + fileStore.supportsFileAttributeView(
DosFileAttributeView.class));
}
catch (IOException ex) {
System.out.println("Attribute view not supported");
}

在 Windows 7 机器上执行时,您应该获得以下输出:

FileAttributeView supported: false

BasicFileAttributeView supported: true

FileOwnerAttributeView supported: true

AclFileAttributeView supported: true

PosixFileAttributeView supported: false

UserDefinedFileAttributeView supported: true

DosFileAttributeView supported: true

使用带有String参数的supportsFileAttributeView方法

重载的supportsFileAttributeView方法接受一个String对象的工作方式类似。将以下代码添加到main方法的 try 块中:

System.out.println("FileAttributeView supported: " + fileStore.supportsFileAttributeView(
"file"));
System.out.println("BasicFileAttributeView supported: " + fileStore.supportsFileAttributeView(
"basic"));
System.out.println("FileOwnerAttributeView supported: " + fileStore.supportsFileAttributeView(
"owner"));
System.out.println("AclFileAttributeView supported: " + fileStore.supportsFileAttributeView(
"acl"));
System.out.println("PosixFileAttributeView supported: " + fileStore.supportsFileAttributeView(
"posix"));
System.out.println("UserDefinedFileAttributeView supported: " + fileStore.supportsFileAttributeView(
"user"));
System.out.println("DosFileAttributeView supported: " + fileStore.supportsFileAttributeView(
"dos"));

在 Windows 7 平台上执行时,您应该获得以下输出:

FileAttributeView supported: false

BasicFileAttributeView supported: true

FileOwnerAttributeView supported: true

AclFileAttributeView supported: true

PosixFileAttributeView supported: false

UserDefinedFileAttributeView supported: true

DosFileAttributeView supported: true

使用 BasicFileAttributeView 维护基本文件属性

java.nio.file.attribute.BasicFileAttributeView提供了一系列方法,用于获取有关文件的基本信息,例如其创建时间和大小。该视图具有一个readAttributes方法,该方法返回一个BasicFileAttributes对象。BasicFileAttributes接口具有几种用于访问文件属性的方法。该视图提供了一种获取文件信息的替代方法,而不是由Files类支持的方法。该方法的结果有时可能比Files类的结果更可靠。

准备工作

有两种方法可以获取BasicFileAttributes对象。第一种方法是使用readAttributes方法,该方法使用BasicFileAttributes.class作为第二个参数。第二种方法使用getFileAttributeView方法,并在本章的*更多内容..*部分中进行了探讨。

Files类的readAttributes方法最容易使用:

  1. 将表示感兴趣的文件的Path对象用作第一个参数。

  2. BasicFileAttributes.class用作第二个参数。

  3. 使用返回的BasicFileAttributes对象方法来访问文件属性。

这种基本方法用于本章中所示的其他视图。只有属性视图类不同。

操作步骤...

  1. 创建一个新的控制台应用程序。使用以下main方法。在该方法中,我们创建了一个BasicFileAttributes对象,并使用其方法来显示有关文件的信息:
public static void main(String[] args) {
Path path
= FileSystems.getDefault().getPath("/home/docs/users.txt");
try {
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("Creation Time: " + attributes.creationTime());
System.out.println("Last Accessed Time: " + attributes.lastAccessTime());
System.out.println("Last Modified Time: " + attributes.lastModifiedTime());
System.out.println("File Key: " + attributes.fileKey());
System.out.println("Directory: " + attributes.isDirectory());
System.out.println("Other Type of File: " + attributes.isOther());
System.out.println("Regular File: " + attributes.isRegularFile());
System.out.println("Symbolic File: " + attributes.isSymbolicLink());
System.out.println("Size: " + attributes.size());
}
catch (IOException ex) {
System.out.println("Attribute error");
}
}

  1. 执行应用程序。您的输出应该类似于以下内容:

Creation Time: 2011-09-06T21:14:11.214057Z

Last Accessed Time: 2011-09-06T21:14:11.214057Z

Last Modified Time: 2011-09-06T01:26:56.501665Z

文件键:null

目录:false

其他类型的文件:false

常规文件:true

符号文件:false

大小:30

它是如何工作的...

首先,我们创建了一个代表users.txt文件的Path对象。接下来,我们使用Files类的readAttributes方法获取了一个BasicFileAttributes对象。该方法的第一个参数是一个Path对象。第二个参数指定了我们想要返回的对象类型。在这种情况下,它是一个BasicFileAttributes.class对象。

然后是一系列打印语句,显示有关文件的特定属性信息。readAttributes方法检索文件的所有基本属性。由于它可能会抛出IOException,代码序列被包含在 try 块中。

大多数BasicFileAttributes接口方法很容易理解,但有一些需要进一步解释。首先,如果isOther方法返回true,这意味着文件不是常规文件、目录或符号链接。此外,尽管文件大小以字节为单位,但由于文件压缩和稀疏文件的实现等问题,实际大小可能会有所不同。如果文件不是常规文件,则返回值的含义取决于系统。

fileKey方法返回一个唯一标识该文件的对象。在 UNIX 中,设备 ID 或 inode 用于此目的。如果文件系统及其文件发生更改,文件键不一定是唯一的。它们可以使用equals方法进行比较,并且可以用于集合。再次强调的是,假设文件系统没有以影响文件键的方式发生更改。两个文件的比较在第二章的确定两个路径是否等效中有所涉及,使用路径定位文件和目录

还有更多...

获取对象的另一种方法是使用Files类的getFileAttributeView方法。它根据第二个参数返回一个基于AttributeView的派生对象。要获取BasicFileAttributeView对象的实例:

  1. 使用代表感兴趣的文件的Path对象作为第一个参数。

  2. 使用BasicFileAttributeView作为第二个参数。

不要使用以下语句:

BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);

我们可以用以下代码序列替换它:

BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class);
BasicFileAttributes attributes = view.readAttributes();

使用getFileAttributeView方法返回BasicFileAttributeView对象。然后readAttributes方法返回BasicFileAttributes对象。这种方法更长,但现在我们可以访问另外三种方法,如下所示:

  • name:这返回属性视图的名称

  • readAttributes:这返回一个BasicFileAttributes对象

  • setTimes:这用于设置文件的时间属性

  1. 然后我们使用如下所示的name方法:
System.out.println("Name: " + view.name());

这导致了以下输出:

名称:basic

然而,这并没有为我们提供太多有用的信息。setTimes方法在第四章的设置文件或目录的时间相关属性中有所说明,管理文件和目录

使用 PosixFileAttributeView 维护 POSIX 文件属性

许多操作系统支持可移植操作系统接口POSIX)标准。这提供了一种更便携的方式来编写可以在不同操作系统之间移植的应用程序。Java 7 支持使用java.nio.file.attribute.PosixFileAttributeView接口访问文件属性。

并非所有操作系统都支持 POSIX 标准。确定操作系统是否支持属性视图的示例说明了如何确定特定操作系统是否支持 POSIX。

准备工作

为了获取文件或目录的 POSIX 属性,我们需要执行以下操作:

  1. 创建一个代表感兴趣的文件或目录的Path对象。

  2. 使用getFileAttributeView方法获取PosixFileAttributeView接口的实例。

  3. 使用readAttributes方法获取一组属性。

如何做...

  1. 创建一个新的控制台应用程序。使用以下main方法。在此方法中,我们获取users.txt文件的属性如下:
public static void main(String[] args) throws Exception {
Path path = Paths.get("home/docs/users.txt");
FileSystem fileSystem = path.getFileSystem();
PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
PosixFileAttributes attributes = view.readAttributes();
System.out.println("Group: " + attributes.group());
System.out.println("Owner: " + attributes.owner().getName());
Set<PosixFilePermission> permissions = attributes.permissions();
for(PosixFilePermission permission : permissions) {
System.out.print(permission.name() + " ");
}
}

  1. 执行应用程序。您的输出应如下所示。所有者名称可能会有所不同。在这种情况下,它是richard:

组:richard

所有者:richard

OWNER_READ OWNER_WRITE OTHERS_READ GROUP_READ

它是如何工作的...

users.txt文件创建了一个Path对象。这被用作Files类的getFileAttributeView方法的第一个参数。第二个参数是PosixFileAttributeView.class。返回了一个PosixFileAttributeView对象。

接下来,使用readAttributes方法获取了PosixFileAttributes接口的实例。使用groupgetName方法显示了文件的组和所有者。权限方法返回了一组PosixFilePermission枚举。这些枚举表示分配给文件的权限。

还有更多...

PosixFileAttributes接口扩展了java.nio.file.attribute.BasicFileAttributes接口,因此可以访问其所有方法。PosixFileAttributeView接口扩展了java.nio.file.attribute.FileOwnerAttributeViewBasicFileAttributeView接口,并继承了它们的方法。

PosixFileAttributeView接口具有setGroup方法,可用于配置文件的组所有者。可以使用setPermissions方法维护文件的权限。在第四章管理文件和目录中讨论了维护文件权限的管理 POSIX 属性配方。

另请参阅

Maintaining basic file attributes using the BasicFileAttributeView配方详细介绍了通过此视图可用的属性。使用 FileOwnerAttributeView 维护文件所有权属性配方讨论了所有权问题。要确定操作系统是否支持 POSIX,请查看确定属性视图的操作系统支持配方。

使用DosFileAttributeView维护 FAT 表属性

java.nio.file.attribute.DosFileAttributeView涉及较旧的磁盘操作系统DOS)文件。在今天的大多数计算机上,它的价值有限。但是,这是唯一可以用来确定文件是否标记为归档文件或系统文件的接口。

准备就绪

要使用DosFileAttributeView接口:

  1. 使用Files类的getFileAttributeView方法获取DosFileAttributeView的实例。

  2. 使用视图的readAttributes方法返回DosFileAttributes的实例。

  3. 使用DosFileAttributes类的方法获取文件信息。

此视图支持以下四种方法:

  • isArchive:关注文件是否需要备份

  • isHidden:如果文件对用户不可见,则返回true

  • isReadOnly:如果文件只能读取,则返回true

  • isSystem:如果文件是操作系统的一部分,则返回true

如何做...

  1. 创建一个新的控制台应用程序,并添加以下main方法。在此方法中,我们创建DosFileAttributes的一个实例,然后使用其方法显示有关文件的信息:
public static void main(String[] args) {
Path path = FileSystems.getDefault().getPath("/home/docs/users.txt");
try {
DosFileAttributeView view = Files.getFileAttributeView(path, DosFileAttributeView.class);
DosFileAttributes attributes = view.readAttributes();
System.out.println("isArchive: " + attributes.isArchive());
System.out.println("isHidden: " + attributes.isHidden());
System.out.println("isReadOnly: " + attributes.isReadOnly());
System.out.println("isSystem: " + attributes.isSystem());
}
catch (IOException ex) {
ex.printStackTrace();
}
}

  1. 执行程序。您的输出应如下所示:

isArchive:true

isHidden:false

isReadOnly:false

isSystem:false

它是如何工作的...

创建了一个代表users.txt文件的Path对象。将此对象用作Files类的getFileAttributeView方法的参数,以及DosFileAttributeView.class。返回了DosFileAttributeView接口的一个实例。这被用于创建DosFileAttributes接口的一个实例,该实例与接口的四个方法一起使用。

DosFileAttributeView扩展了BasicFileAttributes接口,并因此继承了其所有属性,如使用 BasicFileAttributeView 维护基本文件属性配方中所述。

另请参阅

有关其方法的更多信息,请参阅使用 BasicFileAttributeView 维护基本文件属性配方。

使用 FileOwnerAttributeView 来维护文件所有权属性

如果我们只对访问文件或目录的所有者的信息感兴趣,那么java.nio.file.attribute.FileOwnerAttributeView接口提供了检索和设置此类信息的方法。文件所有权的设置在第四章的设置文件和目录所有者配方中有所涵盖,管理文件和目录

准备就绪

检索文件的所有者:

  1. 获取FileOwnerAttributeView接口的实例。

  2. 使用其getOwner方法返回代表所有者的UserPrincipal对象。

如何做...

  1. 创建一个新的控制台应用程序。将以下main方法添加到其中。在此方法中,我们将确定users.txt文件的所有者如下:
public static void main(String[] args) {
Path path = Paths.get("C:/home/docs/users.txt");
try {
FileOwnerAttributeView view = Files.getFileAttributeView(path, FileOwnerAttributeView.class);
UserPrincipal userPrincipal = view.getOwner();
System.out.println(userPrincipal.getName());
}
catch (IOException e) {
e.printStackTrace();
}
}

  1. 执行应用程序。您的输出应该类似于以下内容,除了 PC 和用户名应该不同。

Richard-PC\Richard

它是如何工作的...

users.txt文件创建了一个Path对象。接下来,使用Path对象作为第一个参数调用了Files类的getFileAttributeView方法。第二个参数是FileOwnerAttributeView.class,这导致返回文件的FileOwnerAttributeView对象。

然后调用视图的getOwner方法返回一个UserPrincipal对象。它的getName方法返回用户的名称,然后显示出来。

另请参阅

有关其方法的更多信息,请参阅使用 BasicFileAttributeView 维护基本文件属性配方。

使用 AclFileAttributeView 维护文件的 ACL

java.nio.file.attribute.AclFileAttributeView接口提供了对文件或目录的 ACL 属性的访问。这些属性包括用户主体、属性类型以及文件的标志和权限。使用此接口的能力允许用户确定可用的权限并修改这些属性。

准备就绪

确定文件或目录的属性:

  1. 创建代表该文件或目录的Path对象。

  2. 使用此Path对象作为Files类的getFileAttributeView方法的第一个参数。

  3. 使用AclFileAttributeView.class作为其第二个参数。

  4. 使用返回的AclFileAttributeView对象访问该文件或目录的 ACL 条目列表。

如何做...

  1. 创建一个新的控制台应用程序。在main方法中,我们将检查users.txt文件的 ACL 属性。使用getFileAttributeView方法获取视图并访问 ACL 条目列表。使用两个辅助方法来支持此示例:displayPermissionsdisplayEntryFlags。使用以下main方法:
public static void main(String[] args) {
Path path = Paths.get("C:/home/docs/users.txt");
try {
AclFileAttributeView view = Files.getFileAttributeView(path, AclFileAttributeView.class);
List<AclEntry> aclEntryList = view.getAcl();
for (AclEntry entry : aclEntryList) {
System.out.println("User Principal Name: " + entry.principal().getName());
System.out.println("ACL Entry Type: " + entry.type());
displayEntryFlags(entry.flags());
displayPermissions(entry.permissions());
System.out.println();
}
}
catch (IOException e) {
e.printStackTrace();
}
}

  1. 创建displayPermissions方法以显示文件的权限列表如下:
private static void displayPermissions(Set<AclEntryPermission> permissionSet) {
if (permissionSet.isEmpty()) {
System.out.println("No Permissions present");
}
else {
System.out.println("Permissions");
for (AclEntryPermission permission : permissionSet) {
System.out.print(permission.name() + " " );
}
System.out.println();
}
}

  1. 创建displayEntryFlags方法以显示文件的 ACL 标志列表如下:
private static void displayEntryFlags(Set<AclEntryFlag> flagSet) {
if (flagSet.isEmpty()) {
System.out.println("No ACL Entry Flags present");
}
else {
System.out.println("ACL Entry Flags");
for (AclEntryFlag flag : flagSet) {
System.out.print(flag.name() + " ");
}
System.out.println();
}
}

  1. 执行应用程序。您应该得到类似以下的输出:

用户主体名称:BUILTIN\Administrators

ACL 条目类型:允许

没有 ACL 条目标志

权限

WRITE_ATTRIBUTES EXECUTE DELETE READ_ATTRIBUTES WRITE_DATA READ_ACL READ_DATA WRITE_OWNER READ_NAMED_ATTRS WRITE_ACL APPEND_DATA SYNCHRONIZE DELETE_CHILD WRITE_NAMED_ATTRS

用户主体名称:NT AUTHORITY\SYSTEM

ACL 条目类型:允许

未出现 ACL 条目标志

权限

WRITE_ATTRIBUTES EXECUTE DELETE READ_ATTRIBUTES WRITE_DATA READ_ACL READ_DATA WRITE_OWNER READ_NAMED_ATTRS WRITE_ACL APPEND_DATA SYNCHRONIZE DELETE_CHILD WRITE_NAMED_ATTRS

用户主体名称:BUILTIN\Users

ACL 条目类型:允许

未出现 ACL 条目标志

权限

READ_DATA READ_NAMED_ATTRS EXECUTE SYNCHRONIZE READ_ATTRIBUTES READ_ACL

用户主体名称:NT AUTHORITY\Authenticated Users

ACL 条目类型:允许

未出现 ACL 条目标志

权限

READ_DATA READ_NAMED_ATTRS WRITE_ATTRIBUTES EXECUTE DELETE APPEND_DATA SYNCHRONIZE READ_ATTRIBUTES WRITE_NAMED_ATTRS WRITE_DATA READ_ACL

它是如何工作的...

创建了到users.txt文件的Path。然后将其与AclFileAttributeView.class参数一起用作getFileAttributeView方法的参数。这将返回AclFileAttributeView的一个实例。

AclFileAttributeView接口有三种方法:name, getAclsetAcl。在本例中,只使用了getAcl方法,它返回了一个AclEntry元素列表。每个条目代表文件的特定 ACL。

使用 for each 循环来遍历列表。显示了用户主体的名称和条目类型。接下来调用了displayEntryFlagsdisplayPermissions方法来显示有关条目的更多信息。

这两种方法在构造上相似。进行了检查以确定集合中是否有任何元素,并显示了适当的消息。接下来,将集合的每个元素显示在单独的一行上,以节省输出的垂直空间。

还有更多...

AclFileAttributeView源自java.nio.file.attribute.FileOwnerAttributeView接口。这提供了对getOwnersetOwner方法的访问。这些方法分别为文件或目录返回或设置UserPrincipal对象。

有三种AclFileAttributeView方法:

  • getAcl方法,返回 ACL 条目列表,如前所示

  • setAcl方法,允许我们向文件添加新属性

  • name方法,简单地返回acl

getAcl方法将返回一个AclEntrys列表。条目的一个元素是一个java.nio.file.attribute.UserPrincipal对象。正如我们在前面的示例中看到的,这代表了可以访问文件的用户。访问用户的另一种技术是使用java.nio.file.attribute.UserPrincipalLookupService类。可以使用FileSystem类的getUserPrincipalLookupService方法获取此类的实例,如下所示:

try {
UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService();
GroupPrincipal groupPrincipal = lookupService.lookupPrincipalByGroupName("Administrators");
UserPrincipal userPrincipal = lookupService.lookupPrincipalByName("Richard");
System.out.println(groupPrincipal.getName());
System.out.println(userPrincipal.getName());
}
catch (IOException e) {
e.printStackTrace();
}

服务可用的两种方法可以按用户名或组名查找用户。在前面的代码中,我们使用了Administrators组和用户Richard

将此代码添加到上一个示例中,并更改名称以反映系统中的组和用户。当代码执行时,您应该收到类似以下的输出:

BUILTIN\Administrators

Richard-PC\Richard

然而,请注意,UserPrincipaljava.nio.file.attribute.GroupPrincipal对象的方法提供的信息比用户的名称更少。用户或组名称可能是大小写敏感的,这取决于操作系统。如果使用无效的名称,将抛出java.nio.file.attribute.UserPrincipalNotFoundException

另请参阅

在第四章中讨论了管理文件所有权和权限的内容,管理文件和目录中的设置文件和目录所有者配方。第四章还涵盖了在管理 ACL 文件权限配方中说明的 ACL 属性的设置。

使用 UserDefinedFileAttributeView 维护用户定义的文件属性

java.nio.file.attribute.UserDefinedFileAttributeView接口允许将非标准属性附加到文件或目录。这些类型的属性有时被称为扩展属性。通常,用户定义的属性存储有关文件的元数据。这些数据不一定被文件系统理解或使用。

这些属性存储为名称/值对。名称是一个String,值存储为ByteBuffer对象。该缓冲区的大小不应超过Integer.MAX_VALUE

准备就绪

用户定义的属性必须首先附加到文件上。这可以通过以下方式实现:

  1. 获取UserDefinedFileAttributeView对象的实例

  2. 创建一个以String名称和ByteBuffer值形式的属性

  3. 使用write方法将属性附加到文件

读取用户定义属性的过程在本配方的更多内容部分进行了说明。

如何做...

  1. 创建一个新的控制台应用程序。在main方法中,我们将创建一个名为publishable的用户定义属性,并将其附加到users.txt文件。使用以下main方法:
public static void main(String[] args) {
Path path = Paths.get("C:/home/docs/users.txt");
try {
UserDefinedFileAttributeView view = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
view.write("publishable", Charset.defaultCharset().encode("true"));
System.out.println("Publishable set");
}
catch (IOException e) {
e.printStackTrace();
}
}

  1. 执行应用程序。您的输出应如下所示:

设置为可发布

它是如何工作的...

首先,创建一个代表users.txt文件的Path对象。然后使用Files类的getFileAttributeView方法,使用Path对象和UserDefinedFileAttributeView.class作为第二个参数。这将返回文件的UserDefinedFileAttributeView的实例。

使用这个对象,我们对其执行write方法,使用属性publishable,创建了一个包含属性值truejava.nio.ByteBuffer对象。java.nio.Charset类的defaultCharset方法返回一个使用底层操作系统的区域设置和字符集的Charset对象。encode方法接受String并返回属性值的ByteBuffer。然后我们显示了一个简单的消息,指示进程成功完成。

还有更多...

read方法用于读取属性。要获取与文件关联的用户定义属性,需要按照以下步骤进行:

  1. 获取UserDefinedFileAttributeView对象的实例。

  2. 为属性名称创建一个String

  3. 分配一个ByteBuffer来保存值。

  4. 使用read方法获取属性值。

以下代码序列完成了先前附加的publishable属性的任务:

String name = "publishable";
ByteBuffer buffer = ByteBuffer.allocate(view.size(name));
view.read(name, buffer);
buffer.flip();
String value = Charset.defaultCharset().decode(buffer).toString();
System.out.println(value);

首先创建属性名称的String。接下来,创建一个ByteBuffer来保存要检索的属性值。allocate方法根据UserDefinedFileAttributeView接口的size方法指定的空间分配空间。此方法确定附加属性的大小并返回大小。

然后对view对象执行read方法。缓冲区填充了属性值。flip方法重置了缓冲区。使用decode方法将缓冲区转换为String对象,该方法使用操作系统的默认字符集。

main方法中,用这个read序列替换用户定义的属性write序列。当应用程序执行时,您应该得到类似以下的输出:

true

还有一个delete方法,用于从文件或目录中删除用户定义的属性。另外,需要注意使用UserDefinedFileAttributeView对象需要运行时权限accessUserDefinedAttributes

第四章:管理文件和目录

在这一章中,我们将涵盖以下内容:

  • 创建文件和目录

  • 控制文件复制方式

  • 管理临时文件和目录

  • 设置文件或目录的时间相关属性

  • 管理文件所有权

  • 管理 ACL 文件权限

  • 管理 POSIX 属性

  • 移动文件或目录

  • 删除文件和目录

  • 管理符号链接

介绍

通常需要执行文件操作,如创建文件,操作它们的属性和内容,或从文件系统中删除它们。Java 7 中java.lang.object.Files类的添加简化了这个过程。这个类在很大程度上依赖于新的java.nio.file.Path接口的使用,这在第二章中深入讨论,使用路径定位文件和目录。该类的方法在本质上都是静态的,并且通常将实际的文件操作分配给底层文件系统。

本章描述的许多操作在本质上是原子的,例如用于创建和删除文件或目录的操作。原子操作要么成功执行完成,要么失败并导致操作的有效取消。在执行过程中,它们不会从文件系统的角度受到干扰。其他并发文件操作不会影响该操作。

注意

要执行本章中的许多示例,应用程序需要以管理员身份运行。在 Windows 下以管理员身份运行应用程序,右键单击命令提示符菜单,选择以管理员身份运行。然后导航到适当的目录并使用java.exe命令执行。在 UNIX 系统上以管理员身份运行,使用终端窗口中的sudo命令,然后是java命令。

本章涵盖了基本的文件管理。创建文件和目录所需的方法在创建文件和目录教程中介绍。该教程侧重于普通文件。临时文件和目录的创建在管理临时文件和目录教程中介绍,链接文件的创建在管理符号链接教程中介绍。

复制文件和目录的可用选项在控制文件复制方式教程中找到。那里展示的技术提供了处理文件复制的强大方式。移动和删除文件和目录分别在移动文件或目录删除文件或目录教程中介绍。

设置文件或目录的时间相关属性教程说明了如何为文件分配时间属性。与此相关的还有其他属性,如文件所有权和权限。文件所有权在管理文件所有权教程中讨论。文件权限在两个教程中讨论:管理 ACL 文件权限管理 POSIX 文件权限

创建文件和目录

在 Java 7 中,创建新文件和目录的过程大大简化。Files类实现的方法相对直观,易于整合到您的代码中。在本教程中,我们将介绍如何使用createFilecreateDirectory方法创建新文件和目录。

准备工作

在我们的示例中,我们将使用几种不同的方法来创建代表文件或目录的Path对象。我们将执行以下操作:

  1. 创建Path对象。

  2. 使用Files类的createDirectory方法创建目录。

  3. 使用Files类的createFile方法创建文件。

FileSystem类的getPath方法可用于创建Path对象,Paths类的get方法也可以。Paths类的静态get方法基于字符串序列或URI对象返回Path的实例。FileSystem类的getPath方法也返回Path对象,但只使用字符串序列来标识文件。

如何做...

  1. 创建一个带有main方法的控制台应用程序。在main方法中,添加以下代码,为C目录中的/home/test目录创建一个Path对象。在 try 块内,使用您的Path对象作为参数调用createDirectory方法。如果路径无效,此方法将抛出IOException。接下来,使用此Path对象上的createFile方法创建文件newFile.txt,再次捕获IOException如下:
try {
Path testDirectoryPath = Paths.get("C:/home/test");
Path testDirectory = Files.createDirectory(testDirectoryPath);
System.out.println("Directory created successfully!");
Path newFilePath = FileSystems.getDefault().getPath("C:/home/test/newFile.txt");
Path testFile = Files.createFile(newFilePath);
System.out.println("File created successfully!");
}
catch (IOException ex) {
ex.printStackTrace();
}

  1. 执行程序。您的输出应如下所示:

目录创建成功!

文件创建成功!

  1. 验证新文件和目录是否存在于您的文件系统中。接下来,在两个方法之后添加一个IOException之前的 catch 块,并捕获FileAlreadyExistsException
}
catch (FileAlreadyExistsException a) {
System.out.println("File or directory already exists!");
}
catch (IOException ex) {
ex.printStackTrace();
}

  1. 当您再次执行程序时,您的输出应如下所示:

文件或目录已存在!

工作原理...

第一个Path对象被创建,然后被createDirectory方法用于创建一个新目录。创建第二个Path对象后,使用createFile方法在刚刚创建的目录中创建了一个文件。重要的是要注意,在创建目录之前无法实例化用于文件创建的Path对象,因为它将引用无效的路径。这将导致IOException

当调用createDirectory方法时,系统首先检查目录是否存在,如果不存在,则创建。createFile方法的工作方式类似。如果文件已经存在,该方法将失败。当我们捕获FileAlreadyExistsException时,我们看到了这一点。如果我们没有捕获该异常,将抛出IOException。无论哪种方式,现有文件都不会被覆盖。

还有更多...

createFilecreateDirectory方法在本质上是原子的。createDirectories方法可用于创建目录,如下所述。这三种方法都提供了传递文件属性参数以进行更具体文件创建的选项。

使用createDirectories方法创建目录层次结构

createDirectories方法用于创建目录和可能的其他中间目录。在此示例中,我们通过向test目录添加subtestsubsubtest目录来构建先前的目录结构。注释掉之前创建目录和文件的代码,并添加以下代码序列:

Path directoriesPath = Paths.get("C:/home/test/subtest/subsubtest");
Path testDirectory = Files.createDirectories(directoriesPath);

通过检查生成的目录结构来验证操作是否成功。

另请参阅

创建临时文件和目录在管理临时文件和目录中有所涉及。符号文件的创建在管理符号链接中有所说明。

控制文件复制的方式

在 Java 7 中,文件复制的过程也变得更加简化,并允许控制复制的方式。Files类的copy方法支持此操作,并提供了三种不同的复制技术。

准备就绪

在我们的示例中,我们将创建一个新文件,然后将其复制到另一个目标文件。这个过程涉及:

  1. 使用createFile方法创建一个新文件。

  2. 为目标文件创建一个路径。

  3. 使用copy方法复制文件。

如何做...

  1. 创建一个带有main方法的控制台应用程序。在main方法中,添加以下代码序列来创建一个新文件。指定两个Path对象,一个用于您的初始文件,另一个用于将其复制的位置。然后添加copy方法将该文件复制到目标位置,如下所示:
Path newFile = FileSystems.getDefault().getPath("C:/home/docs/newFile.txt");
Path copiedFile = FileSystems.getDefault().getPath("C:/home/docs/copiedFile.txt");
try {
Files.createFile(newFile);
System.out.println("File created successfully!");
Files.copy(newFile, copiedFile);
System.out.println("File copied successfully!");
}
catch (IOException e) {
System.out.println("IO Exception.");
}

  1. 执行程序。您的输出应如下所示:

文件创建成功!

文件复制成功!

它是如何工作的...

createFile方法创建了您的初始文件,copy方法将该文件复制到copiedFile变量指定的位置。如果您尝试连续两次运行该代码序列,您将遇到IOException,因为copy方法默认情况下不会替换现有文件。copy方法是重载的。使用带有java.lang.enum.StandardCopyOption枚举值REPLACE_EXISTINGcopy方法,允许替换文件,如下所示。

StandardCopyOption的三个枚举值列在下表中:

含义
ATOMIC_MOVE原子性地执行复制操作
COPY_ATTRIBUTES将源文件属性复制到目标文件
REPLACE_EXISTING如果已存在,则替换现有文件

用以下代码序列执行前面的示例中的copy方法调用替换:

Files.copy(newFile, copiedFile, StandardCopyOption.REPLACE_EXISTING);

当代码执行时,文件应该被替换。在还有更多..部分的移动文件和目录配方中还有另一个使用复制选项的示例。

还有更多...

如果源文件和目标文件相同,则该方法会完成,但实际上不会发生复制。copy方法不是原子的。

还有另外两个重载的copy方法。一个是将java.io.InputStream复制到文件,另一个是将文件复制到java.io.OutputStream。在本节中,我们将更深入地研究以下过程:

  • 复制符号链接文件

  • 复制目录

  • 将输入流复制到文件

  • 将文件复制到输出流

复制符号链接文件

当复制符号链接文件时,会复制符号链接的目标。为了说明这一点,在music目录中创建一个名为users.txt的符号链接文件,指向docs目录中的users.txt文件。可以通过使用第二章中描述的管理符号链接配方中的过程,即使用路径定位文件和目录,或者使用本章中所示的管理符号链接配方中的方法来完成。

使用以下代码序列执行复制操作:

Path originalLinkedFile = FileSystems.getDefault().getPath("C:/home/music/users.txt");
Path newLinkedFile = FileSystems.getDefault().getPath("C:/home/music/users2.txt");
try {
Files.copy(originalLinkedFile, newLinkedFile);
System.out.println("Symbolic link file copied successfully!");
}
catch (IOException e) {
System.out.println("IO Exception.");
}

执行代码。您应该得到以下输出:

符号链接文件复制成功!

检查生成的music目录结构。user2.txt文件已添加,并且与链接文件或原始目标文件没有连接。修改user2.txt不会影响其他两个文件的内容。

复制目录

当复制目录时,会创建一个空目录。原始目录中的文件不会被复制。以下代码序列说明了这个过程:

Path originalDirectory = FileSystems.getDefault().getPath("C:/home/docs");
Path newDirectory = FileSystems.getDefault().getPath("C:/home/tmp");
try {
Files.copy(originalDirectory, newDirectory);
System.out.println("Directory copied successfully!");
}
catch (IOException e) {
e.printStackTrace();
}

执行此序列时,您应该得到以下输出:

目录复制成功!

检查tmp目录。它应该是空的,因为源目录中的任何文件都没有被复制。

将输入流复制到文件

copy方法有一个方便的重载版本,允许基于InputStream的输入创建新文件。该方法的第一个参数与原始copy方法不同,因为它是InputStream的实例。

以下示例使用此方法将jdk7.java.net网站复制到文件中:

Path newFile = FileSystems.getDefault().getPath("C:/home/docs/java7WebSite.html");
URI url = URI.create("http://jdk7.java.net/");
try (InputStream inputStream = url.toURL().openStream())
Files.copy(inputStream, newFile);
System.out.println("Site copied successfully!");
}
catch (MalformedURLException ex) {
ex.printStackTrace();
}
catch (IOException ex) {
ex.printStackTrace();
}

当代码执行时,您应该得到以下输出:

站点复制成功!

创建一个java.lang.Object.URI对象来表示网站。使用URI对象而不是java.lang.Object.URL对象立即避免了创建一个单独的 try-catch 块来处理MalformedURLException异常。

URL类的openStream方法返回一个InputStream,该流作为copy方法的第一个参数使用。请注意使用 try-with-resource 块。这个 try 块是 Java 7 中的新功能,并在第一章的使用 try-with-resource 块改进异常处理代码中有详细说明,Java 语言改进

然后执行了copy方法。现在可以使用浏览器打开新文件,或者根据需要进行处理。请注意,该方法返回一个表示写入的字节数的长整型值。

将文件复制到输出流

copy方法的第三个重载版本将打开一个文件并将其内容写入OutputStream。当需要将文件的内容复制到非文件对象(如PipedOutputStream)时,这可能很有用。当与其他线程通信或写入字节数组时,这也可能很有用,如本例所示。在这个例子中,users.txt文件的内容被复制到一个ByteArrayOutputStream的实例中。然后使用它的toByteArray方法来填充一个数组,如下所示:

Path sourceFile = FileSystems.getDefault().getPath("C:/home/docs/users.txt");
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Files.copy(sourceFile, outputStream);
byte arr[] = outputStream.toByteArray();
System.out.println("The contents of " + sourceFile.getFileName());
for(byte data : arr) {
System.out.print((char)data);
}
System.out.println();
}
catch (IOException ex) {
ex.printStackTrace();
}

执行这个序列。输出将取决于您的文件内容,但应该类似于以下内容:

users.txt 的内容

Bob

Jennifer

Sally

Tom

Ted

注意使用 try-with-resources 块来处理文件的打开和关闭。在复制操作完成或发生异常时关闭OutputStream总是一个好主意。try-with-resources 块很好地处理了这个问题。在某些情况下,该方法可能会阻塞,直到操作完成。它的行为大部分是特定于实现的。此外,输出流可能需要刷新,因为它实现了Flushable接口。请注意,该方法返回一个表示写入的字节数的长整型值。

另请参阅

有关使用符号链接的更多详细信息,请参阅管理符号链接食谱。

管理临时文件和目录

创建临时文件和目录的过程可能是许多应用程序的重要部分。临时文件可以用于中间数据或作为稍后清理的临时存储。通过Files类可以简单地完成管理临时文件和目录的过程。在本食谱中,我们将介绍如何使用createTempDirectorycreateTempFile方法创建临时文件和目录。

准备就绪

在我们的示例中,我们将创建一个临时目录,然后在目录中创建一个临时文件,如下所示:

  1. 创建代表临时文件和目录的Path对象。

  2. 使用createTempDirectory方法创建一个临时目录。

  3. 使用createTempFile方法创建一个临时文件。

如何做...

  1. 创建一个带有main方法的控制台应用程序。在main方法中,使用getPath方法创建一个Path对象rootDirectory。使用rootDirectory作为第一个参数,空字符串作为第二个参数调用createTempDirectory方法。然后使用toString方法将返回的Path对象dirPath转换为String并打印到屏幕上。接下来,使用dirPath作为第一个参数,空字符串作为第二和第三个参数添加createTempFile方法。再次使用toString方法打印出这个结果路径,如下所示:
try {
Path rootDirectory = FileSystems.getDefault().getPath("C:/home/docs");
Path tempDirectory = Files.createTempDirectory(rootDirectory, "");
System.out.println("Temporary directory created successfully!");
String dirPath = tempDirectory.toString();
System.out.println(dirPath);
Path tempFile = Files.createTempFile(tempDirectory,"", "");
System.out.println("Temporary file created successfully!");
String filePath = tempFile.toString();
System.out.println(filePath);
}
catch (IOException e) {
System.out.println("IO Exception.");
}

  1. 这段代码序列将产生类似于以下内容的输出:

临时目录创建成功!

C:\home\docs\7087436262102989339

临时文件创建成功!

C:\home\docs\7087436262102989339\3473887367961760381

工作原理...

createTempDirectory方法创建一个空目录并返回代表这个新目录位置的Path对象。同样,createTempFile方法创建一个空文件并返回代表这个新文件的Path对象。在我们之前的例子中,我们使用toString方法来查看我们的目录和文件创建的路径。之前的数字目录和文件名由系统分配,并且是特定于平台的。

这个createTempDirectory方法至少需要两个参数,即指向新目录位置的Path对象和指定目录前缀的String变量。在我们之前的例子中,我们留空了前缀。但是,如果我们想要指定文本以在系统分配的文件名之前出现,第二个变量可以用这个前缀字符串填充。

createTempFile方法的工作方式与createTempDirectory方法类似,如果我们想要为临时文件分配一个前缀,我们可以使用第二个参数来指定字符串。此方法的第三个参数也可以用来指定文件的后缀或类型,例如.txt

重要的是要注意,尽管在我们的例子中我们指定了我们想要创建目录和文件的Path,但每种方法还有另一个版本,其中初始参数,Path对象,可以被省略,目录和/或文件将被创建在系统的默认临时目录中。此外,这些方法在创建文件或目录之前不会检查文件或目录的存在,并且会覆盖具有相同临时、系统分配名称的任何现有文件或目录。

更多内容...

文件属性名称也可以传递给重载的createTempDirectorycreateTempFile方法。这些属性是可选的,但可以用来指定临时文件的处理方式,例如文件是否应在关闭时被删除。文件属性的创建在更多内容管理 POSIX 文件权限配方的部分中描述。

createTempDirectorycreateTempFile方法的存在是有限的。如果希望自动删除这些文件或目录,可以使用关闭挂钩或java.io.File类的deleteOnExit方法。这两种技术将导致在应用程序或 JVM 终止时删除元素。

设置文件或目录的与时间相关的属性

文件的时间戳对于某些应用程序可能至关重要。例如,操作执行的顺序可能取决于文件的最后更新时间。BasicFileAttributeView支持三种日期:

  • 最后修改时间

  • 最后访问时间

  • 创建时间

它们可以使用BasicFileAttributeView接口的setTimes方法进行设置。正如我们将在更多内容部分看到的,Files类可以用来设置或仅获取最后修改时间。

准备工作

为了使用setTimes方法设置时间,我们需要做以下操作:

  1. 获取代表感兴趣文件的Path对象。

  2. 获取BasicFileAttributeView对象。

  3. 为所需的时间创建FileTime对象。

  4. 使用这些FileTime对象作为setTimes方法的参数。

如何做...

  1. 使用以下main方法创建一个新的控制台应用程序。我们将更新我们最喜欢的文件users.txt的最后修改时间为当前时间:
public static void main(String[] args) throws Exception {
Path path = Paths.get("C:/home/docs/users.txt");
BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class);
FileTime lastModifedTime;
FileTime lastAccessTime;
FileTime createTime;
BasicFileAttributes attributes = view.readAttributes();
lastModifedTime = attributes.lastModifiedTime();
createTime = attributes.creationTime();
long currentTime = Calendar.getInstance().getTimeInMillis();
lastAccessTime = FileTime.fromMillis(currentTime);
view.setTimes(lastModifedTime, lastAccessTime, createTime);
System.out.println(attributes.lastAccessTime());
}

  1. 执行应用程序。除非您有时间机器的访问权限,或者以其他方式操纵了系统的时钟,否则您的输出应该反映出比以下显示的时间更晚的时间:

2011-09-24T21:34:55.012Z

工作原理...

首先为 users.txt 文件创建了一个 Path。接下来,使用 getFileAttributeView 方法获得了 BasicFileAttributeView 接口的一个实例。使用 try 块来捕获 readAttributessetTimes 方法可能抛出的任何 IOExceptions

在 try 块中,为三种类型的时间创建了 FileTime 对象。文件的 lastModifedTimecreateTime 时间没有改变。这些是使用 BasicFileAttributes 类的相应方法获得的,该类是使用 view 方法获得的。

currentTime 长变量被赋予以毫秒表示的当前时间。它的值是使用 Calendar 类的实例执行 getTimeInMillis 方法获得的。然后,三个 FileTime 对象被用作 setTimes 方法的参数,有效地设置了这些时间值。

还有更多...

FileTime 类的使用还不止以上所述。此外,Files 类提供了维护时间的替代方法。在这里,我们将进一步探讨以下内容:

  • 了解 FileTime

  • 使用 Files 类的 setLastModifiedTime 来维护最后修改时间

  • 使用 Files 类的 setAttribute 方法来设置单个属性

了解 FileTime

java.nio.file.attribute.FileTime 类表示用于 java.nio 包方法的时间。要创建一个 FileTime 对象,我们需要使用以下两个静态 FileTime 方法之一:

  • from 方法,接受一个表示持续时间的长数字和一个表示时间测量单位的 TimeUnit 对象

  • fromMillis 方法,接受一个基于纪元的毫秒数的长参数

TimeUnitjava.util.concurrent 包中的一个枚举。它表示如下表中定义的时间持续时间。它与另一个参数结合使用,其组合表示时间持续时间:

枚举值含义
纳秒千分之一微秒
微秒千分之一毫秒
毫秒千分之一秒
一秒
分钟六十秒
小时六十分钟
二十四小时

from 方法返回一个 TimeUnit 对象。它的值是通过将第一个长参数(其度量单位由第二个 TimeUnit 参数指定)加到纪元得到的。

注意

纪元是 1970-01-01T00:00:00Z,这是大多数计算机上用于指定时间的基本时间。这个基本时间代表 1970 年 1 月 1 日的协调世界时午夜。

例如,from 方法可以用来表示一个时间点,即从纪元开始的 1000 天,使用以下代码序列:

FileTime fileTime = FileTime.from(1000, TimeUnit.DAYS);
System.out.println(fileTime);

执行时应该得到以下输出:

1972-09-27T00:00:00Z

fromMillis 方法用于创建一个 FileTime 对象,其时间是通过将其参数加到纪元得到的,其中参数是以毫秒表示的长数字。如果我们使用以下 fromMillis 方法而不是如下所示的 from 方法:

FileTime fileTime = FileTime.fromMillis(1000L*60*60*24*1000);

我们将得到相同的结果。注意,第一个参数是一个长整型字面量,这迫使表达式的结果为长整数。如果我们没有将结果提升为长整数值,我们将得到一个整数值,这将导致溢出和错误的日期。任何方法的第一个参数都可以是负数。

注意

有关在 Java 中使用时间的更多细节,请参阅www3.ntu.edu.sg/home/ehchua/programming/java/DateTimeCalendar.html

使用 Files 类的 setLastModifiedTime 来维护最后修改时间

Files类的getLastModifiedTimesetLastModifiedTime方法提供了设置文件最后修改属性的另一种方法。在下面的代码序列中,setLastModifiedTime方法使用lastModifedTime对象来设置时间,如下所示:

Files.setLastModifiedTime(path, lastModifedTime);

Files类的getLastModifiedTime返回一个FileTime对象。我们可以使用这个方法将一个值赋给lastModifedTime变量,如下所示:

lastModifedTime = Files.getLastModifiedTime(path);

该方法有一个可选的LinkOption参数,指示是否应该跟随符号链接。

使用 Files 类的 setAttribute 方法来设置单个属性

setAttribute方法提供了一种灵活和动态的方法来设置某些文件属性。要设置最后修改时间,我们可以使用以下代码序列:

Files.setAttribute(path, "basic:lastAccessTime", lastAccessTime);

第三章中的使用 getAttribute 方法逐个获取属性配方详细介绍了可以设置的其他属性。

另请参阅

管理符号链接配方讨论了符号链接的使用。

管理文件所有权

文件或目录的所有者可以在文件创建后进行修改。这是通过使用java.nio.file.attribute.FileOwnerAttributeView接口的setOwner方法来实现的,当所有权发生变化并需要以编程方式进行控制时,这将非常有用。

使用java.nio.file.attribute.UserPrincipal对象表示一个用户。使用Path对象表示一个文件或目录。将这两个对象与Files类的setOwner方法一起使用,可以维护文件的所有权。

准备工作

为了更改文件或目录的所有者:

  1. 获取一个代表文件或目录的Path对象。

  2. 使用Path作为getFileAttributeView方法的参数。

  3. 创建一个代表新所有者的UserPrincipal对象。

  4. 使用FileOwnerAttributeView接口的setOwner方法来更改文件的所有者。

如何做...

  1. 在这个例子中,我们将假设users.txt文件的当前所有者是richard。我们将把所有者更改为一个名为jennifer的用户。为此,在系统上创建一个名为jennifer的新用户。创建一个包含以下main方法的新控制台应用程序。在该方法中,我们将使用FileOwnerAttributeViewUserPrincipal对象来更改所有者,如下所示:
public static void main(String[] args) throws Exception {
Path path = Paths.get("C:/home/docs/users.txt");
FileOwnerAttributeView view = Files.getFileAttributeView(path, FileOwnerAttributeView.class);
UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService();
UserPrincipal userPrincipal = lookupService.lookupPrincipalByName("jennifer");
view.setOwner(userPrincipal);
System.out.println("Owner: " + view.getOwner().getName());
}

  1. 为了修改文件的所有权,我们必须拥有适当的权限。本章的介绍解释了如何获取管理员权限。当应用程序在 Windows 7 上执行时,输出应该反映出 PC 名称和文件所有者,如下所示。PC 名称与所有者之间用反斜杠分隔:

所有者:Richard-PC\Richard

所有者:Richard-PC\Jennifer

工作原理...

首先为users.txt文件创建了一个Path。接下来,使用getFileAttributeView方法获取了FileOwnerAttributeView接口的一个实例。在 try 块内,使用默认的FileSystem类的getUserPrincipalLookupService方法创建了一个UserPrincipalLookupService对象。lookupPrincipalByName方法传递了字符串jennifer,返回了代表该用户的UserPrincipal对象。

最后一步是将UserPrincipal对象传递给setOwner方法。然后使用getOwner方法检索当前所有者以验证更改。

还有更多...

FileOwnerAttributeView派生的任何接口都可以使用getOwnersetOwner方法。这些包括AclFileAttributeViewPosixFileAttributeView接口。此外,Files类的setOwner方法也可以用于更改文件的所有权。

使用 Files 类的 setOwner 方法

Files类的setOwner方法与FileOwnerAttributeView接口的setOwner方法相同。不同之处在于它有两个参数,一个表示文件的Path对象和一个UserPrincipal对象。以下序列说明了将users.txt文件的所有者设置为jennifer的过程:

Path path = Paths.get("C:/home/docs/users.txt");
try {
UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService();
UserPrincipal userPrincipal = lookupService.lookupPrincipalByName("jennifer");
Files.setOwner(path, userPrincipal);
System.out.println("Owner: " + view.getOwner().getName());
}
catch (IOException ex) {
ex.printStackTrace();
}

管理 ACL 文件权限

在本教程中,我们将研究如何设置 ACL 权限。设置这些权限的能力对许多应用程序很重要。例如,当我们需要控制谁可以修改或执行文件时,我们可以通过编程方式影响这种变化。我们可以改变的内容由稍后列出的AclEntryPermission枚举值表示。

准备工作

为文件设置新的 ACL 权限:

  1. 为要更改其属性的文件创建Path对象。

  2. 获取该文件的AclFileAttributeView

  3. 为用户获取一个UserPrincipal对象。

  4. 获取当前分配给文件的 ACL 条目列表。

  5. 创建一个持有我们要添加的权限的新AclEntry.Builder对象。

  6. 将权限添加到 ACL 列表中。

  7. 使用setAcl方法用新的 ACL 列表替换当前的 ACL 列表。

操作步骤...

  1. 使用以下main方法创建一个新的控制台应用程序。在这个方法中,我们将首先简单地显示文件users.txt的当前 ACL 列表,如下所示:
public static void main(String[] args) throws Exception {
Path path = Paths.get("C:/home/docs/users.txt");
AclFileAttributeView view = Files.getFileAttributeView(path, AclFileAttributeView.class);
List<AclEntry> aclEntryList = view.getAcl();
displayAclEntries(aclEntryList);
}

  1. 为了说明添加和删除 ACL 属性的过程,我们将使用一系列辅助方法:
  • displayAclEntries: 显示主体和条目类型,然后调用其他两个辅助方法

  • displayEntryFlags: 如果存在,显示条目标志

  • displayPermissions: 如果有的话,显示条目权限

  1. 按照以下代码向应用程序添加方法:
private static void displayAclEntries(List<AclEntry> aclEntryList) {
System.out.println("ACL Entry List size: " + aclEntryList.size());
for (AclEntry entry : aclEntryList) {
System.out.println("User Principal Name: " + entry.principal().getName());
System.out.println("ACL Entry Type: " + entry.type());
displayEntryFlags(entry.flags());
displayPermissions(entry.permissions());
System.out.println();
}
}
private static void displayPermissions(Set<AclEntryPermission> permissionSet) {
if (permissionSet.isEmpty()) {
System.out.println("No Permissions present");
}
else {
System.out.println("Permissions");
for (AclEntryPermission permission : permissionSet) {
System.out.print(permission.name() + " ");
}
System.out.println();
}
}
private static void displayEntryFlags(Set<AclEntryFlag> flagSet) {
if (flagSet.isEmpty()) {
System.out.println("No ACL Entry Flags present");
}
else {
System.out.println("ACL Entry Flags");
for (AclEntryFlag flag : flagSet) {
System.out.print(flag.name() + " ");
}
System.out.println();
}
}

  1. ACL 列表包含文件的 ACL 条目。当执行displayAclEntries方法时,它将方便地显示条目数量,然后每个条目将用空行分隔。以下是users.txt文件可能的列表:

所有者:Richard-PC\Richard

ACL 条目列表大小:4

用户主体名称:BUILTIN\Administrators

ACL 条目类型:允许

没有 ACL 条目标志

权限

读取数据 删除 读取命名属性 读取属性 写入所有者 删除子项 写入数据 追加数据 同步 执行 写入属性 写入 ACL 写入命名属性 读取 ACL

用户主体名称:NT AUTHORITY\SYSTEM

ACL 条目类型:允许

没有 ACL 条目标志

权限

读取数据 删除 读取命名属性 读取属性 写入所有者 删除子项 写入数据 追加数据 同步 执行 写入属性 写入 ACL 写入命名属性 读取 ACL

用户主体名称:BUILTIN\用户

ACL 条目类型:允许

没有 ACL 条目标志

权限

读取数据 同步 执行 读取命名属性 读取属性 读取 ACL

用户主体名称:NT AUTHORITY\已验证用户

ACL 条目类型:允许

没有 ACL 条目标志

权限

追加数据 读取数据 删除 同步 执行 读取命名属性 读取属性 写入属性 写入命名属性 读取 ACL 写入数据

  1. 接下来,使用UserPrincipalLookupService类的lookupService方法返回UserPrincipalLookupService类的实例。使用它的lookupPrincipalByName方法根据用户名称返回一个UserPrincipal对象。在调用displayAclEntries方法之后添加以下代码:
UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService();
UserPrincipal userPrincipal = lookupService.lookupPrincipalByName("users");

  1. 接下来,添加以下代码来创建和设置一个AclEntry.Builder对象。这将用于为用户添加WRITE_ACL 和 DELETE权限。将条目添加到 ACL 列表,并使用setAcl方法将其附加到当前文件,如下所示:
AclEntry.Builder builder = AclEntry.newBuilder();
builder.setType(AclEntryType.ALLOW);
builder.setPrincipal(userPrincipal);
builder.setPermissions(
AclEntryPermission.WRITE_ACL,
AclEntryPermission.DELETE);
AclEntry entry = builder.build();
aclEntryList.add(0, entry);
view.setAcl(aclEntryList);

  1. 执行应用程序。为了修改文件的一些 ACL 属性,我们必须具有适当的权限。本章的介绍详细介绍了如何以管理员身份运行应用程序的细节。接下来,注释掉添加 ACL 条目的代码,并验证是否已添加 ACL 条目。您应该看到以下条目添加到列表中:

ACL 条目列表大小:5

用户主体名称:BUILTIN\Users

ACL 条目类型:允许

没有 ACL 条目标志存在

权限

WRITE_ACL DELETE

它是如何工作的...

main方法中,我们创建了Path对象,然后使用它来获取java.nio.file.attribute.AclFileAttributeView接口的实例。Path对象表示的文件是users.txt文件。AclFileAttributeView对象可以用于多种目的。在这里,我们只对使用其getAcl方法返回与文件关联的 ACL 属性列表感兴趣。

我们只显示当前 ACL 的列表,以查看它们是什么,并最终验证文件的属性是否已更改。ACL 属性与用户关联。在这个例子中,我们创建了一个代表用户的UserPrincipal对象。

可以使用java.nio.file.attribute.AclEntry.Builder类的build方法创建新的 ACL 条目。静态的newBuilder方法创建了AclEntry.Builder类的一个实例。执行setPrincipal方法将用户设置为属性的主体。setPermissions方法接受一组AclEntryPermission对象或可变数量的AclEntryPermission对象。在这个例子中,我们使用了一个由逗号分隔的两个权限组成的列表:AclEntryPermission.WRITE_ACLAclEntryPermission.DELETE

然后将AclEntry.Builder对象添加到文件的现有 ACL 中。条目被添加到列表的开头。最后一步是使用setAcl方法用新的 ACL 列表替换旧的 ACL 列表。

还有更多...

要删除 ACL 属性,我们需要获取当前列表,然后确定要删除的属性的位置。我们可以使用java.util.List接口的remove方法来删除该项。然后可以使用setAcl方法用新列表替换旧列表。

ACL 属性在RFC 3530: Network File System (NFS) version 4 Protocol中有更详细的解释。以下表格提供了有关可用 ACL 权限的附加信息和见解。枚举AclEntryType具有以下值:

含义
ALARM在尝试访问指定属性时,以系统特定的方式生成警报
ALLOW授予权限
AUDIT在尝试访问指定属性时,以系统相关的方式记录所请求的访问
DENY拒绝访问

AclEntryPermission枚举值总结如下表所示:

含义
APPEND_DATA能够向文件追加数据
DELETE能够删除文件
DELETE_CHILD能够删除目录中的文件或目录
EXECUTE能够执行文件
READ_ACL能够读取 ACL 属性
READ_ATTRIBUTES能够读取(非 ACL)文件属性
READ_DATA能够读取文件的数据
READ_NAMED_ATTRS能够读取文件的命名属性
SYNCHRONIZE能够在服务器上本地访问文件,进行同步读写
WRITE_ACL能够写入 ACL 属性
WRITE_ATTRIBUTES能够写入(非 ACL)文件属性
WRITE_DATA能够修改文件的数据
WRITE_NAMED_ATTRS能够写入文件的命名属性
WRITE_OWNER能够更改所有者

AclEntryFlag枚举适用于目录条目。总结为四个值如下:

含义
DIRECTORY_INHERITACL 条目应添加到每个新创建的目录
FILE_INHERITACL 条目应添加到每个新创建的非目录文件
INHERIT_ONLYACL 条目应添加到每个新创建的文件或目录
NO_PROPAGATE_INHERITACL 条目不应放置在新创建的目录上,该目录可被创建目录的子目录继承

目前,AclEntryType.AUDITAclEntryType.ALARM没有与之关联的标志。

管理 POSIX 属性

可用的 POSIX 属性包括组所有者、用户所有者和一组权限。在本示例中,我们将研究如何维护这些属性。管理这些属性使得开发应用程序在多个操作系统上执行更加容易。虽然属性的数量有限,但对于许多应用程序来说可能已经足够了。

有三种方法可以用来管理 POSIX 属性:

  • java.nio.file.attribute.PosixFileAttributeView接口

  • Files类的设置/获取 POSIX 文件权限方法

  • Files类的setAttribute方法

使用PosixFileAttributeView接口获取PosixFileAttributes对象的方法在第三章的使用 PosixFileAttributeView 维护 POSIX 文件属性中有详细说明。在这里,我们将首先说明如何使用PosixFileAttributeView接口方法,并在本示例的*还有更多..*部分演示最后两种方法。

准备工作

要维护文件的 POSIX 权限属性,我们需要:

  1. 创建一个表示感兴趣的文件或目录的Path对象。

  2. 获取该文件的PosixFileAttributes对象。

  3. 使用permissions方法获取该文件的一组权限。

  4. 修改权限集。

  5. 使用setPermissions方法替换权限。

如何做...

  1. 我们将创建一个应用程序,获取PosixFileAttributes对象并使用它来显示users.txt文件的当前权限集,然后向文件添加PosixFilePermission.OTHERS_WRITE权限。创建一个新的控制台应用程序,并添加以下main方法:
public static void main(String[] args) throws Exception {
Path path = Paths.get("home/docs/users.txt");
FileSystem fileSystem = path.getFileSystem();
PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
PosixFileAttributes attributes = view.readAttributes();
Set<PosixFilePermission> permissions = attributes.permissions();
listPermissions(permissions);
permissions.add(PosixFilePermission.OTHERS_WRITE);
view.setPermissions(permissions);
System.out.println();
listPermissions(permissions);
}
private static void listPermissions(Set<PosixFilePermission> permissions) {
System.out.print("Permissions: ");
for (PosixFilePermission permission : permissions) {
System.out.print(permission.name() + " ");
}
System.out.println();
}

  1. 在支持 POSIX 的系统上执行应用程序。在Ubuntu 11.04下执行时,应该会得到类似以下的结果:

权限:GROUP_READ OWNER_WRITE OTHERS_READ OWNER_READ

权限:GROUP_READ OWNER_WRITE OTHERS_WRITE OTHERS_READ OWNER_READ

它是如何工作的...

main方法中,我们获取了users.txt文件的Path,然后使用getFileAttributeView方法获取了PosixFileAttributeView的实例。然后使用readAttributes方法获取了表示文件 POSIX 属性的PosixFileAttributes对象的实例。

使用listPermissions方法列出文件的权限。在添加新权限到文件之前和之后各执行一次此方法。我们这样做只是为了显示权限的变化。

使用add方法将PosixFilePermission.OTHERS_WRITE权限添加到权限集中。以下表列出了PosixFilePermission枚举值:

级别授予权限
GROUP_EXECUTE执行和搜索
GROUP_READ 读取
GROUP_WRITE 写入
OTHERS_EXECUTE其他人执行和搜索
OTHERS_READ 读取
OTHERS_WRITE 写入
OWNER_EXECUTE拥有者执行和搜索
OWNER_READ 读取
OWNER_WRITE 写入

在这个例子中,我们添加了一个PosixFilePermission.OTHERS_WRITE权限。在下一节中,我们将说明如何删除权限。

还有更多...

还有其他几个感兴趣的操作,包括:

  • 删除文件权限

  • 修改文件的 POSIX 所有权

  • 使用Files类的set/get POSIX 文件权限方法

  • 使用Files类的setAttribute方法

  • 使用PosixFilePermissions类创建PosixFilePermissions

删除文件权限

删除权限只是一个简单的事情:

  • 获取文件的一组权限

  • 使用Set接口的remove方法来删除权限

  • 将集合重新分配给文件

这在以下代码序列中有所体现,其中删除了PosixFilePermission.OTHERS_WRITE权限:

Set<PosixFilePermission> permissions = attributes.permissions();
Permissions.remove(PosixFilePermission.OTHERS_WRITE);
view.setPermissions(permissions);

修改文件的 POSIX 所有权

POSIX 所有者在组和用户级别指定。PosixFileAttributes方法的组和所有者将返回表示文件的组和用户所有者的对象。setGroupsetOwner方法将设置相应的成员资格。

在接下来的示例中,将显示users.txt文件的所有者,然后进行更改。创建UserPrincipal对象以支持set方法:

Path path = Paths.get("home/docs/users.txt");
try {
FileSystem fileSystem = path.getFileSystem();
PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
PosixFileAttributes attributes = view.readAttributes();
Set<PosixFilePermission> permissions = attributes.permissions();
System.out.println("Old Group: " + attributes.group().getName());
System.out.println("Old Owner: " + attributes.owner().getName());
System.out.println();
UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService();
UserPrincipal userPrincipal = lookupService.lookupPrincipalByName("jennifer");
GroupPrincipal groupPrincipal = lookupService.lookupPrincipalByGroupName(("jennifer");
view.setGroup(groupPrincipal);
view.setOwner(userPrincipal);
attributes = view.readAttributes();
System.out.println("New Group: " + attributes.group().getName());
System.out.println("New Owner: " + attributes.owner().getName());
System.out.println();
POSIX attributesfile permission, removing}
catch (IOException ex) {
ex.printStackTrace();
}

执行时,输出应如下所示:

为 users.txt 设置所有者

旧组:richard

旧所有者:richard

新组:jennifer

新所有者:jennifer

您可能需要以管理员身份执行代码,详细信息请参见介绍。

使用 Files 类的 set/get POSIX 文件权限方法

此方法使用Files类的setPosixFilePermissionsgetPosixFilePermissions方法。getPosixFilePermissions方法返回指定其第一个参数的文件的PosixFilePermissions集合。它的第二个参数是LinkOption,用于确定如何处理符号链接文件。通常不会跟随链接,除非使用LinkOption.NOFOLLOW_LINKS。我们可以使用以下代码序列列出与文件关联的权限:

Path path = Paths.get("home/docs/users.txt");
try {
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
System.out.print("Permissions: ");
for (PosixFilePermission permission : permissions) {
System.out.print(permission.name() + " ");
}
System.out.println();
}
catch (IOException ex) {
ex.printStackTrace();
}

setPermissions方法接受表示文件的Path对象和一组PosixFilePermission。而不是使用以前的方法:

view.setPermissions(path, permissions);

我们可以使用Files类的setPosixFilePermissions方法:

Files.setPosixFilePermissions(path, permissions);

使用Files类简化了该过程,避免了创建PosixFileAttributes对象。

使用 Files 类的 setAttribute 方法

Files类的getAttribute方法在第三章中详细介绍了使用 getAttribute 方法逐个获取属性配方。setAttribute方法将设置一个属性,并具有以下四个参数:

  • 表示文件的Path对象

  • 包含要设置的属性的String

  • 表示属性值的对象

  • 指定符号链接的可选LinkOption

以下说明了向users.txt文件添加PosixFilePermission.OTHERS_WRITE权限:

Path path = Paths.get("home/docs/users.txt");
try {
Files.setAttribute(path, "posix:permission, PosixFilePermission.OTHERS_WRITE);
}
catch (IOException ex) {
ex.printStackTrace();
}

在此示例中未使用LinkOption值。

使用 PosixFilePermissions 类创建 PosixFilePermissions

PosixFilePermissions类拥有三种方法:

  • asFileAttribute,返回一个包含一组PosixFilePermissionsFileAttribute对象

  • fromString,也返回一个基于String参数的PosixFilePermissions集合

  • toString,执行fromString方法的逆操作

所有三种方法都是静态的。第一种方法返回一个FileAttribute对象,可以与创建文件和目录配方中讨论的createFilecreateDirectory方法一起使用。

在 Unix 系统上,文件权限经常表示为一个九个字符的字符串。这个字符串分为三个字符组。第一组表示用户的权限,第二组表示组的权限,最后一组表示其他所有人的权限。这三个字符组中的每一个表示为该组授予的读、写或执行权限。在第一个位置的r表示读权限,第二个位置的w表示写权限,最后一个位置的x表示执行权限。在任何这些位置上的-表示权限未设置。

为了说明这些方法,执行以下代码序列:

Path path = Paths.get("home/docs/users.txt");
try {
FileSystem fileSystem = path.getFileSystem();
PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
PosixFileAttributes attributes = view.readAttributes();
Set<PosixFilePermission> permissions = attributes.permissions();
for(PosixFilePermission permission : permissions) {
System.out.print(permission.toString() + ' ');
}
System.out.println();
FileAttribute<Set<PosixFilePermission>> fileAttributes = PosixFilePermissions.asFileAttribute(permissions);
Set<PosixFilePermission> fileAttributeSet = fileAttributes.value();
for (PosixFilePermission posixFilePermission : fileAttributeSet) {
System.out.print(posixFilePermission.toString() + ' ');
}
System.out.println();
System.out.println(PosixFilePermissions.toString(permissions));
permissions = PosixFilePermissions.fromString("rw-rw-r--");
for(PosixFilePermission permission : permissions) {
System.out.print(permission.toString() + ' ');
}
System.out.println();
}
catch (IOException ex) {
}

你的输出应该类似于以下内容:

OTHERS_READ OWNER_READ GROUP_READ OWNER_WRITE

OTHERS_READ OWNER_READ OWNER_WRITE GROUP_READ

rw-r--r--

OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE OTHERS_READ

代码的第一部分获取了users.txt文件的权限集,就像在本食谱中早些时候详细介绍的那样。然后显示了权限。接下来,执行了asFileAttribute方法,返回了文件的FileAttribute。使用value方法获取了一组属性,然后显示了这些属性。两组权限被显示,但顺序不同。

接下来,使用toString方法将这组权限显示为字符串。注意每个字符反映了对users.txt文件授予的权限。

最后的代码段使用fromString方法创建了一个新的权限集。然后显示这些权限以验证转换。

移动文件和目录

移动文件或目录在重新组织用户空间结构时非常有用。这个操作由Files类的move方法支持。在移动文件或目录时,有几个因素需要考虑。这些包括符号链接文件是否存在,move是否应该替换现有文件,以及移动是否应该是原子的。

如果移动发生在相同的文件存储上,移动可能会导致资源的重命名。使用这个方法有时会使用Path接口的resolveSibling方法。这个方法将用它的参数替换路径的最后一部分。这在重命名文件时很有用。resolveSibling方法在第二章的使用路径解析合并路径食谱的*还有更多..*部分中有详细介绍。

做好准备

为了移动文件或目录:

  1. 获取一个Path对象,表示要移动的文件或目录。

  2. 获取一个Path对象,表示移动的目的地。

  3. 确定复制选项以控制移动。

  4. 执行move方法。

操作步骤...

  1. 使用以下main方法创建一个新的控制台应用程序。我们将把users.txt文件移动到music目录:
public static void main(String[] args) throws Exception {
Path sourceFile = Paths.get("C:/home/docs/users.txt");
Path destinationFile = Paths.get ("C:/home/music/users.txt");
Files.move(sourceFile, destinationFile);
}

  1. 执行应用程序。检查docsmusic目录的内容。users.txt文件应该不在docs目录中,但在music目录中。

工作原理...

move方法使用这两个Path对象,并且没有使用第三个可选参数。这个参数用于确定复制操作的工作方式。当它没有被使用时,文件复制操作默认为简单复制。

StandardCopyOption枚举实现了CopyOption接口,并定义了支持的复制操作类型。CopyOption接口与Files类的copymove方法一起使用。下表列出了这些选项。这些选项在*还有更多..*部分中有更详细的解释:

含义
ATOMIC_MOVE移动操作是原子的
COPY_ATTRIBUTES源文件属性被复制到新文件
REPLACE_EXISTING如果目标文件存在,则替换目标文件

如果目标文件已经存在,则会抛出FileAlreadyExistsException异常。但是,如果CopyOption.REPLACE_EXISTING作为move方法的第三个参数使用,则不会抛出异常。当源是符号链接时,将复制链接而不是链接的目标。

还有更多...

有几个需要涵盖的变化和问题。这些包括:

  • 移动方法的琐碎用法

  • 标准复制选项枚举值的含义

  • 使用resolveSibling方法与move方法影响重命名操作

  • 移动目录

移动方法的琐碎用法

如果源文件和目标文件相同,则该方法不会产生任何效果。以下代码序列将不会产生任何效果:

Path sourceFile = ...;
Files.move(sourceFile, sourceFile);

不会抛出异常,文件也不会被移动。

标准复制选项枚举值的含义

标准复制选项枚举值需要更多的解释。StandardCopyOption.REPLACE_EXISTING的值将替换已存在的文件。如果文件是符号链接,则只替换符号链接文件,而不是其目标。

StandardCopyOption.COPY_ATTRIBUTES的值将复制文件的所有属性。StandardCopyOption.ATOMIC_MOVE的值指定移动操作要以原子方式执行。所有其他枚举值都将被忽略。但是,如果目标文件已经存在,则要么替换文件,要么抛出IOException。结果取决于实现。如果无法以原子方式执行移动操作,则会抛出AtomicMoveNotSupportedException。原子移动可能由于源文件和目标文件的文件存储器的差异而失败。

如果在 Windows 7 上执行以下代码序列:

Path sourceFile = Paths.get("C:/home/docs/users.txt");
Path destinationFile = Paths.get("C:/home/music/users. txt");
Files.move(sourceFile, destinationFile, StandardCopyOption.ATOMIC_MOVE);

如果目标文件已经存在,则会抛出AccessDeniedException异常。如果文件不存在,其执行将导致以下错误消息:

java.nio.file.AtomicMoveNotSupportedException: C:\home\docs\users.txt -> E:\home\music\users.txt: 系统无法将文件移动到不同的磁盘驱动器

使用resolveSibling方法与move方法影响重命名操作

resolveSibling方法将用不同的字符串替换路径的最后一部分。这可以用于在使用move方法时影响重命名操作。在以下序列中,users.txt文件被有效地重命名:

Path sourceFile = Paths.get("C:/home/docs/users.txt");
Files.move(sourceFile, sourceFile.resolveSibling(sourceFile.getFileName()+".bak"));

文件已重命名为users.txt.bak。请注意,源文件路径被使用了两次。要重命名文件并替换其扩展名,可以使用显式名称,如下所示:

Files.move(sourceFile, sourceFile.resolveSibling("users.bak"));

更复杂的方法可能使用以下序列:

Path sourceFile = Paths.get("C:/home/docs/users.txt");
String newFileName = sourceFile.getFileName().toString();
newFileName = newFileName.substring(0, newFileName.indexOf('.')) + ".bak";
Files.move(sourceFile, sourceFile.resolveSibling(newFileName));

substring方法返回一个以第一个字符开头,以紧接着句号之前的字符结尾的新文件名。

移动目录

当在同一文件存储器上移动目录时,目录和子目录也会被移动。以下将把docs目录、其文件和子目录移动到music目录中:

Path sourceFile = Paths.get("C:/home/docs");
Path destinationFile = Paths.get("C:/home/music/docs");
Files.move(sourceFile, destinationFile);

然而,执行此代码序列,其中docs目录将被移动到E驱动器上类似的文件结构,将导致DirectoryNotEmptyException异常:

Path sourceFile = Paths.get("C:/home/docs");
Path destinationFile = Paths.get("E:/home/music/docs");
Files.move(sourceFile, destinationFile);

在不同文件存储器之间移动目录将导致异常,如果目录不为空。如果在前面的例子中docs目录是空的,move方法将成功执行。如果需要在不同文件存储器之间移动非空目录,通常需要进行复制操作,然后进行删除操作。

删除文件或目录

删除文件或目录当它们不再需要时是一个常见的操作。它将节省系统空间并导致更干净的文件系统。Files类有两种方法可以用来删除文件或目录:deletedeleteIfExists。它们都以Path对象作为参数,并可能抛出IOException

准备工作

要删除文件或目录,需要执行以下操作:

  1. 获取一个代表文件或目录的Path对象。

  2. 使用deletedeleteIfExists方法来删除元素。

如何做...

  1. 创建一个新的控制台应用程序,并使用以下main方法:
public static void main(String[] args) throws Exception {
Path sourceFile = Paths.get("C:/home/docs/users.txt");
Files.delete(sourceFile);
}

  1. 执行应用程序。如果users.txt文件在程序运行时存在于目录中,那么在程序执行后它就不应该存在。如果文件不存在,那么你的程序输出应该类似于以下内容:

java.nio.file.NoSuchFileException: C:\home\docs\users.txt

它是如何工作的...

这种方法很简单。我们创建了一个代表users.txt方法的Path对象。然后我们将它作为delete方法的参数。由于delete方法可能会抛出IOException,所以代码被包含在 try-catch 块中。

为了避免如果文件不存在会抛出异常,我们可以使用deleteIfExists方法。用以下内容替换delete方法的调用:

Files.deleteIfExists(sourceFile);

确保文件不存在,然后执行此代码。程序应该正常终止,不会抛出任何异常。

还有更多...

如果我们尝试删除一个目录,那么该目录必须首先为空。如果目录不为空,则会抛出DirectoryNotEmptyException异常。执行以下代码序列来代替前面的示例:

Path sourceFile = Paths.get("C:/home/docs");
Files.delete(sourceFile);

假设docs目录不为空,应用程序应该抛出DirectoryNotEmptyException异常。

空目录的定义取决于文件系统的实现。在一些系统中,如果目录只包含特殊文件或符号链接,该目录可能被认为是空的。

如果一个目录不为空并且需要被删除,那么需要首先使用walkFileTree方法删除它的条目,就像在第五章的使用 SimpleFileVisitor 类遍历文件系统中所示的那样,管理文件系统

注意

如果要删除的文件是一个符号链接,只有链接被删除,而不是链接的目标。此外,如果文件被其他应用程序打开或正在使用中,可能无法删除文件。

管理符号链接

符号链接是文件,它们不是真正的文件,而是指向真正文件的链接,通常称为目标文件。当希望一个文件出现在多个目录中而不必实际复制文件时,这些链接是有用的。这样可以节省空间,并且所有更新都被隔离到一个单独的文件中。

Files类具有以下三种方法来处理符号链接:

  • createSymbolicLink方法,用于创建到可能不存在的目标文件的符号链接

  • createLink方法创建一个到现有文件的硬链接

  • readSymbolicLink检索到目标文件的Path

链接通常对文件的用户是透明的。对符号链接的任何访问都会被重定向到引用的文件。硬链接类似于符号链接,但有更多的限制。这些类型的链接在本食谱的*还有更多..*部分中有更详细的讨论。

准备工作

为了创建一个符号链接到一个文件:

  1. 获取一个代表链接的Path对象。

  2. 获取一个代表目标文件的Path对象。

  3. 将这些路径作为createSymbolicLink方法的参数。

如何做...

  1. 创建一个新的控制台应用程序。将以下main方法添加到应用程序中。在这个应用程序中,我们将在music目录中创建一个符号链接,指向docs目录中的实际users.txt文件。
public static void main(String[] args) throws Exception {
Path targetFile = Paths.get("C:/home/docs/users.txt");
Path linkFile = Paths.get("C:/home/music/users.txt");
Files.createSymbolicLink(linkFile, targetFile);
}

  1. 执行应用程序。如果应用程序没有足够的权限,那么将抛出异常。在 Windows 7 上执行时的示例如下:

java.nio.file.FileSystemException: C:\home\music\users.txt: A required privilege is not held by the client.

  1. 验证music目录中是否存在一个名为users.txt的新文件。检查文件的属性,以验证它是否是一个符号链接。在 Windows 7 上,右键单击文件名,然后选择属性。接下来,选择快捷方式选项卡。它应该显示如下截图所示:

操作步骤...

注意指定的目标是docs目录中的users.txt文件。

它是如何工作的...

我们创建了两个Path对象。第一个代表docs目录中的目标文件。第二个代表要在music目录中创建的链接文件。接下来,我们使用createSymbolicLink方法来实际创建符号链接。整个代码序列被包含在 try 块中,以捕获可能抛出的任何IOExceptions

createSymbolicLink方法的第三个参数可以是一个或多个FileAttribute值。这些值用于在创建链接文件时设置其属性。然而,目前它还没有得到完全支持。未来的 Java 版本将增强这一功能。FileAttribute可以按照还有更多..部分中管理 POSIX 文件权限配方中的详细说明来创建。

还有更多...

在这里,我们将更仔细地研究以下问题:

  • 创建硬链接

  • 创建对目录的符号链接

  • 确定链接文件的目标

创建硬链接

与符号链接相比,硬链接有更多的限制。这些限制包括以下内容:

  • 目标必须存在。如果不存在,就会抛出异常。

  • 不能对目录创建硬链接。

  • 硬链接只能在单个文件系统内建立。

硬链接的行为就像普通文件。文件没有明显的属性表明它是一个链接文件,而不是一个具有快捷方式选项卡的符号链接文件。硬链接的所有属性与目标文件的属性相同。

硬链接不像软链接那样经常使用。Path类的方法可以处理硬链接,不需要任何特殊考虑。使用createLink方法创建硬链接。它接受两个参数:链接文件的Path对象和目标文件的Path对象。在下面的示例中,我们在music目录中创建了一个硬链接,而不是一个符号链接:

try {
Path targetFile = Paths.get("C:/home/docs/users.txt");
Path linkFile = Paths.get("C:/home/music/users.txt");
Files.createLink(linkFile, targetFile);
}
catch (IOException ex) {
ex.printStackTrace();
}

执行应用程序。如果您检查链接文件的属性,您会发现它不显示为符号链接。但是,修改任一文件的内容将导致另一个文件也被修改。它们实际上是一样的。

创建对目录的符号链接

创建对目录的符号链接使用与文件相同的方法。在下面的示例中,创建了一个新目录tmp,它是docs目录的符号链接:

try {
Path targetFile = Paths.get("C:/home/docs");
Path linkFile = Paths.get("C:/home/tmp");
Files.createSymbolicLink(linkFile, targetFile);
}
catch (IOException ex) {
ex.printStackTrace();
}

tmp目录中的所有文件实际上都是指向docs目录中相应文件的符号链接。

确定链接文件的目标

isSymbolicLink方法,如第二章 使用路径定位文件和目录管理符号链接配方中所讨论的,用于确定文件是否是符号链接。readSymbolicLink方法接受一个代表链接文件的Path对象,并返回一个代表链接目标的Path对象。

以下代码序列说明了这一点,在music目录中的users.txt文件是一个符号链接:

try {
Path targetFile = Paths.get("C:/home/docs/users.txt");
Path linkFile = Paths.get("C:/home/music/users.txt");
System.out.println("Target file is: " + Files.readSymbolicLink(linkFile));
}
catch (IOException ex) {
ex.printStackTrace();
}

然而,如果users.txt链接文件是一个硬链接,就像使用createLink方法创建的那样,当执行代码时会得到以下异常:

java.nio.file.NotLinkException: 文件或目录不是一个重解析点

注意

重解析点是一个NTFS文件系统对象,它将特定数据与文件或目录关联起来。文件系统过滤器可以与重解析点类型关联。当文件系统打开文件时,它将传递这些信息给文件系统过滤器进行处理。这种方法是扩展文件系统功能的一种方式。