额, 不得不说, 论坛是个好地方, 每隔几天总有很好的问题出现. 这篇博客还是因为一个有意思的帖子. 这次的问题和路径的表示方法有关. 帖子在这里: 《求java大神,关于se中的文件操作》
注: “完全相对路径”和”目录相对路径” 的英文分别为 “Completely relative”, “Directory-relative”, 限于本人英文水平, 翻译的可能有些问题, 还请大家指导
原问题
帖子地址: http://bbs.csdn.net/topics/391025871.
问题的大致意思是说: 当使用File(String pathname)
去构造一个File
对象是, 参数"e:"
和"e:\\"
有什么区别?
估计大多数人和我最开始的想法一样: 没区别. 然后下面就有位仁兄截了个图, 证明这两个是有区别的. 本着实事求是的精神, 我也测了测, 发现当java程序的当前路径在e盘时, "e:"
和"e:\\"
还真不一样…
源程序
为了方便说明问题, 这里的源程序跟帖子上稍微有点区别.
import java.io.*;
class Test
{
public static void main(String[] args)
{
digui(new File("e:"));
digui(new File("e:\\"));
}
public static void digui(File f)
{
System.out.println(f.getPath());
if( !f.exists() ){
System.out.println("not exist");
return;
}
File [] name = f.listFiles();
System.out.println(name.length);
for(int i = 0 ;i < name.length; i++)
{
System.out.println(name[i]);
}
}
}
将这个程序中的 "e:"
和"e:\\"
改成自己的工作盘符, 运行程序, 就会发现这两个路径的区别…
API文档上有关的说明
为了解释这个现象, 我首先查看了File
类的文档, 看看有没有什么特殊说明. 只是发现了这样一句话
对于 Microsoft Windows 平台,包含盘符的路径名前缀由驱动器号和一个 “:” 组成。如果路径名是绝对路径名,还可能后跟 “\\”。
注意, 上面说如果是绝对路径名, 还可能跟"\\"
. 也就是说, 即使是以 驱动器号+”:” 开始的路径, 也不一定是绝对路径!
同时,经过测试, getPath
方法得到的并不是完整的路径!仅仅是把我们传进去的参数标准化了而已.
我们在API中发现两个可以替代的方法: getAbsolutePath
方法, 以及getCanonicalPath
方法. 这里我们只使用getAbsolutePath
方法
public String getAbsolutePath()
返回此抽象路径名的绝对路径名字符串。
(以下省略n行)…
更详细的信息, 请查看JDK API文档.
然后我们修改一下我们的程序.将
System.out.println(f.getPath());
修改为
System.out.println("path:[" + f.getPath() + "] absolutePath:[" + f.getAbsolutePath() + "]");
从API文档只能得到这么多信息了, 为了的到更加详细的信息, 接下来, 我们看看Java类库的源码.(我的是jdk 1.8.0_45附带的源码)
从Java类库源码得到的信息
从输出来看, getAbsolutePath
方法输出的就是File
对象所表示的路径, 那么就让我们先看看File
类的构造方法, 然后再看看getAbsolutePath
方法. 可能就会得到我们想要的信息.
File
类的构造方法
首先, 我们看看File(String pathname)
方法的源码:
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
这里的normalize(pathname)
是标准化路径. 我们就不继续向下查看的. 对于我们讨论的这两种路径表示方法, 返回值都是原本的值.
getAbsolutePath
方法
由于getAbsolutePath
里需要使用到prefixLength
属性. 同时在Windows下, 实现FileSystem接口的类是:WinNTFileSystem
类. 所以, 我们看看WinNTFileSystem.prefixLength(path)
方法:
java.io.WinNTFileSystem.java
public int prefixLength(String path) {
char slash = this.slash;
int n = path.length();
if (n == 0) return 0;
char c0 = path.charAt(0);
char c1 = (n > 1) ? path.charAt(1) : 0;
if (c0 == slash) {
if (c1 == slash) return 2; /* Absolute UNC pathname "\\\\foo" */
return 1; /* Drive-relative "\\foo" */
}
if (isLetter(c0) && (c1 == ':')) {
if ((n > 2) && (path.charAt(2) == slash))
return 3; /* Absolute local pathname "z:\\foo" */
return 2; /* Directory-relative "z:foo" */
}
return 0; /* Completely relative */
}
从上面的代码来看
-
"e:\\"
对应的File
对象的prefixLength
属性为3
-
"e:"
对应的File
对象的prefixLength
属性为2
FileSystem.resolve
方法
由于File.getAbsolutePath
方法直接调用了FileSystem.resolve
. 所以,我们查看一下WinNTFileSystem.resolve
方法:
public String resolve(File f) {
String path = f.getPath();
int pl = f.getPrefixLength();
if ((pl == 2) && (path.charAt(0) == slash))
return path; /* UNC */
if (pl == 3)
return path; /* Absolute local */
if (pl == 0)
return getUserPath() + slashify(path); /* Completely relative */
if (pl == 1) { /* Drive-relative */
String up = getUserPath();
String ud = getDrive(up);
if (ud != null) return ud + path;
return up + path; /* User dir is a UNC path */
}
if (pl == 2) { /* Directory-relative */
String up = getUserPath();
String ud = getDrive(up);
if ((ud != null) && path.startsWith(ud))
return up + slashify(path.substring(2));
char drive = path.charAt(0);
String dir = getDriveDirectory(drive);
...
return drive + ":" + slashify(path.substring(2)); /* fake it */
}
throw new InternalError("Unresolvable path: " + path);
}
private String slashify(String p) {
if ((p.length() > 0) && (p.charAt(0) != slash)) return slash + p;
else return p;
}
-
getUserPath
方法返回System.getProperty("user.dir")
, 也就是当前的工作目录. -
getDrive
返回路径的盘符部分
那么
-
"e:\\"
构造的File
对象的getAbsolutePath
方法返回"e:\\"
. -
"e:"
构造的File
对象的getAbsolutePath
方法返回该应用程序在e盘下的工作目录
那么, e盘下的工作目录是什么? 相信大家都在cmd里执行过这几条命令:
C:\Windows\System32>cd e:\win8
C:\Windows\System32>e:
E:\win8>c:
C:\Windows\System32>
这里, E:\\win8
就是E盘的工作路径. C:\\Windows\\System32
就是C盘的工作路径. 而目录相对路径就是相对于这些目录.
比如:
C:drivers 就是
C:\\Windows\\System32\\drivers
E:eclipse 就是E:\\win8\\eclipse
但是由于我们没有切换到F盘过, 工作路径也不是F盘, 所以
F:就是
F:\\
F:test 就是F:\\test
所以, File("E:")
表示的到底是哪个路径, 跟工作环境有关:
- 如果当前工作路径不在e盘, 也从来没有移到e盘过, 那么
File("E:")
和File("E:\\")
表示同一个目录 - 如果当前工作路径在e盘, 那么
File("E:")
表示当前路径 - 如果曾经切换到e盘过, 那么
File("E:")
表示最后一次在e盘时的路径.F:\test>java Test
path:[e:\] absolutePath:[e:\]
8
输出e:\中的文件(夹)
path:[e:\] absolutePath:[e:\]
8
输出e:\中的文件(夹)
F:\test>cd e:\win8
F:\test>e:
E:\win8>f:
F:\test>java Test
path:[e:] absolutePath:[e:\win8]
20
输出e:\win8中的文件(夹)
path:[e:\] absolutePath:[e:\]
8
输出e:\中的文件(夹)
Java在Windows下的几种路径表示法
从`WinNTFileSystem.prefixLength`方法中的注释中我们可以看到Windows中的五种路径表示方法 :-
“Absolute local pathname”(绝对路径)
e:\\win8
C:\\Windows\\System32
-
“Completely relative”(完全相对路径)
-
win8
当前工作目录下的win8
文件(夹) -
System32
当前工作目录下的System32
文件(夹) -
Windows\\System32
当前工作目录下的Windows\\System32
文件(夹)
-
-
“Directory-relative”(目录相对路径)
- 就是 盘符 + “:” + 相对路径.
-
e:
e盘的工作目录 -
c:drivers
c盘工作目录下的drivers
文件(夹) - 注意, 在java中使用
Properties
类的setProperty("user.dir", dir)
方法切换当前工作目录的影响有点特殊. 当然如果我们把上面看过的方法及其调用的方法都完整的看完, 那么是可以解释下面的现象的:
System.setProperty("user.dir", "c:\\Windows");
System.out.println(System.getProperty("user.dir"));
digui(new File("c:")); // 输出为c:\\Windows, 也就是当前工作目录
String path = System.getProperty("user.dir"); // 保存当前工作路径
System.setProperty("user.dir", "c:\\Windows");
System.out.println(System.getProperty("user.dir"));
System.setProperty("user.dir", path); // 回复工作路径
System.out.println(System.getProperty("user.dir"));
digui(new File("c:")); // 输出路径为c:\\Windows\\System32, 也就是运行java程序之前的c盘符的工作目录
-
“Drive-relative”(盘符相对路径 或者 驱动器相对路径)
-
\\win8
当前工作盘符下的win8
文件(夹)
-
-
“Absolute UNC pathname”(绝对UNC路径)
-
\\\\c:\\Windows
c:\\Windows
- 详细信息可以自己搜索UNC
-
注: linux下只有两种路径表示方法: 相对路径和绝对路径.
写于 2015/04/29