系列中的上一篇
当前教程
路径操作
系列中的下一篇

系列中的上一篇: 使用路径访问资源

系列中的下一篇: 访问文件系统

路径操作

 

创建路径

使用 Paths 工厂类

一个 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(String) 方法是以下代码的简写

Path p4 = FileSystems.getDefault().getPath("/users/sally");

以下示例创建 /u/joe/logs/foo.log,假设您的主目录是 /u/joe,或者如果您在 Windows 上,则为 C:\joe\logs\foo.log

Path p5 = Paths.get(System.getProperty("user.home"),"logs", "foo.log");

使用 Path.of() 工厂方法

Java SE 9 中已向 Path 接口添加了两个工厂方法。

第一个方法接受一个字符字符串,表示路径字符串或路径字符串的初始部分。它可以接受作为可变参数的更多字符字符串,这些字符串将连接起来形成路径字符串。

第二个方法接受一个 URI,该 URI 将转换为此路径。

以下代码使用第一个工厂方法创建路径。

Path debugFile = Path.of("/tmp/debug.log");

 

检索有关路径的信息

您可以将 Path 视为将这些名称元素存储为一个序列。目录结构中最高的元素将位于索引 0 处。目录结构中最低的元素将位于索引 [n-1] 处,其中 nPath 中的名称元素数量。可以使用这些索引检索单个元素或 Path 的子序列的方法。

本节中的示例使用以下目录结构。

Sample Directory Structure

示例目录结构

以下代码片段定义了一个 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 操作系统的输出

调用的方法 Solaris 操作系统中的返回值 Microsoft Windows 中的返回值 注释
toString() /home/joe/foo C:\home\joe\foo 返回 Path 的字符串表示形式。如果路径是使用 Filesystems.getDefault().getPath(String)Paths.get()Path.of()(后者是 getPath 的便捷方法)创建的,该方法会执行一些轻微的语法清理。例如,在 UNIX 操作系统中,它会将输入字符串 //home/joe/foo 修正为 /home/joe/foo
getFileName() foo foo 返回文件名或名称元素序列的最后一个元素。
getName(0) home home
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");
// Microsoft Windows syntax
Path path = Paths.get("sally\\bar");

以下是 Windows 和 Solaris 操作系统的输出

调用的方法 Solaris 操作系统中的返回值 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

 

从路径中删除冗余

许多文件系统使用 "." 表示法表示当前目录,使用 ".." 表示父目录。您可能遇到 Path 包含冗余目录信息的情况。也许服务器配置为将其日志文件保存在 /dir/logs/. 目录中,并且您希望从路径中删除尾随的 "/." 表示法。

以下示例都包含冗余

/home/./joe/foo
/home/sally/../joe/foo

normalize() 方法会删除任何冗余元素,包括任何 "." 或 "directory/.." 出现。前面的两个示例都规范化为 /home/joe/foo

需要注意的是,规范化在清理路径时不会检查文件系统。这是一个纯粹的语法操作。在第二个示例中,如果 sally 是一个符号链接,删除 sally/.. 可能会导致 Path 无法再定位目标文件。

要清理路径,同时确保结果定位到正确文件,可以使用 toRealPath() 方法。此方法将在下一节中介绍。

 

转换路径

您可以使用三种方法来转换 Path。如果您需要将路径转换为可以在浏览器中打开的字符串,可以使用 toUri()。例如

Path p1 = Paths.get("/home/logfile");

System.out.format("%s%n", p1.toUri());

运行此代码会产生以下结果

file:///home/logfile

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,该 Path 在查询时会返回有用的值。该文件不需要存在才能使此方法起作用。

toRealPath() 方法返回现有文件的真实路径。此方法在一个方法中执行多个操作

  • 如果将 true 传递给此方法,并且文件系统支持符号链接,则此方法会解析路径中的任何符号链接。
  • 如果路径是相对路径,则返回绝对路径。
  • 如果路径包含任何冗余元素,则返回一个已删除这些元素的路径。

如果文件不存在或无法访问,此方法会抛出异常。当您想要处理任何这些情况时,可以捕获异常。例如

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"));

或者

// 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() 方法来满足此需求。此方法构造一个从原始路径开始并以传入路径指定的 location 结束的路径。新路径相对于原始路径。

例如,考虑定义为 joesally 的两个相对路径

Path p1 = Paths.get("joe");
Path p2 = Paths.get("sally");

在没有其他信息的情况下,假设joesally是兄弟姐妹,这意味着它们在树结构中位于同一级别。要从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中。有关此强大功能的更多信息,请参见集合教程

当您要验证两个Path对象是否定位同一个文件时,可以使用isSameFile()方法来自Files类,如检查两条路径是否定位同一个文件部分所述。


上次更新: 2023年1月25日


系列中的上一篇
当前教程
路径操作
系列中的下一篇

系列中的上一篇: 使用路径访问资源

系列中的下一篇: 访问文件系统