在本章中,我们将介绍以下内容:
- 创建文件和目录
- 控制文件的复制方式
- 管理临时文件和目录
- 设置文件或目录的时间相关属性
- 管理文件所有权
- 管理 ACL 文件权限
- 管理 POSIX 属性
- 移动文件或目录
- 删除文件和目录
- 管理符号链接
通常需要执行文件操作,例如创建文件、操作文件的属性和内容,或者将其从文件系统中删除。在 Java7 中添加 java.lang.object.Files
类简化了这个过程。此类严重依赖于新的 java.nio.file.Path
接口的使用,这将在第 2 章、使用路径定位文件和目录中进行深入讨论。该类的方法本质上都是静态的,通常将实际的文件操作分配给底层文件系统。
本章中描述的许多操作本质上是原子操作,例如用于创建和删除文件或目录的操作。原子操作要么成功执行到完成,要么失败并导致操作的有效取消。在执行过程中,它们不会从文件系统的角度被中断。其他并发文件操作不会影响该操作。
要执行本章中的许多示例,应用程序需要以管理员身份运行。要在 Windows 下以管理员身份运行应用程序,请右键单击命令提示符菜单,然后选择以管理员身份运行。然后导航到适当的目录并使用 java.exe
命令执行。要在 UNIX 系统上以管理员身份运行,请在终端窗口中使用 sudo
命令,然后使用 java
命令。
本章介绍基本的文件管理。创建文件和目录所需的方法见创建文件和目录配方。此配方主要针对普通文件。临时文件和目录的创建包含在管理临时文件和目录配方中,链接文件的创建包含在管理符号链接配方中。
可用于复制文件和目录的选项可在控制文件复制方式配方中找到。这里介绍的技术提供了一种处理文件复制的强大方法。移动和删除文件和目录分别包含在移动文件或目录和删除文件和目录配方中。
设置文件或目录的时间相关属性说明了如何为文件分配时间属性。与此工作相关的还有其他属性,如文件所有权和权限。文件所有权在管理文件所有权配方中解决。文件权限在两个食谱中讨论:管理 ACL 文件权限和管理 POSIX 文件权限。
Java7 大大简化了创建新文件和目录的过程。 Files
类实现的方法相对直观,并且易于合并到代码中。在本食谱中,我们将介绍如何使用 createFile
和 createDirectory
方法创建新文件和目录。
在我们的示例中,我们将使用几种不同的方法来创建表示文件或目录的 Path
对象。我们将做以下工作:
- 创建一个
Path
对象。 - 使用
Files
类“createDirectory
方法创建目录。 - 使用
Files
类“createFile
方法创建一个文件。
FileSystem
类的 getPath
方法和 Paths
类的 get
方法一样可以用来创建 Path
对象。 Paths
类的静态 get
方法基于字符串序列或 URI
对象返回 Path
的实例。 FileSystem
类的 getPath
方法也返回一个 Path
对象,但只使用字符串序列来标识文件。
-
使用
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(); }
-
Execute the program. Your output should appear as follows:
目录创建成功!
文件创建成功!
-
验证文件系统中是否存在新文件和目录。接下来,在两种方法之后的
IOException
之前添加一个 catch 块,捕捉一个FileAlreadyExistsException:
} catch (FileAlreadyExistsException a) { System.out.println("File or directory already exists!"); } catch (IOException ex) { ex.printStackTrace(); }
-
When you execute the program again, your output should appear as follows:
文件或目录已存在!
第一个 Path
对象被创建,然后被 createDirectory
方法用来创建一个新目录。创建第二个 Path
对象后,使用 createFile
方法在刚刚创建的目录中创建一个文件。需要注意的是,在创建目录之前,无法实例化文件创建中使用的 Path
对象,因为它引用了无效路径。这将导致 IOException
。
当调用 createDirectory
方法时,系统将首先检查目录是否存在,如果不存在,则创建目录。 createFile
方法也以类似的方式工作。如果文件已存在,则该方法将失败。我们在抓到 FileAlreadyExistsException
时看到了这一点。如果我们没有捕捉到那个异常,就会抛出一个 IOException
。无论哪种方式,现有文件都不会被覆盖。
createFile
和 createDirectory
方法本质上是原子的。 createDirectories
方法可用于创建目录,如下所述。这三种方法都提供了传递文件属性参数以创建更具体的文件的选项。
createDirectories
方法用于创建目录和可能的其他中间目录。在本例中,我们在前面的目录结构的基础上,向 test
目录添加了 subtest
和 subsubtest
目录。注释掉之前创建目录和文件的代码,并添加以下代码序列:
Path directoriesPath = Paths.get("C:/home/test/subtest/subsubtest");
Path testDirectory = Files.createDirectories(directoriesPath);
通过检查生成的目录结构,验证操作是否成功。
创建临时文件和目录包含在管理临时文件和目录配方中。符号文件的创建如管理符号链接配方所示。
Java7 还简化了复制文件的过程,并允许控制复制文件的方式。 Files
类“ copy
方法支持此操作,并且重载提供了三种不同于源或目标的复制技术。
在我们的示例中,我们将创建一个新文件,然后将其复制到另一个目标文件。这个过程包括:
- 使用
createFile
方法创建新文件。 - 正在为目标文件创建路径。
- 使用
copy
方法复制文件。
-
使用
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."); }
-
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
类简单地完成。在本食谱中,我们将介绍如何使用 createTempDirectory
和 createTempFile
方法创建临时文件和目录。
在我们的示例中,我们将创建一个临时目录,然后在目录中创建一个临时文件,如下所示:
- 创建表示临时文件和目录的
Path
对象。 - 使用
createTempDirectory
方法创建临时目录。 - 使用
createTempFile
方法创建一个临时文件。
-
使用
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."); }
-
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
对象,并且在系统的默认临时目录中创建目录和/或文件。此外,这些方法在创建文件或目录之前不会检查文件或目录是否存在,并且会使用相同的临时系统分配名称覆盖任何现有文件或目录。
文件属性名也可以传递给重载的 createTempDirectory
或 createTempFile
方法。这些属性是可选的,但可用于指定如何处理临时文件,例如是否应在关闭时删除文件。文件属性的创建在中有详细描述。。。管理 POSIX 文件权限配方的一节。
createTempDirectory
和 createTempFile
方法的存在是有限的。如果需要自动删除这些文件或目录,可以使用 shutdownhook 或 java.io.File
类的 deleteOnExit
方法。这两种技术将导致在应用程序或 JVM 终止时删除元素。
对于某些应用程序,文件的时间戳可能非常关键。例如,操作的执行顺序可能取决于文件上次更新的时间。 BasicFileAttributeView:
支持三个日期
- 上次修改时间
- 最后访问时间
- 创作时间
可以使用 BasicFileAttributeView
接口的 setTimes
方法进行设置。正如我们将在中看到的,还有更多。。。第节, Files
类只能用于设置或获取上次修改的时间。
使用 setTimes
方法设置时间。我们需要做到以下几点:
- 获取一个表示感兴趣文件的
Path
对象。 - 获取一个
BasicFileAttributeView
对象。 - 根据需要的时间创建
FileTime
对象。 - 使用这些
FileTime
对象作为setTimes
方法的参数。
-
使用以下
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()); }
-
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
接口的一个实例。一个试块被用来捕捉任何可能被 readAttributes
或 setTimes
方法抛出的 IOExceptions
。
在 try 块中,分别为这三种时间类型创建了 FileTime
对象。文件的 lastModifedTime
和 createTime
时间未更改。这些数据是通过 BasicFileAttributes
类的相应方法获得的,而 BasicFileAttributes
类是通过 view
方法获得的。
currentTime
长变量分配了以毫秒为单位的当前时间。它的值是使用针对 Calendar
类的实例执行的 getTimeInMillis
方法获得的。然后将三个 FileTime
对象用作 setTimes
方法的参数,有效地设置这些时间值。
FileTime
类的使用比目前介绍的更多。此外, Files
类还提供了维护时间的替代方法。在这里,我们将进一步探讨以下内容:
- 理解
FileTime
类 - 使用
Files
类“setLastModifiedTime
维护上次修改的时间 - 使用
Files
类的setAttribute
方法设置个别属性
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
类“ getLastModifiedTime
和 setLastModifiedTime
方法为设置文件的上次修改属性提供了另一种方法。在下面的代码序列中, setLastModifiedTime
方法使用 lastModifedTime
对象设置时间,如下所示:
Files.setLastModifiedTime(path, lastModifedTime);
Files
类“ getLastModifiedTime
返回一个 FileTime
对象。我们可以使用此方法为 lastModifedTime
变量赋值,如下所示:
lastModifedTime = Files.getLastModifiedTime(path);
该方法有一个可选的 LinkOption
参数,指示是否应遵循符号链接。
setAttribute
方法为设置某些文件属性提供了灵活和动态的方法。要设置上次修改的时间,我们可以使用以下代码序列:
Files.setAttribute(path, "basic:lastAccessTime", lastAccessTime);
使用第 3 章获取文件和目录信息中的 getAttribute 方法配方一次获取单个属性,详细说明了可以设置的其他属性。
管理符号链接配方讨论了符号链接的使用。
创建文件后,可以修改文件或目录的所有者。这是通过使用 java.nio.file.attribute.FileOwnerAttributeView
接口的 setOwner
方法实现的,当所有权发生变化并且需要通过编程进行控制时,该方法非常有用。
java.nio.file.attribute.UserPrincipal
对象用于表示用户。 Path
对象用于表示文件或目录。通过将这两个对象与 Files
类的 setOwner
方法一起使用,我们可以维护文件所有权。
要更改文件或目录的所有者,请执行以下操作:
- 获取一个表示文件或目录的
Path
对象。 - 使用
Path
作为getFileAttributeView
方法的参数。 - 创建一个表示新所有者的
UserPrincipal
对象。 - 使用
FileOwnerAttributeView
接口的setOwner
方法更改文件的所有者。
-
在本例中,我们假设
users.txt
文件的当前所有者是richard
。我们会将所有者更改为名为jennifer
的用户。为此,请在系统上创建一个名为jennifer
的新用户。使用以下main
方法创建一个新的控制台应用程序。在该方法中,我们将使用FileOwnerAttributeView
和UserPrincipal
对象来更改所有者,如下所示: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()); }
-
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
派生的任何接口都可以使用 getOwner
或 setOwner
方法。这些包括 AclFileAttributeView
和 PosixFileAttributeView
接口。此外,还可以使用 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 权限。设置这些权限的能力对于许多应用程序都很重要。例如,当我们需要控制谁可以修改或执行一个文件时,我们可以通过编程来影响这个更改。我们可以更改的是后面列出的 AclEntryPermission
枚举值。
要为文件设置新的 ACL 权限,请执行以下操作:
- 为要更改其属性的文件创建一个
Path
对象。 - 获取该文件的
AclFileAttributeView
。 - 为用户获取一个
UserPrincipal
对象。 - 获取当前分配给文件的 ACL 条目列表。
- 创建一个新的
AclEntry.Builder
对象,该对象持有我们要添加的权限。 - 将权限添加到 ACL 列表中。
- 使用
setAcl
方法将当前 ACL 列表替换为新的 ACL 列表。
-
使用以下
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); }
-
为了说明添加和删除 ACL 属性的过程,我们将使用一系列助手方法:
displayAclEntries:
显示主体和条目类型,然后调用其他两个助手方法displayEntryFlags:
如果存在条目标志displayPermissions:
,则显示条目标志如果有
则显示进入权限
-
将如下代码所示的方法添加到您的应用程序中:
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(); } }
-
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 theusers.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 写入\数据
-
接下来,使用
UserPrincipalLookupService
类的lookupService
方法返回UserPrincipalLookupService
类的实例。使用其lookupPrincipalByName
方法根据用户名返回UserPrincipal
对象。调用displayAclEntries
方法后增加以下代码:UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService(); UserPrincipal userPrincipal = lookupService.lookupPrincipalByName("users");
-
接下来,添加以下代码来创建和设置一个
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);
-
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_ACL
和 AclEntryPermission.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.AUDIT
或 AclEntryType.ALARM
相关联的标志。
可用的 POSIX 属性包括组所有者、用户所有者和一组权限。在这个配方中,我们将研究如何维护这些属性。这些属性的管理使得开发设计用于在多个操作系统上执行的应用程序变得更加容易。虽然属性的数量有限,但对于许多应用程序来说,它们可能已经足够了。
有三种方法可用于管理 POSIX 属性:
java.nio.file.attribute.PosixFileAttributeView
接口Files
类的 set/get POSIX 文件权限方法Files
类的setAttribute
方法
使用 PosixFileAttributeView
接口访问 PosixFileAttributes
对象的方法详见第 3 章配方使用 PosixFileAttributeView 维护 POSIX 文件属性。在这里,我们将首先说明如何使用 PosixFileAttributeView
接口方法,并在中演示最后两种方法,还有更多。。。这是食谱的一部分。
要维护文件的 POSIX 权限属性,我们需要:
- 创建一个表示感兴趣的文件或目录的
Path
对象。 - 为该文件获取一个
PosixFileAttributes
对象。 - 使用 permissions 方法获取该文件的一组权限。
- 修改权限集。
- 使用
setPermissions
方法替换权限。
-
我们将创建一个获得
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(); }
-
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/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 所有者是在组和用户级别指定的。 PosixFileAttributes
方法的组和所有者将返回表示文件的组和用户所有者的对象。 setGroup
和 setOwner
方法将设置相应的成员资格。
在下面的示例中, 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
类的 setPosixFilePermissions
和 getPosixFilePermissions
方法。 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
类的 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
类有三种方法:
asFileAttribute
,返回一个包含一组PosixFilePermissions
的FileAttribute
对象fromString
,它还返回一组基于String
参数的PosixFilePermissions
toString
,执行fromString
方法的逆运算
这三种方法都是静态的。第一个方法返回一个 FileAttribute
对象,可与 createFile
或 createDirectory
方法一起使用,如创建文件和目录配方中所述。
在 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章中使用路径解析组合路径的章节使用路径定位文件和目录。
要移动文件或目录,请执行以下操作:
- 获取一个
Path
对象,表示要移动的文件或目录。 - 获取一个表示移动目的地的
Path
对象。 - 确定复制选项以控制移动。
- 执行
move
方法。
-
使用以下
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); }
-
执行应用程序。检查
docs
和music
目录的内容。users.txt
文件应该不在docs
目录中,但存在于music
目录中。
move
方法使用了这两个 Path
对象,并且没有使用第三个可选参数。此参数用于确定复制操作的工作方式。不使用时,文件复制操作默认为简单复制。
StandardCopyOption
枚举实现 CopyOption
接口,定义支持的复制操作类型。 CopyOption
接口与 Files
类的 copy
和 move
方法一起使用。下表列出了这些选项。这些选项在中有更多详细说明。。。第节:
价值
|
意思
|
| --- | --- |
| ATOMIC_MOVE
| 移动操作本质上是原子操作 |
| COPY_ATTRIBUTES
| 源文件属性将复制到新文件 |
| REPLACE_EXISTING
| 如果目标文件存在,则替换该文件 |
如果目标文件已经存在,则抛出 FileAlreadyExistsException
异常。但是,如果将 CopyOption.REPLACE_EXISTING
用作 move
方法的第三个参数,则不会引发异常。当源是符号链接时,将复制该链接,而不是链接的目标。
有几个变化和问题需要涵盖。这些措施包括:
move
方法的琐碎用途StandardCopyOption
枚举值的含义- 使用
resolveSibling
方法和move
方法影响重命名操作 - 移动目录
如果源文件和目标文件相同,则该方法不会产生任何效果。以下代码序列将无效:
Path sourceFile = ...;
Files.move(sourceFile, sourceFile);
不会引发异常,并且不会移动文件。
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
方法时影响重命名操作。按照以下顺序, 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
类中有两种方法可以用来删除文件或目录: delete
和 deleteIfExists
。他们都以一个 Path
对象作为论据,并可能抛出一个 IOException
。
要删除文件或目录,需要执行以下操作:
- 获取一个表示文件或目录的
Path
对象。 - 使用
delete
或deleteIfExists
方法删除元素。
-
创建新的控制台应用程序并使用以下
main
方法:public static void main(String[] args) throws Exception { Path sourceFile = Paths.get("C:/home/docs/users.txt"); Files.delete(sourceFile); }
-
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
方法创建指向现有文件的硬链接readSymbolicLink
将Path
检索到目标文件
链接通常对文件的用户是透明的。对符号链接的任何访问都将重定向到引用的文件。硬链接类似于符号链接,但有更多限制。这些类型的链接在中有更详细的讨论。。。这是食谱的一部分。
为了创建指向文件的符号链接:
- 获取一个表示链接的
Path
对象。 - 获取一个
Path
对象,表示目标文件。 - 使用这些路径作为
createSymbolicLink
方法的参数。
-
创建一个新的控制台应用程序。将以下
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); }
-
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:客户端没有所需的权限。
-
验证名为
users.txt
的新文件是否存在于music
目录中。检查文件的属性以验证它是否为符号链接。在 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
方法,如第 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文件系统对象,它将特定数据与文件或目录关联到应用程序。文件系统筛选器可以与重分析点类型相关联。当文件系统打开文件时,它会将此信息传递给文件系统过滤器进行处理。这种方法是扩展文件系统功能的一种方法。