Skip to content

Latest commit

 

History

History
1442 lines (949 loc) · 65.3 KB

File metadata and controls

1442 lines (949 loc) · 65.3 KB

四、管理文件和目录

在本章中,我们将介绍以下内容:

  • 创建文件和目录
  • 控制文件的复制方式
  • 管理临时文件和目录
  • 设置文件或目录的时间相关属性
  • 管理文件所有权
  • 管理 ACL 文件权限
  • 管理 POSIX 属性
  • 移动文件或目录
  • 删除文件和目录
  • 管理符号链接

导言

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

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

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

本章介绍基本的文件管理。创建文件和目录所需的方法见创建文件和目录配方。此配方主要针对普通文件。临时文件和目录的创建包含在管理临时文件和目录配方中,链接文件的创建包含在管理符号链接配方中。

可用于复制文件和目录的选项可在控制文件复制方式配方中找到。这里介绍的技术提供了一种处理文件复制的强大方法。移动和删除文件和目录分别包含在移动文件或目录删除文件和目录配方中。

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

创建文件和目录

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

准备好了吗

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

  1. 创建一个 Path对象。
  2. 使用 Files类“ createDirectory方法创建目录。
  3. 使用 Files类“ createFile方法创建一个文件。

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

怎么做。。。

  1. 使用 main方法创建控制台应用程序。在 main方法中,添加以下代码,为 C目录中的目录 /home/test创建一个 Path对象。在 try 块中,以 Path对象作为参数调用 createDirectory方法。如果路径无效,此方法将抛出一个 IOException。接下来,在这个 Path对象上使用 createFile方法为文件 newFile.txt创建一个 Path对象,再次捕捉 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();
    }
  2. Execute the program. Your output should appear as follows:

    目录创建成功!

    文件创建成功!

  3. 验证文件系统中是否存在新文件和目录。接下来,在两种方法之后的 IOException之前添加一个 catch 块,捕捉一个 FileAlreadyExistsException:

    }
    catch (FileAlreadyExistsException a) {
    System.out.println("File or directory already exists!");
    }
    catch (IOException ex) {
    ex.printStackTrace();
    }
  4. When you execute the program again, your output should appear as follows:

    文件或目录已存在!

它是如何工作的。。。

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

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

还有更多。。。

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

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

createDirectories方法用于创建目录和可能的其他中间目录。在本例中,我们在前面的目录结构的基础上,向 test目录添加了 subtestsubsubtest目录。注释掉之前创建目录和文件的代码,并添加以下代码序列:

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

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

另见

创建临时文件和目录包含在管理临时文件和目录配方中。符号文件的创建如管理符号链接配方所示。

控制文件的复制方式

Java7 还简化了复制文件的过程,并允许控制复制文件的方式。 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.");
    }
  2. Execute the program. Your output should appear as follows:

    文件创建成功!

    文件复制成功!

它是如何工作的。。。

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

下表列出了 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文件。这可以通过使用第 2 章管理符号链接配方中描述的过程、使用路径定位文件和目录,或者使用本章管理符号链接配方中说明的方法来实现。

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

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 资源块的使用。这个 try 块对 Java 7 来说是新的,在第 1 章Java 语言改进中的使用 try with resource 块改进异常处理代码配方中进行了说明。

然后执行 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的内容

鲍勃

詹妮弗

莎莉

汤姆

Ted

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

另见

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

管理临时文件和目录

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

准备好了吗

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

  1. 创建表示临时文件和目录的 Path对象。
  2. 使用 createTempDirectory方法创建临时目录。
  3. 使用 createTempFile方法创建一个临时文件。

怎么做。。。

  1. 使用 main方法创建控制台应用程序。在 main方法中,使用 getPath方法创建 Path对象 rootDirectory。调用 createTempDirectory方法,使用 rootDirectory作为第一个参数,使用空字符串作为第二个参数。然后使用 toString方法将返回的 Path对象 dirPath转换为 String并打印到屏幕上。接下来,添加 createTempFile方法,使用 dirPath作为第一个参数,空字符串作为第二个和第三个参数。再次使用 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.");
    }
  2. This code sequence will result in an output similar to the following:

    临时目录创建成功!

    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方法的存在是有限的。如果需要自动删除这些文件或目录,可以使用 shutdownhook 或 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());
    }
  2. Execute the application. Unless you have access to a time machine, or have otherwise manipulated your system's clock, your output should reflect a time later than the time shown as follows:

    2011-09-24T21:34:55.012Z

它是如何工作的。。。

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

在 try 块中,分别为这三种时间类型创建了 FileTime对象。文件的 lastModifedTimecreateTime时间未更改。这些数据是通过 BasicFileAttributes类的相应方法获得的,而 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方法,它接受一个长参数,表示基于历元的毫秒数

TimeUnit是在 java.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 中时间使用的更多详细信息,请参见http://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);

使用第 3 章获取文件和目录信息中的 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());
    }
  2. In order to modify the ownership of a file, we must have appropriate privileges. The introduction to this chapter explains how to get administrator privileges. When the application is executed using Windows 7, the output should reflect the PC name and the file's owners shown as follows. The PC name is separated from the owner with a backslash:

    所有者: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);
    }
  2. 为了说明添加和删除 ACL 属性的过程,我们将使用一系列助手方法:

    • displayAclEntries:显示主体和条目类型,然后调用其他两个助手方法
    • displayEntryFlags:如果存在条目标志
    • displayPermissions:,则显示条目标志如果有

    则显示进入权限

  3. 将如下代码所示的方法添加到您的应用程序中:

    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();
    }
    }
  4. The ACL list contains the ACL entries for a file. When the displayAclEntries method is executed, it will display the number of entries as a convenience and then each entry will be separated by a blank line. The following illustrates a possible list for the users.txt file:

    所有者:Richard PC\Richard

    ACL 条目列表大小:4

    用户主体名称:内置\管理员

    ACL 录入类型:允许

    不存在 ACL 进入标志

    权限

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

    用户主体名称:NT AUTHORITY\SYSTEM

    ACL 录入类型:允许

    不存在 ACL 进入标志

    权限

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

    用户主体名称:内置\用户

    ACL 录入类型:允许

    不存在 ACL 进入标志

    权限

    读取数据同步执行名为【属性读取】的读取属性读取 ACL

    用户主体名称:NT 权威\认证用户

    ACL 录入类型:允许

    不存在 ACL 进入标志

    权限

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

  5. 接下来,使用 UserPrincipalLookupService类的 lookupService方法返回 UserPrincipalLookupService类的实例。使用其 lookupPrincipalByName方法根据用户名返回 UserPrincipal对象。调用 displayAclEntries方法后增加以下代码:

    UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService();
    UserPrincipal userPrincipal = lookupService.lookupPrincipalByName("users");
  6. 接下来,添加以下代码来创建和设置一个 AclEntry.Builder对象。这将用于为用户添加 WRITE_ACL and 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);
  7. Execute the application. In order to modify some ACL attributes of a file, we must have the appropriate privileges. The introduction to this chapter gives the details of how to run the application as the administrator. Next, comment out the code that adds the ACL entry and verify that the ACL entry has been made. You should see the following entry added to the list:

    ACL 条目列表大小:5

    用户主体名称:内置\用户

    ACL 录入类型:允许

    不存在 ACL 进入标志

    权限

    写入 ACL 删除

它是如何工作的。。。

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:网络文件系统(NFS)版本 4 协议中有更详细的解释。下表提供了有关可用 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_INHERIT | 应将 ACL 条目添加到创建的每个新目录中 | | FILE_INHERIT | 应将 ACL 条目添加到创建的每个新的非目录文件中 | | INHERIT_ONLY | 应将 ACL 条目添加到创建的每个新文件或目录中 | | NO_PROPAGATE_INHERIT | ACL 条目不应放在新创建的目录上,该目录可由已创建目录的子目录继承 |

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

管理 POSIX 属性

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

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

  • java.nio.file.attribute.PosixFileAttributeView接口
  • Files类的 set/get POSIX 文件权限方法
  • Files类的 setAttribute方法

使用 PosixFileAttributeView接口访问 PosixFileAttributes对象的方法详见第 3 章配方使用 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();
    }
  2. Execute the application on a system that supports POSIX. When executed under Ubuntu 11.04 you should get results similar to the following:

    权限:组\读所有者\写其他\读所有者\读

    权限:组\读取所有者\写入其他\写入其他\读取所有者\读取

它是如何工作的。。。

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/getPOSIX 文件权限方法
  • 使用 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 的所有者

老组:李察

老业主:李察

新组别:詹妮弗

新主人:詹妮弗

您可能需要以管理员的身份执行代码,如简介中所述。

使用 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方法详见使用第 3 章中的 getAttribute 方法配方一次获取单个属性。 setAttribute方法将设置一个属性,并具有以下四个参数:

  • 表示文件的 Path对象
  • 包含要设置的属性的 String
  • 表示属性值的对象
  • 一个可选的 LinkOption值,指定如何处理符号链接

下面说明如何将 PosixFilePermission.OTHERS_WRITE权限添加到 users.txt文件中:

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) {
}

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

其他\读所有者\读组\读所有者\写

其他\读所有者\读所有者\写组\读

rw-r--r--

所有者\读取所有者\写入组\读取组\写入其他人\读取

代码的第一部分获得 users.txt文件的一组权限,如本配方前面所述。然后显示权限。接下来,执行 asFileAttribute方法返回文件的 FileAttribute。使用 value方法获得一组属性,然后显示这些属性。显示了两组权限,但顺序不同。

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

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

移动文件和目录

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

如果移动发生在同一文件存储上,则移动可能会导致资源重命名。此方法的使用有时会使用 Path接口的 resolveSibling方法。此方法将用其参数替换路径的最后一部分。这在重命名文件时非常有用。 resolveSibling方法在中有详细说明,还有更多。。第 2使用路径解析组合路径的章节使用路径定位文件和目录。

准备好了吗

要移动文件或目录,请执行以下操作:

  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);
    }
  2. 执行应用程序。检查 docsmusic目录的内容。 users.txt文件应该不在 docs目录中,但存在于 music目录中。

它是如何工作的。。。

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

StandardCopyOption枚举实现 CopyOption接口,定义支持的复制操作类型。 CopyOption接口与 Files类的 copymove方法一起使用。下表列出了这些选项。这些选项在中有更多详细说明。。。第节:

|

价值

|

意思

| | --- | --- | | ATOMIC_MOVE | 移动操作本质上是原子操作 | | COPY_ATTRIBUTES | 源文件属性将复制到新文件 | | REPLACE_EXISTING | 如果目标文件存在,则替换该文件 |

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

还有更多。。。

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

  • move方法的琐碎用途
  • StandardCopyOption枚举值的含义
  • 使用 resolveSibling方法和 move方法影响重命名操作
  • 移动目录

移动方法的琐碎用途

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

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

不会引发异常,并且不会移动文件。

StandardCopyOption 枚举值的含义

StandardCopyOption枚举值需要更多的解释。如果存在, 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.AtomicMoveNotSupported 异常: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);
    }
  2. Execute the application. If the users.txt file existed in the directory when the program ran, it should not be there after the program executes. If the file did not exist, then your program output should appear similar to the following:

    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方法删除其条目,如第 5 章管理文件系统中的使用 SimpleFileVisitor 类遍历文件系统配方所示。

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

管理符号链接

符号链接是文件,不是真实文件,而是指向真实文件(通常称为目标文件)的链接。当需要一个文件出现在多个目录中而实际上不必复制该文件时,这些功能非常有用。这样可以节省空间并将所有更新隔离到单个文件中。

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

  • createSymbolicLink方法,该方法创建指向可能不存在的目标文件的符号链接
  • createLink方法创建指向现有文件的硬链接
  • readSymbolicLinkPath检索到目标文件

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

准备好了吗

为了创建指向文件的符号链接:

  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);
    }
  2. Execute the application. If the application does not have sufficient privileges, then an exception will be thrown. An example of this when executed on Windows 7 is shown as follows:

    java.nio.file.FileSystemException:C:\home\music\users.txt:客户端没有所需的权限。

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

How to do it...

请注意,指定的目标是 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方法,如第 2 章管理符号链接配方所述,使用路径定位文件和目录,确定文件是否为符号链接。 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文件系统对象,它将特定数据与文件或目录关联到应用程序。文件系统筛选器可以与重分析点类型相关联。当文件系统打开文件时,它会将此信息传递给文件系统过滤器进行处理。这种方法是扩展文件系统功能的一种方法。