当前位置:  开发笔记 > 编程语言 > 正文

如何从两个绝对路径(或URL)构建Java中的相对路径?

如何解决《如何从两个绝对路径(或URL)构建Java中的相对路径?》经验,为你挑选了11个好方法。

给出两条绝对路径,例如

/var/data/stuff/xyz.dat
/var/data

如何创建使用第二条路径作为基础的相对路径?在上面的例子中,结果应该是:./stuff/xyz.dat



1> Adam Crume..:

这是一个小回旋,但为什么不使用URI?它有一个相对化的方法,为您做所有必要的检查.

String path = "/var/data/stuff/xyz.dat";
String base = "/var/data";
String relative = new File(base).toURI().relativize(new File(path).toURI()).getPath();
// relative == "stuff/xyz.dat"

请注意,对于文件路径,java.nio.file.Path#relativize自Java 1.7开始,正如@Jirka Meluzin在另一个答案中指出的那样.


请参阅Peter Mueller的回答.除了最简单的情况之外,relativize()看起来很糟糕.
是的,它仅在基本路径是第一个路径的父路径时才有效.如果你需要一些像"../../relativepath"这样的分层向后,它将无法工作.我找到了一个解决方案:http://mrpmorris.blogspot.com/2007/05/convert-absolute-path-to-relative-path.html
正如@VitaliiFedorenko所写的:使用`java.nio.file.Path#relativize(Path)`,它只适用于父双点和全部.

2> Vitalii Fedo..:

从Java 7开始,您可以使用relativize方法:

import java.nio.file.Path;
import java.nio.file.Paths;

public class Test {

     public static void main(String[] args) {
        Path pathAbsolute = Paths.get("/var/data/stuff/xyz.dat");
        Path pathBase = Paths.get("/var/data");
        Path pathRelative = pathBase.relativize(pathAbsolute);
        System.out.println(pathRelative);
    }

}

输出:

stuff/xyz.dat


好,短,没有额外的lib +1.Adam Crume的解决方案(点击1)没有通过我的测试和下一个答案(hit2)"唯一的'工作'解决方案"添加了一个新的jar并且代码比我的实现更多,我在此后发现...比从未好过.- )

3> Dónal..:

在撰写本文时(2010年6月),这是通过我的测试用例的唯一解决方案.我不能保证这个解决方案没有错误,但它确实通过了包含的测试用例.我编写的方法和测试依赖于Apache commons IO的FilenameUtils类.

该解决方案使用Java 1.4进行测试.如果您使用的是Java 1.5(或更高版本),你应该考虑更换StringBufferStringBuilder(如果你还在使用Java 1.4,你应该考虑雇主的变化,而不是).

import java.io.File;
import java.util.regex.Pattern;

import org.apache.commons.io.FilenameUtils;

public class ResourceUtils {

    /**
     * Get the relative path from one file to another, specifying the directory separator. 
     * If one of the provided resources does not exist, it is assumed to be a file unless it ends with '/' or
     * '\'.
     * 
     * @param targetPath targetPath is calculated to this file
     * @param basePath basePath is calculated from this file
     * @param pathSeparator directory separator. The platform default is not assumed so that we can test Unix behaviour when running on Windows (for example)
     * @return
     */
    public static String getRelativePath(String targetPath, String basePath, String pathSeparator) {

        // Normalize the paths
        String normalizedTargetPath = FilenameUtils.normalizeNoEndSeparator(targetPath);
        String normalizedBasePath = FilenameUtils.normalizeNoEndSeparator(basePath);

        // Undo the changes to the separators made by normalization
        if (pathSeparator.equals("/")) {
            normalizedTargetPath = FilenameUtils.separatorsToUnix(normalizedTargetPath);
            normalizedBasePath = FilenameUtils.separatorsToUnix(normalizedBasePath);

        } else if (pathSeparator.equals("\\")) {
            normalizedTargetPath = FilenameUtils.separatorsToWindows(normalizedTargetPath);
            normalizedBasePath = FilenameUtils.separatorsToWindows(normalizedBasePath);

        } else {
            throw new IllegalArgumentException("Unrecognised dir separator '" + pathSeparator + "'");
        }

        String[] base = normalizedBasePath.split(Pattern.quote(pathSeparator));
        String[] target = normalizedTargetPath.split(Pattern.quote(pathSeparator));

        // First get all the common elements. Store them as a string,
        // and also count how many of them there are.
        StringBuffer common = new StringBuffer();

        int commonIndex = 0;
        while (commonIndex < target.length && commonIndex < base.length
                && target[commonIndex].equals(base[commonIndex])) {
            common.append(target[commonIndex] + pathSeparator);
            commonIndex++;
        }

        if (commonIndex == 0) {
            // No single common path element. This most
            // likely indicates differing drive letters, like C: and D:.
            // These paths cannot be relativized.
            throw new PathResolutionException("No common path element found for '" + normalizedTargetPath + "' and '" + normalizedBasePath
                    + "'");
        }   

        // The number of directories we have to backtrack depends on whether the base is a file or a dir
        // For example, the relative path from
        //
        // /foo/bar/baz/gg/ff to /foo/bar/baz
        // 
        // ".." if ff is a file
        // "../.." if ff is a directory
        //
        // The following is a heuristic to figure out if the base refers to a file or dir. It's not perfect, because
        // the resource referred to by this path may not actually exist, but it's the best I can do
        boolean baseIsFile = true;

        File baseResource = new File(normalizedBasePath);

        if (baseResource.exists()) {
            baseIsFile = baseResource.isFile();

        } else if (basePath.endsWith(pathSeparator)) {
            baseIsFile = false;
        }

        StringBuffer relative = new StringBuffer();

        if (base.length != commonIndex) {
            int numDirsUp = baseIsFile ? base.length - commonIndex - 1 : base.length - commonIndex;

            for (int i = 0; i < numDirsUp; i++) {
                relative.append(".." + pathSeparator);
            }
        }
        relative.append(normalizedTargetPath.substring(common.length()));
        return relative.toString();
    }


    static class PathResolutionException extends RuntimeException {
        PathResolutionException(String msg) {
            super(msg);
        }
    }    
}

这个测试用例是

public void testGetRelativePathsUnix() {
    assertEquals("stuff/xyz.dat", ResourceUtils.getRelativePath("/var/data/stuff/xyz.dat", "/var/data/", "/"));
    assertEquals("../../b/c", ResourceUtils.getRelativePath("/a/b/c", "/a/x/y/", "/"));
    assertEquals("../../b/c", ResourceUtils.getRelativePath("/m/n/o/a/b/c", "/m/n/o/a/x/y/", "/"));
}

public void testGetRelativePathFileToFile() {
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\sapisvr.exe";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDirectoryToFile() {
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathFileToDirectory() {
    String target = "C:\\Windows\\Boot\\Fonts";
    String base = "C:\\Windows\\Speech\\Common\\foo.txt";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts", relPath);
}

public void testGetRelativePathDirectoryToDirectory() {
    String target = "C:\\Windows\\Boot\\";
    String base = "C:\\Windows\\Speech\\Common\\";
    String expected = "..\\..\\Boot";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals(expected, relPath);
}

public void testGetRelativePathDifferentDriveLetters() {
    String target = "D:\\sources\\recovery\\RecEnv.exe";
    String base = "C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";

    try {
        ResourceUtils.getRelativePath(target, base, "\\");
        fail();

    } catch (PathResolutionException ex) {
        // expected exception
    }
}


太好了!但有一件事,如果基数和目标是相同的,它就会中断 - 字符串common在分隔符中结束,规范化的目标路径没有,所以子字符串调用要求一个太多的数字.我想通过在函数的最后两行之前添加以下内容来修复它:if(common.length()> = normalizedTargetPath.length()){return"."; }
说这是唯一有效的解决方案.其他答案工作得更好(当基数和目标相同时,这个答案会崩溃),更简单,不依赖于commons-io.

4> Christian K...:

使用java.net.URI.relativize时你应该知道Java bug: JDK-6226081(URI应该能够使具有部分根的路径相对化)

目前,只有当一个是另一个的前缀时,该relativize()方法URI才会重新激活URI.

这基本上意味着java.net.URI.relativize不会为你创造"..".


讨厌.有一个解决方法,显然:http://stackoverflow.com/questions/204784/how-to-construct-a-relative-path-in-java-from-two-absolute-paths-or-urls/1290311 #1290311

5> skaffman..:

在@Peter穆勒的回答中提到的错误被解决URIUtils在Apache的HttpComponents

public static URI resolve(URI baseURI,
                          String reference)

解析针对基URI的URI引用.解决java.net.URI()中的错误



6> rmuller..:

Java 8中,您可以轻松完成(相比之下URI,它是无bug的):

Path#relativize(Path)



7> Keeg..:

如果您知道第二个字符串是第一个字符串的一部分:

String s1 = "/var/data/stuff/xyz.dat";
String s2 = "/var/data";
String s3 = s1.substring(s2.length());

或者如果你真的想要在你的例子中开头的句号:

String s3 = ".".concat(s1.substring(s2.length()));


String s3 ="." + s1.substring(s2.length()); IMO略显可读

8> 小智..:

递归产生较小的解决方案.如果结果不可能(例如,不同的Windows磁盘)或不切实际(root只是公共目录),则抛出异常.

/**
 * Computes the path for a file relative to a given base, or fails if the only shared 
 * directory is the root and the absolute form is better.
 * 
 * @param base File that is the base for the result
 * @param name File to be "relativized"
 * @return the relative name
 * @throws IOException if files have no common sub-directories, i.e. at best share the
 *                     root prefix "/" or "C:\"
 */

public static String getRelativePath(File base, File name) throws IOException  {
    File parent = base.getParentFile();

    if (parent == null) {
        throw new IOException("No common directory");
    }

    String bpath = base.getCanonicalPath();
    String fpath = name.getCanonicalPath();

    if (fpath.startsWith(bpath)) {
        return fpath.substring(bpath.length() + 1);
    } else {
        return (".." + File.separator + getRelativePath(parent, name));
    }
}



9> Jirka Meluzi..:

这是其他免费图书馆的解决方案:

Path sourceFile = Paths.get("some/common/path/example/a/b/c/f1.txt");
Path targetFile = Paths.get("some/common/path/example/d/e/f2.txt"); 
Path relativePath = sourceFile.relativize(targetFile);
System.out.println(relativePath);

输出

..\..\..\..\d\e\f2.txt

[编辑]实际上它输出更多.. \因为源文件不是目录.我的案例的正确解决方案是:

Path sourceFile = Paths.get(new File("some/common/path/example/a/b/c/f1.txt").parent());
Path targetFile = Paths.get("some/common/path/example/d/e/f2.txt"); 
Path relativePath = sourceFile.relativize(targetFile);
System.out.println(relativePath);



10> Gili..:

我的版本基于马特和史蒂夫的版本:

/**
 * Returns the path of one File relative to another.
 *
 * @param target the target directory
 * @param base the base directory
 * @return target's path relative to the base directory
 * @throws IOException if an error occurs while resolving the files' canonical names
 */
 public static File getRelativeFile(File target, File base) throws IOException
 {
   String[] baseComponents = base.getCanonicalPath().split(Pattern.quote(File.separator));
   String[] targetComponents = target.getCanonicalPath().split(Pattern.quote(File.separator));

   // skip common components
   int index = 0;
   for (; index < targetComponents.length && index < baseComponents.length; ++index)
   {
     if (!targetComponents[index].equals(baseComponents[index]))
       break;
   }

   StringBuilder result = new StringBuilder();
   if (index != baseComponents.length)
   {
     // backtrack to base directory
     for (int i = index; i < baseComponents.length; ++i)
       result.append(".." + File.separator);
   }
   for (; index < targetComponents.length; ++index)
     result.append(targetComponents[index] + File.separator);
   if (!target.getPath().endsWith("/") && !target.getPath().endsWith("\\"))
   {
     // remove final path separator
     result.delete(result.length() - File.separator.length(), result.length());
   }
   return new File(result.toString());
 }


+1对我有用.只有小修正:而不是`"/".length()`你应该使用separator.length

11> Matuszek..:

Matt B的解决方案获取错误回溯的目录数 - 它应该是基本路径的长度减去公共路径元素的数量减去1(对于最后一个路径元素,可以是文件名,也可以是""生成的尾随split) .它发生一起工作/a/b/c//a/x/y/,但替换的参数/m/n/o/a/b/c//m/n/o/a/x/y/,你会看到这个问题.

此外,它需要else break在第一个for循环内部,否则它将错误处理碰巧具有匹配目录名称的路径,例如/a/b/c/d//x/y/c/z- c两个数组中的相同插槽中,但不是实际匹配.

所有这些解决方案都缺乏处理无法相互关联的路径的能力,因为它们具有不兼容的根,例如C:\foo\barD:\baz\quux.可能只是Windows上的一个问题,但值得注意.

我花的时间比我想的要长得多,但没关系.我实际上需要这个用于工作,所以感谢所有已经插入的人,我相信这个版本也会有更正!

public static String getRelativePath(String targetPath, String basePath, 
        String pathSeparator) {

    //  We need the -1 argument to split to make sure we get a trailing 
    //  "" token if the base ends in the path separator and is therefore
    //  a directory. We require directory paths to end in the path
    //  separator -- otherwise they are indistinguishable from files.
    String[] base = basePath.split(Pattern.quote(pathSeparator), -1);
    String[] target = targetPath.split(Pattern.quote(pathSeparator), 0);

    //  First get all the common elements. Store them as a string,
    //  and also count how many of them there are. 
    String common = "";
    int commonIndex = 0;
    for (int i = 0; i < target.length && i < base.length; i++) {
        if (target[i].equals(base[i])) {
            common += target[i] + pathSeparator;
            commonIndex++;
        }
        else break;
    }

    if (commonIndex == 0)
    {
        //  Whoops -- not even a single common path element. This most
        //  likely indicates differing drive letters, like C: and D:. 
        //  These paths cannot be relativized. Return the target path.
        return targetPath;
        //  This should never happen when all absolute paths
        //  begin with / as in *nix. 
    }

    String relative = "";
    if (base.length == commonIndex) {
        //  Comment this out if you prefer that a relative path not start with ./
        //relative = "." + pathSeparator;
    }
    else {
        int numDirsUp = base.length - commonIndex - 1;
        //  The number of directories we have to backtrack is the length of 
        //  the base path MINUS the number of common path elements, minus
        //  one because the last element in the path isn't a directory.
        for (int i = 1; i <= (numDirsUp); i++) {
            relative += ".." + pathSeparator;
        }
    }
    relative += targetPath.substring(common.length());

    return relative;
}

这里有几个案例的测试:

public void testGetRelativePathsUnixy() 
{        
    assertEquals("stuff/xyz.dat", FileUtils.getRelativePath(
            "/var/data/stuff/xyz.dat", "/var/data/", "/"));
    assertEquals("../../b/c", FileUtils.getRelativePath(
            "/a/b/c", "/a/x/y/", "/"));
    assertEquals("../../b/c", FileUtils.getRelativePath(
            "/m/n/o/a/b/c", "/m/n/o/a/x/y/", "/"));
}

public void testGetRelativePathFileToFile() 
{
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\sapisvr.exe";

    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDirectoryToFile() 
{
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common";

    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDifferentDriveLetters() 
{
    String target = "D:\\sources\\recovery\\RecEnv.exe";
    String base   = "C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";

    //  Should just return the target path because of the incompatible roots.
    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals(target, relPath);
}

推荐阅读
爱唱歌的郭少文_
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有