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

给定两条绝对路径,例如

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

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


这有点迂回,但为什么不使用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 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

在撰写本文时(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:WindowsBootFontschs_boot.ttf";
    String base = "C:WindowsSpeechCommonsapisvr.exe";

    String relPath = ResourceUtils.getRelativePath(target, base, "");
    assertEquals("....BootFontschs_boot.ttf", relPath);
}

public void testGetRelativePathDirectoryToFile() {
    String target = "C:WindowsBootFontschs_boot.ttf";
    String base = "C:WindowsSpeechCommon";

    String relPath = ResourceUtils.getRelativePath(target, base, "");
    assertEquals("....BootFontschs_boot.ttf", relPath);
}

public void testGetRelativePathFileToDirectory() {
    String target = "C:WindowsBootFonts";
    String base = "C:WindowsSpeechCommonfoo.txt";

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

public void testGetRelativePathDirectoryToDirectory() {
    String target = "C:WindowsBoot";
    String base = "C:WindowsSpeechCommon";
    String expected = "....Boot";

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

public void testGetRelativePathDifferentDriveLetters() {
    String target = "D:sourcesrecoveryRecEnv.exe";
    String base = "C:JavaworkspaceAcceptanceTestsStandard test datageo";

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

    } catch (PathResolutionException ex) {
        // expected exception
    }
}
链接地址: http://www.djcxy.com/p/19997.html

上一篇: How to construct a relative path in Java from two absolute paths (or URLs)?

下一篇: Open file in a relative location in Python