生成规范路径

24

请问有没有Java库可以用来生成规范路径(基本上是删除反向引用)。

我需要的是能够执行以下操作的东西:

原始路径 -> 规范路径

/../foo/       -> /foo
/foo/          -> /foo
/../../../     -> /
/./foo/./      -> /foo
//foo//bar     -> /foo/bar
//foo/../bar   -> /bar

目前我懒惰地依赖于使用:

 new File("/", path).getCanonicalPath();

但是这会根据实际的文件系统解析路径,并保持同步。

   java.lang.Thread.State: BLOCKED (on object monitor)
        at java.io.ExpiringCache.get(ExpiringCache.java:55)
        - waiting to lock <0x93a0d180> (a java.io.ExpiringCache)
        at java.io.UnixFileSystem.canonicalize(UnixFileSystem.java:137)
        at java.io.File.getCanonicalPath(File.java:559)

我需要进行规范路径,但这些路径在我的文件系统上并不存在,因此我只需要该方法的逻辑即可,不需要任何同步。我希望能够使用一个经过充分测试的库,而不是编写自己的代码。


支持使用相对路径作为输入吗?还是这会导致错误? - Joachim Sauer
/foo/../bar/的输出应该是什么? - Joachim Sauer
@Joachim:我假设所有路径都是基于根目录的。在大多数情况下,我只是从URL路径中删除后向引用。 - Joel
@Joachim - 不,对于输入'/foo/../bar',getAbsPath的结果是'/foo/../bar'。 - Joel
7个回答

21

我认为你可以使用URI类来实现这个功能;例如,如果路径中不包含需要在URI路径组件中转义的字符,则可以这样做。

String normalized = new URI(path).normalize().getPath();
如果路径包含(或可能包含)需要转义的字符,多参数构造函数将对path参数进行转义,您可以为其他参数提供null
注:
1. 上述方法通过将其视为相对URI来归一化文件路径。如果要规范化整个URI(包括(可选的)方案、授权和其他组件),请勿调用getPath()! 2. URI规范化不涉及查看文件系统,就像文件规范化那样。但反过来,当路径中存在符号链接时,规范化的行为与规范化不同。

看起来不错。它仍然需要一点微调(以删除前导/ ../),但它让我大部分完成了,谢谢。 - Joel
1
@Joel:你为什么想要删除前导的“/../”?它们要么是错误的,你应该将它们视为错误条件,要么你指定所有路径都相对于某个点,并且你应该支持它们。但是默默地删除它们听起来不是一个好主意。 - Joachim Sauer
你可能是正确的,但我收到各种糟糕的数据,我只是在清理数据,并确保所有路径都以 / 为根路径。 - Joel

17

使用Apache Commons IO(一个众所周知且经过充分测试的库)

public static String normalize(String filename)

会完全满足你的需求。

例如:

String result = FilenameUtils.normalize(myFile.getAbsolutePath());

12
如果您不需要路径规范化,只需要标准化,在Java 7中可以使用java.nio.file.Path.normalize方法。根据http://docs.oracle.com/javase/7/docs/api/java/nio/file/Path.html

该方法不访问文件系统;路径可能无法定位到存在的文件。

如果您使用File对象,可以使用以下代码:
file.toPath().normalize().toFile()

4
您可以尝试使用以下算法:

您可以尝试使用以下算法:

String collapsePath(String path) {
    /* Split into directory parts */
    String[] directories = path.split("/");
    String[] newDirectories = new String[directories.length];
    int i, j = 0;

    for (i=0; i<directories.length; i++) {
        /* Ignore the previous directory if it is a double dot */
        if (directories[i].equals("..") && j > 0)
            newDirectories[j--] = "";
        /* Completely ignore single dots */
        else if (! directories[i].equals("."))
            newDirectories[j++] = directories[i];
    }

    /* Ah, what I would give for String.join() */
    String newPath = new String();
    for (i=0; i < j; i++)
        newPath = newPath + "/" + newDirectories[i];
    return newPath;
}

它并不完美;它在目录数量上是线性的,但确实会在内存中制作副本。


0

哪种路径被视为规范路径取决于操作系统。这就是为什么Java需要在文件系统上进行检查的原因。因此,在不了解操作系统的情况下,没有简单的逻辑来测试路径。


0

因此,尽管规范化可以解决问题,但是这里有一个程序,它比仅调用Paths.normalize()更多地暴露了Java API的一些内容。

假设我想要在文件系统中查找不在当前目录中的文件。 我的工作代码文件是

myproject/src/JavaCode.java

文件位于 myproject/src/。我的文件在:

../../data/myfile.txt

我正在测试从 JavaCode.java 运行我的代码。

public static void main(String[] args) { 
    findFile("../../data","myfile.txt");
    System.out.println("Found it.");
}
public static File findFile(String inputPath, String inputFile) {
    File dataDir = new File("").getAbsoluteFile(); // points dataDir to working directory
    String delimiters = "" + '\\' + '/';           // dealing with different system separators
    StringTokenizer st = new StringTokenizer(inputPath, delimiters);
    while(st.hasMoreTokens()) {
        String s = st.nextToken();
        if(s.trim().isEmpty() || s.equals(".")) 
            continue;
        else if(s.equals("..")) 
            dataDir = dataDir.getParentFile();
        else {
            dataDir = new File(dataDir, s);
            if(!dataDir.exists())
                throw new RuntimeException("Data folder does not exist.");
        }
    }
    return new File(dataDir, inputFile);    
}

在指定位置放置文件后,应该打印出“找到了它”。


-1

我假设你有字符串并且想要字符串,你现在有Java 7可用,并且你的默认文件系统使用'/'作为路径分隔符,所以尝试:

String output = FileSystems.getDefault().getPath(input).normalize().toString();

你可以尝试使用以下方法:

/**
 * Input           Output
 * /../foo/     -> /foo
 * /foo/        -> /foo
 * /../../../   -> /
 * /./foo/./    -> /foo
 * //foo//bar   -> /foo/bar
 * //foo/../bar -> /bar
 */
@Test
public void testNormalizedPath() throws URISyntaxException, IOException {
    String[] in = new String[]{"/../foo/", "/foo/", "/../../../", "/./foo/./",
            "//foo/bar", "//foo/../bar", "/", "/foo"};
    String[] ex = new String[]{"/foo", "/foo", "/", "/foo", "/foo/bar", "/bar", "/", "/foo"};
    FileSystem fs = FileSystems.getDefault();
    for (int i = 0; i < in.length; i++) {
        assertEquals(ex[i], fs.getPath(in[i]).normalize().toString());
    }
}

我有点想知道为什么留下关于Java的后续版本的最新答案会被投票否决。但我仍然认为这里对问题的回答没有问题。 - dlamblin
文件系统可安全地供多个并发线程使用。 - dlamblin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接