敏捷冲冲_

The Art of software design

Java 文件路径详解

刚接触 Java 的小伙伴,都学习过使用 java.io.* 类库操作磁盘文件。而操作文件时需要指定文件的路径,在 Java 中,文件路径分为两种:绝对路径相对路径

绝对路径的概念:文件在磁盘上真正存在的完整地址,通常是从盘符开始的路径。

相对路径的概念:相对路径指的是相对于 JVM 进程启动路径的路径。

举个例子:假设有一个 Java 源文件 Example.java 在 D 盘根目录下,那么它的绝对路径地址就是 D:\Example.java。如果我们使用 javac Example.java 命令来编译此文件,然后在 D 盘根目录下再调用 java Example 来运行此文件,此时我们就启动了一个 jvm 进程,这个 jvm 进程是在 D 盘根目录下被启动的,所以此时 jvm 所加载的程序中 File 类的相对路径就是相对于这个路径的。

1
2
3
4
5
6
7
8
9
import java.io.File;
import java.io.IOException;

public class Example {
public static void main(String[] args) throws IOException {
File file = new File("hello.txt");
file.createNewFile();
}
}

假设 jvm 是在 “D:\” 下启动的,那么 hello.txt 就会生成在 D:\hello.txt。

假设 jvm 是在 “C:\” 下启动的,那么 hello.txt 就会生成在 C:\hello.txt。

user.dir

JDK 官方文档对 user.dir 的解释:User’s current working directory.

翻译过来就是 “用户的当前工作目录”,网上一些文章说 user.dir 代表的是 jvm 的启动路径,其实这种说法并不完全正确,在大多数情况下 user.dir 确实与 jvm 的启动路径相同。 我们可以通过 System.getProperty 静态方法获取 user.dir 的值:

1
System.getProperty("user.dir"); // 返回值 D:\

但是,JDK 官方文档中也有说明 user.dir 是一个可配置的 jvm 启动参数,通过以下命令启动 Java 程序就可以修改 user.dir 的值:

1
java -Duser.dir="D:\demo" Example

然而,通过以上命令再次执行 Example 程序,会报 java.lang.ClassNotFoundException 错误,因为当前程序的工作目录已经被修改为 D:\demo 了,所以 jvm 进程虽然在 D:\ 下被启动,但会去 D:\demo 工作目录中加载 Example 类:

1
2
3
D:\>java -Duser.dir="d:\\demo" Example
错误: 找不到或无法加载主类 Example
原因: java.lang.ClassNotFoundException: Example

如果我们把编译好的 Example.class 复制到 D:\demo 中,就可以执行成功。并且读取到的 user.dir 确实是我们修改后的目录:

1
2
D:\>java -Duser.dir="d:\\demo" Example
user.dir: d:\\demo

注意看上面的执行命令,我们是在 D:\ 目录下执行的 java 命令,jvm 进程也在这里启动,虽然我们修改了 user.dir 启动参数,但是我们创建的 hello.txt 文件,依然被创建在了 D:\hello.txt,而不是我们以为的 D:\demo\hello.txt。

还记得我们之前说的相对路径的概念吗,相对路径指的是相对于 JVM 进程启动路径的路径,user.dir 只是修改了用户的工作目录,所以这里可以证明 user.dir ≠ jvm 启动路径。也就是说不能把 user.dir 当做 jvm 的启动路径去理解和使用。

java.io.File 类

JDK 官方文档中针对 File 类的相对路径有这么一段描述:

A pathname, whether abstract or in string form, may be either absolute or relative. An absolute pathname is complete in that no other information is required in order to locate the file that it denotes. A relative pathname, in contrast, must be interpreted in terms of information taken from some other pathname. By default the classes in the java.io package always resolve relative pathnames against the current user directory. This directory is named by the system property user.dir, and is typically the directory in which the Java virtual machine was invoked.

一个路径,无论是抽象的还是字符串形式的,都无非是绝对路径或相对路径。绝对路径是完整的,因为不需要再补充其它信息来定位它所指示的文件。相比之下,相对路径必须根据来自其它路径的信息来进行解释。默认情况下,java.io 包总是根据用户的当前工作目录来解析相对路径。该路径由系统属性 user.dir 标识,通常它是调用 Java 虚拟机的目录。

改写我们刚才的程序:

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.File;
import java.io.IOException;

public class Example {
public static void main(String[] args) throws IOException {
System.out.println("user.dir: " + System.getProperty("user.dir"));

File file = new File("hello.txt");
file.createNewFile();
System.out.println("file created at: " + file.getAbsolutePath());
}
}

在 D:\ 下执行,会创建 D:\hello.txt 文件:

1
2
3
D:\>java Example
user.dir: D:\
file created at: D:\\hello.txt

把 D:\hello.txt 删除掉,在 D:\ 下执行程序,但是人为修改 jvm 的 user.dir 属性:

1
2
3
D:\>java -Duser.dir="d:\\demo" Example
user.dir: d:\\demo
file created at: d:\demo\hello.txt

查看下 D 盘根目录,对的,你没有看错,真实创建的文件依然是 D:\hello.txt,而不是我们以为的 D:\demo\hello.txt。但是 File 类的方法,确实是受到了 user.dir 的影响,getAbsolutePath 方法返回的值是相对于 user.dir 的,与真实创建的文件不能对应。

结论:

Java 操作文件时,如果给定的是相对路径,Java 会使用 JVM 进程的启动路径作为参照来处理文件。但是 java.io 包在处理相对路径时,总是根据用户的当前工作目录来解析相对路径。

所以,我们在处理文件的时候,最好都先把文件转换为绝对路径,再进行操作。

Linux 和 Windows 路径分隔符

Linux 下:“/”

Windows 下:“\”

Java 中获取系统分隔符:System.getProperty(“file.separator”)

参考资料

https://docs.oracle.com/javase/tutorial/essential/io/path.html

https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/System.html#getProperties()

https://docs.oracle.com/javase/7/docs/api/java/io/File.html