转自开发者的天空
在上面的一篇文章中,我们已经说过了Path 类的操作主要有两种:对路径的操作和对文件的操 作。这篇文章中我们就来了解一下对路径的操作。
创建Path实例
Path实例包含了指定文件或目录位置的信息,在实例化Path类时,需要指定一个或多个目录或文件名。路径的根目录不是必须的;路径信息可能仅仅是一个 目录或文件的名称。
最简单的创建Path实例的方式就是使用Paths(注意这里有一个s)类的get方法:
Path p1 = Paths.get("/tmp/foo");
Path p2 = Paths.get(args[0]);
Path p3 = Paths.get("file:///Users/joe/FileTest.java");
Path类接受String或URI作为参数。
获取路径信息
前面我们已经说过了,File System一般是树形结构,因此我们可以把Path理解为按顺序存储的一系列的名称(目录名称和文件名称)。目录结构中最高一层的目录名就是序列中 index为0的那一个,目录结构中最低一层的目录名或者文件名就是序列中index为n-1的那一个(这里n是路径中层次的数目)。Path类提供方法 来通过index获取序列中的一个元素或一个子序列。
随后的例子中我们使用的目录结构如下图:
下面的代码定义了一个Path对象并获取其中的信息。要注意的是这些代码中除了isHidden方法外,其他的方法并不需要指定的目录或文件存在;如果不 存在,isHidden方法会抛出异常。
Path path = Paths.get("C://home//joe//foo"); // Microsoft Windows syntax
//Path path = Paths.get("/home/joe/foo"); // Solaris syntax
System.out.format("toString: %s%n", path.toString());
System.out.format("getName: %s%n", path.getName());
System.out.format("getName(0): %s%n", path.getName(0));
System.out.format("getNameCount: %d%n", path.getNameCount());
System.out.format("subpath(0,2): %d%n", path.subpath(0,2));
System.out.format("getParent: %s%n", path.getParent());
System.out.format("getRoot: %s%n", path.getRoot());
System.out.format("isHidden: %s%n", path.isHidden());
下面是这段代码的输出情况
方法 | Solaris下的输出 | Windows下的 输出 | 备注 |
toString | /home/joe/foo | C:/home/joe/foo | |
getName | foo | foo | 获 取名称序列中的最后一个,也就是最底层的目录或文件名 |
getName(0) | home | home | 获 取名称序列中的第一个,也就是最靠近根目录的那一层。注意根目录不在名称序列中 |
getNameCount | 3 | 3 | 获 取名称序列的元素个数 |
subpath(0,2) | home/joe | home/joe | 获 取从指定的开始点到指定的结束点的子路径。注意这里包括开始点,但不包括结束点。 |
getParent | /home/joe | /home/joe | 返 回Path指定的目录或文件的父目录 |
getRoot | / | C:/ | 返 回根目录 |
isHidden | false | false | 如果文件是 隐藏文件,或者目录是隐藏目录,返回true。因为要访问文件的属性,所以如果Path指定的目录或者文件不存在,会抛出异常。 |
上面的代码中我们创建Path时使用的是绝对路径,下面我们来看看创建路径时使用相对路径时,这段代码的执行结果:
//Path path = Paths.get("sally/bar"); // Solaris syntax
Path path = Paths.get("sally//bar"); // Microsoft Windows syntax
大 家可以自行去实验一下具体的输出是什么。
去除Path中的冗余
在很多文件系统中我们使用'.'来代表当前目录,使用'..'代表父目录。在有些情况下我们创建的路径中会有冗余的路径信息,例如:
/home/./joe/foo
/home/sally/../joe/foo
方法normalize会去除这些冗余信息,包括'.'或'directory/..'。上面的两个例子在去除冗余信息后都是/home/joe /foo。
要注意的是normalize方法并不去检查文件系统,它只是简单的进行语法操作。在第二个例子中,如果sally是一个指向其他的目录的符号链接,那么 去除了sally/..后可能导致Path不在指向原来的文件或目录。
如果你需要清除冗余信息,又要保证结果仍然指向正确的文件或目录,可以使用toRealPath方法。在下面我们会讲到这个方法。
转换Path
有3个方法用来转换Path。
连接两个Path
可以使用resolve方法来将两个Path连接起来。该方法的参数是一个字符串。如果该字符串代表的是一个相对路径,那么这个路径会被扩展到原来的路径 后。如果传入的字符串是一个绝对路径,那么返回的值就是传入的这个绝对路径。例如:
创建两个路径之间的路径
这个功能说起来有些绕口,实际的功能就是创建两个指定的目录或文件之间的相对路径。例如:
在这个例子中,由于两个路径都是相对路径,没有其他的 信息,我们会认为这两个joe和sally是同一级的兄弟目录,因此有以下的结果
让我们看看另外一个例子:
在这个例子中,两个路径共享同一个节点-home, 所以结果并不是../home/sally/bar和../../../home.
如果两个路径中有一个是绝对路径,另外一个是相对路径,relative方法会抛出异常。如果两个路径都是绝对路径,那么relative方法的行为和系 统相关,不同的系统可能不同。
我在Windows操作系统下实验了一下,如果两个路径属于同一个硬盘,那么可以执行成功,否则会抛出异常。
Path 的比较
Path提供equals 方法来检查两个Path是否相等。但是这里 要注意的是比较的并不是两个Path是否指向同一个目录或者文件。请看下面的例子:
Path类实现了Iterable接口,iterator方法会返回一个Iterator对象,该对象中的第一个元素就是原 路径中最上层(最靠近根节点)的目录。下面是使用这个方法的例子:
Path类还实现了Comparable接口,因此可以使用compareTo来比较两个Path。比较的算法和结果是和文 件系统的提供者和系统平台相关的。大家在使用之前,最后先实验一下。
Path类还提供了一个方法isSameFile来检查两个Path是否指向同一个目录或文件。如果作为参数的Path为null,那么会直接返回 false,不会去检查Path指向的文件是否存在。如果两Path来自不同的文件系统提供者,那么也会直接返回false,不会去检查文件或目录是否存 在。如果两个Path执行equals方法的返回结果为true,那么这个方法直接返回true,也不会去检查文件或目录是否存在。其他的情况下是否会去 打开或访问Path指向的文件或目录是与具体的实现相关的,也就是说不同的JDK/JRE可能会有不同的行为。
验证文件或目录是否存在
上面所介绍的很多方法都不会去验证Path指向的文件或目录是否存在,只是操作Path实例自身。但是在有些情况下,你 需要访问文件系统来验证文件、目录存在与否,这时你可以使用exists和notExists方法。需要注意的是!path.exists()并不等于 path.notExists()。当你调用这两个方法时,有以下3中情况:
如果exists()和notExists()都返回false,说明无法验证该文件是否存在。