Java 中文官方教程 2022 版(八)(1)https://developer.aliyun.com/article/1486329
什么是路径?(以及其他文件系统事实)
文件系统在某种介质上存储和组织文件,通常是一个或多个硬盘,以便可以轻松检索文件。今天大多数使用的文件系统将文件存储在树(或分层)结构中。树的顶部是一个(或多个)根节点。在根节点下面,有文件和目录(在 Microsoft Windows 中称为文件夹)。每个目录可以包含文件和子目录,子目录又可以包含文件和子目录,依此类推,可能深入到几乎无限的深度。
本节涵盖以下内容:
- 什么是路径?
- 相对还是绝对?
- 符号链接
什么是路径?
以下图显示了包含单个根节点的示例目录树。Microsoft Windows 支持多个根节点。每个根节点映射到一个卷,例如C:\
或D:\
。Solaris OS 支持单个根节点,用斜杠字符/
表示。
示例目录结构
文件通过其在文件系统中的路径来标识,从根节点开始。例如,在前面的图中,Solaris OS 中的statusReport
文件由以下表示:
/home/sally/statusReport
在 Microsoft Windows 中,statusReport
由以下表示:
C:\home\sally\statusReport
用于分隔目录名称的字符(也称为分隔符)特定于文件系统:Solaris OS 使用正斜杠(/
),而 Microsoft Windows 使用反斜杠(\
)。
相对还是绝对?
路径可以是相对的或绝对的。绝对路径始终包含根元素和完整的目录列表,以定位文件。例如,/home/sally/statusReport
是一个绝对路径。定位文件所需的所有信息都包含在路径字符串中。
相对路径需要与另一个路径结合才能访问文件。例如,joe/foo
是一个相对路径。没有更多信息,程序无法可靠地定位文件系统中的joe/foo
目录。
符号链接
文件系统对象通常是目录或文件。每个人都熟悉这些对象。但是一些文件系统也支持符号链接的概念。符号链接也称为symlink或soft link。
符号链接是一个特殊文件,用作指向另一个文件的引用。在大多数情况下,符号链接对应用程序是透明的,对符号链接的操作会自动重定向到链接的目标。(被指向的文件或目录称为链接的目标。)例外情况是当符号链接被删除或重命名时,链接本身被删除或重命名,而不是链接的目标。
在下图中,对用户来说,logFile
看起来像是一个常规文件,但实际上它是指向dir/logs/HomeLogFile
的符号链接。HomeLogFile
是链接的目标。
符号链接示例。
对用户来说,符号链接通常是透明的。读取或写入符号链接与读取或写入任何其他文件或目录相同。
解析链接这个短语意味着用文件系统中的实际位置替换符号链接。在这个例子中,解析logFile
会得到dir/logs/HomeLogFile
。
在现实场景中,大多数文件系统广泛使用符号链接。偶尔,粗心创建的符号链接可能会导致循环引用。循环引用发生在链接的目标指向原始链接的情况下。循环引用可能是间接的:目录a
指向目录b
,后者指向目录c
,后者包含一个子目录指向目录a
。当程序递归遍历目录结构时,循环引用可能会造成混乱。然而,这种情况已经考虑到,不会导致程序无限循环。
下一页将讨论 Java 编程语言中文件 I/O 支持的核心,即Path
类。
Path 类
原文:
docs.oracle.com/javase/tutorial/essential/io/pathClass.html
Path
类是 Java SE 7 版本中引入的主要入口点之一,属于java.nio.file
包。如果您的应用程序使用文件 I/O,您将希望了解此类的强大功能。
版本说明: 如果您有使用java.io.File
的 JDK7 之前的代码,您仍然可以通过使用File.toPath
方法来利用Path
类的功能。有关更多信息,请参阅旧版文件 I/O 代码。
正如其名称所示,Path
类是文件系统中路径的程序表示。Path
对象包含用于构建路径的文件名和目录列表,并用于检查、定位和操作文件。
一个Path
实例反映了底层平台。在 Solaris 操作系统中,Path
使用 Solaris 语法(/home/joe/foo
),而在 Microsoft Windows 中,Path
使用 Windows 语法(C:\home\joe\foo
)。Path
不是系统独立的。你不能比较来自 Solaris 文件系统的Path
并期望它与来自 Windows 文件系统的Path
匹配,即使目录结构相同,两个实例都定位到相同的相对文件。
与Path
对应的文件或目录可能不存在。您可以创建一个Path
实例并以各种方式操作它:您可以附加到它,提取它的部分,将其与另一个路径进行比较。在适当的时候,您可以使用Files
类中的方法来检查与Path
对应的文件的存在性,创建文件,打开文件,删除文件,更改其权限等。
下一页将详细讨论Path
类。
Path 操作
原文:
docs.oracle.com/javase/tutorial/essential/io/pathOps.html
Path
类包括各种方法,可用于获取有关路径的信息,访问路径的元素,将路径转换为其他形式或提取路径的部分。还有用于匹配路径字符串的方法以及用于删除路径中冗余的方法。本课程介绍了这些Path
方法,有时称为语法操作,因为它们作用于路径本身,而不访问文件系统。
本节涵盖以下内容:
- 创建一个 Path
- 检索有关 Path 的信息
- 从 Path 中删除冗余
- 转换 Path
- 连接两个路径
- 在两个路径之间创建路径
- 比较两个路径
创建一个 Path
一个Path
实例包含用于指定文件或目录位置的信息。在定义时,Path
会提供一系列一个或多个名称。可能包括根元素或文件名,但都不是必需的。Path
可能仅包含单个目录或文件名。
您可以通过使用Paths
(注意复数形式)辅助类中的以下get
方法之一轻松创建Path
对象:
Path p1 = Paths.get("/tmp/foo"); Path p2 = Paths.get(args[0]); Path p3 = Paths.get(URI.create("file:///Users/joe/FileTest.java"));
Paths.get
方法是以下代码的简写:
Path p4 = FileSystems.getDefault().getPath("/users/sally");
以下示例假定您的主目录是/u/joe
,则创建/u/joe/logs/foo.log
,或者如果您在 Windows 上,则为C:\joe\logs\foo.log
。
Path p5 = Paths.get(System.getProperty("user.home"),"logs", "foo.log");
检索有关路径的信息
你可以将Path
看作将这些名称元素存储为序列。目录结构中最高的元素位于索引 0。目录结构中最低的元素位于索引[n-1]
,其中n
是Path
中名称元素的数量。可用于使用这些索引检索单个元素或Path
的子序列的方法。
本课程中的示例使用以下目录结构。
示例目录结构
以下代码片段定义了一个Path
实例,然后调用了几种方法以获取有关路径的信息:
// None of these methods requires that the file corresponding // to the Path exists. // Microsoft Windows syntax Path path = Paths.get("C:\\home\\joe\\foo"); // Solaris syntax Path path = Paths.get("/home/joe/foo"); System.out.format("toString: %s%n", path.toString()); System.out.format("getFileName: %s%n", path.getFileName()); System.out.format("getName(0): %s%n", path.getName(0)); System.out.format("getNameCount: %d%n", path.getNameCount()); System.out.format("subpath(0,2): %s%n", path.subpath(0,2)); System.out.format("getParent: %s%n", path.getParent()); System.out.format("getRoot: %s%n", path.getRoot());
这是 Windows 和 Solaris OS 的输出:
调用的方法 | Solaris OS 中的返回 | Microsoft Windows 中的返回 | 注释 |
toString |
/home/joe/foo |
C:\home\joe\foo |
返回Path 的字符串表示。如果路径是使用Filesystems.getDefault().getPath(String) 或Paths.get (后者是getPath 的便利方法)创建的,则该方法会执行轻微的语法清理。例如,在 UNIX 操作系统中,它将修正输入字符串//home/joe/foo 为/home/joe/foo 。 |
getFileName |
foo |
foo |
返回文件名或名称元素序列的最后一个元素。 |
getName(0) |
home |
home |
返回与指定索引对应的路径元素。第 0 个元素是最靠近根的路径元素。 |
getNameCount |
3 |
3 |
返回路径中的元素数量。 |
subpath(0,2) |
home/joe |
home\joe |
返回Path 的子序列(不包括根元素),由开始和结束索引指定。 |
getParent |
/home/joe |
\home\joe |
返回父目录的路径。 |
getRoot |
/ |
C:\ |
返回路径的根。 |
前面的示例显示了绝对路径的输出。在以下示例中,指定了相对路径:
// Solaris syntax Path path = Paths.get("sally/bar"); or // Microsoft Windows syntax Path path = Paths.get("sally\\bar");
以下是 Windows 和 Solaris OS 的输出:
调用的方法 | Solaris OS 中返回 | Microsoft Windows 中返回 |
toString |
sally/bar |
sally\bar |
getFileName |
bar |
bar |
getName(0) |
sally |
sally |
getNameCount |
2 |
2 |
subpath(0,1) |
sally |
sally |
getParent |
sally |
sally |
getRoot |
null |
null |
从路径中删除冗余
许多文件系统使用“.”符号表示当前目录,“…”表示父目录。您可能会遇到路径包含冗余目录信息的情况。也许服务器配置为将其日志文件保存在“/dir/logs/.
”目录中,您希望从路径中删除末尾的“/.`”符号。
以下示例都包含冗余:
/home/./joe/foo /home/sally/../joe/foo
normalize
方法会删除任何冗余元素,包括任何“.”或“
directory/…”出现。前面两个示例都会规范化为
/home/joe/foo`。
值得注意的是,normalize
在清理路径时不会检查文件系统。这是一个纯语法操作。在第二个示例中,如果sally
是一个符号链接,删除sally/..
可能导致Path
不再定位到预期的文件。
为了清理路径并确保结果定位到正确的文件,您可以使用toRealPath
方法。该方法在下一节转换路径中描述。
转换路径
您可以使用三种方法来转换Path
。如果需要将路径转换为可以从浏览器打开的字符串,可以使用toUri
。例如:
Path p1 = Paths.get("/home/logfile"); // Result is file:///home/logfile System.out.format("%s%n", p1.toUri());
toAbsolutePath
方法将路径转换为绝对路径。如果传入的路径已经是绝对路径,则返回相同的Path
对象。在处理用户输入的文件名时,toAbsolutePath
方法非常有帮助。例如:
public class FileTest { public static void main(String[] args) { if (args.length < 1) { System.out.println("usage: FileTest file"); System.exit(-1); } // Converts the input string to a Path object. Path inputPath = Paths.get(args[0]); // Converts the input Path // to an absolute path. // Generally, this means prepending // the current working // directory. If this example // were called like this: // java FileTest foo // the getRoot and getParent methods // would return null // on the original "inputPath" // instance. Invoking getRoot and // getParent on the "fullPath" // instance returns expected values. Path fullPath = inputPath.toAbsolutePath(); } }
toAbsolutePath
方法转换用户输入并返回一个Path
,在查询时返回有用的值。此方法不需要文件存在即可工作。
toRealPath
方法返回现有文件的真实路径。该方法一次执行多个操作:
- 如果向该方法传递
true
,并且文件系统支持符号链接,则该方法会解析路径中的任何符号链接。 - 如果
Path
是相对路径,则返回绝对路径。 - 如果
Path
包含任何多余的元素,则返回一个删除了这些元素的路径。
如果文件不存在或无法访问,则此方法会抛出异常。您可以在需要处理这些情况时捕获异常。例如:
try { Path fp = path.toRealPath(); } catch (NoSuchFileException x) { System.err.format("%s: no such" + " file or directory%n", path); // Logic for case when file doesn't exist. } catch (IOException x) { System.err.format("%s%n", x); // Logic for other sort of file error. }
连接两个路径
您可以使用resolve
方法组合路径。您传入一个部分路径,即不包括根元素的路径,并将该部分路径附加到原始路径。
例如,考虑以下代码片段:
// Solaris Path p1 = Paths.get("/home/joe/foo"); // Result is /home/joe/foo/bar System.out.format("%s%n", p1.resolve("bar")); or // Microsoft Windows Path p1 = Paths.get("C:\\home\\joe\\foo"); // Result is C:\home\joe\foo\bar System.out.format("%s%n", p1.resolve("bar"));
将绝对路径传递给resolve
方法会返回传入的路径:
// Result is /home/joe Paths.get("foo").resolve("/home/joe");
创建两个路径之间的路径
在编写文件 I/O 代码时,通常需要能够构造从文件系统中的一个位置到另一个位置的路径。您可以使用relativize
方法来实现这一点。该方法构造一个源自原始路径并以传入路径指定的位置结束的路径。新路径是相对于原始路径的。
例如,考虑两个定义为joe
和sally
的相对路径:
Path p1 = Paths.get("joe"); Path p2 = Paths.get("sally");
在没有其他信息的情况下,假定joe
和sally
是兄弟姐妹,即在树结构中处于同一级别的节点。要从joe
导航到sally
,你需要先向上导航一级到父节点,然后再向下导航到sally
:
// Result is ../sally Path p1_to_p2 = p1.relativize(p2); // Result is ../joe Path p2_to_p1 = p2.relativize(p1);
考虑一个稍微复杂的例子:
Path p1 = Paths.get("home"); Path p3 = Paths.get("home/sally/bar"); // Result is sally/bar Path p1_to_p3 = p1.relativize(p3); // Result is ../.. Path p3_to_p1 = p3.relativize(p1);
在这个例子中,这两个路径共享相同的节点home
。要从home
导航到bar
,你首先向下导航一级到sally
,然后再向下导航一级到bar
。从bar
到home
的导航需要向上移动两级。
如果只有一个路径包含根元素,则无法构造相对路径。如果两个路径都包含根元素,则构造相对路径的能力取决于系统。
递归Copy
示例使用relativize
和resolve
方法。
比较两个路径
Path
类支持equals
,使您能够测试两个路径是否相等。startsWith
和endsWith
方法使您能够测试路径是否以特定字符串开头或结尾。这些方法易于使用。例如:
Path path = ...; Path otherPath = ...; Path beginning = Paths.get("/home"); Path ending = Paths.get("foo"); if (path.equals(otherPath)) { // *equality logic here* } else if (path.startsWith(beginning)) { // *path begins with "/home"* } else if (path.endsWith(ending)) { // *path ends with "foo"* }
Path
类实现了Iterable
接口。iterator
方法返回一个对象,使你能够遍历路径中的名称元素。返回的第一个元素是在目录树中最接近根的元素。以下代码片段遍历一个路径,打印每个名称元素:
Path path = ...; for (Path name: path) { System.out.println(name); }
Path
类还实现了Comparable
接口。你可以使用compareTo
比较Path
对象,这对于排序很有用。
你也可以将Path
对象放入Collection
中。查看 Collections trail,了解更多关于这一强大功能的信息。
当你想要验证两个Path
对象是否定位到同一文件时,可以使用isSameFile
方法,如 Checking Whether Two Paths Locate the Same File 中所述。
文件操作
原文:
docs.oracle.com/javase/tutorial/essential/io/fileOps.html
Files
类是java.nio.file
包的另一个主要入口点。该类提供了丰富的静态方法集,用于读取、写入和操作文件和目录。Files
方法适用于Path
对象的实例。在继续阅读其余部分之前,您应该熟悉以下常见概念:
- 释放系统资源
- 捕获异常
- 可变参数
- 原子操作
- 方法链
- 什么是 Glob?
- 链接感知
释放系统资源
此 API 中使用的许多资源,如流或通道,实现或扩展了java.io.Closeable
接口。Closeable
资源的要求是在不再需要时必须调用close
方法来释放资源。忽略关闭资源可能会对应用程序的性能产生负面影响。下一节描述的try-
with-resources 语句会为您处理此步骤。
Java 中文官方教程 2022 版(八)(3)https://developer.aliyun.com/article/1486340