如何从两个绝对路径(或url)在Java中构造一个相对路径?

给出两条绝对路径,例如

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

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

242538 次浏览

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

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

或者,如果你真的想把句号放在开头,就像你的例子中那样:

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

Psuedo-code:

  1. 使用路径分隔符("/")拆分字符串
  2. 通过迭代拆分字符串的结果来找到最大的公共路径(因此在两个示例中,您最终会得到"/var/data"或"/a")
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);

我假设你有fromPath(一个文件夹的绝对路径),和toPath(一个文件夹/文件的绝对路径),你正在寻找一个路径,表示文件/文件夹在toPath作为一个相对路径从fromPath(你当前的工作目录是fromPath),然后像这样的工作:

public static String getRelativePath(String fromPath, String toPath) {


// This weirdness is because a separator of '/' messes with String.split()
String regexCharacter = File.separator;
if (File.separatorChar == '\\') {
regexCharacter = "\\\\";
}


String[] fromSplit = fromPath.split(regexCharacter);
String[] toSplit = toPath.split(regexCharacter);


// Find the common path
int common = 0;
while (fromSplit[common].equals(toSplit[common])) {
common++;
}


StringBuffer result = new StringBuffer(".");


// Work your way up the FROM path to common ground
for (int i = common; i < fromSplit.length; i++) {
result.append(File.separatorChar).append("..");
}


// Work your way down the TO path
for (int i = common; i < toSplit.length; i++) {
result.append(File.separatorChar).append(toSplit[i]);
}


return result.toString();
}

实际上,如果目标路径不是基路径的子路径,我的另一个答案是不成立的。

这应该有用。

public class RelativePathFinder {


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


// find common path
String[] target = targetPath.split(pathSeparator);
String[] base = basePath.split(pathSeparator);


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++;
}
}




String relative = "";
// is the target a child directory of the base directory?
// i.e., target = /a/b/c/d, base = /a/b/
if (commonIndex == base.length) {
relative = "." + pathSeparator + targetPath.substring(common.length());
}
else {
// determine how many directories we have to backtrack
for (int i = 1; i <= commonIndex; i++) {
relative += ".." + pathSeparator;
}
relative += targetPath.substring(common.length());
}


return relative;
}


public static String getRelativePath(String targetPath, String basePath) {
return getRelativePath(targetPath, basePath, File.pathSeparator);
}
}

public class RelativePathFinderTest extends TestCase {


public void testGetRelativePath() {
assertEquals("./stuff/xyz.dat", RelativePathFinder.getRelativePath(
"/var/data/stuff/xyz.dat", "/var/data/", "/"));
assertEquals("../../b/c", RelativePathFinder.getRelativePath("/a/b/c",
"/a/x/y/", "/"));
}


}

这有点迂回,但为什么不使用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 1.7开始有java.nio.file.Path#relativize,正如另一个答案中的@Jirka Meluzin所指出的那样。

当使用java.net.URI.relativize时,你应该注意Java错误: JDK-6226081 (URI应该能够将路径与部分根相对化) < / p >

目前,URIrelativize()方法只在一个uri是另一个uri的前缀时才会相对化uri。

这本质上意味着java.net.URI.relativize将不会创建“..这是给你的。

我的版本松散地基于马特史蒂夫的版本:

/**
* 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());
}

Matt B的解决方案得到了要回溯的目录数量错误——它应该是基本路径的长度减去公共路径元素的数量,减去1(对于最后一个路径元素,它要么是文件名,要么是由split生成的尾随"")。它恰好适用于/a/b/c//a/x/y/,但是将参数替换为/m/n/o/a/b/c//m/n/o/a/x/y/,你就会发现问题。

此外,它还需要在第一个for循环中使用else break,否则它会错误地处理恰巧具有匹配目录名的路径,例如/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);
}

另一个答案中提到的错误由Apache HttpComponents中的URIUtils解决

public static URI resolve(URI baseURI,
String reference)

解析一个URI引用 基本URI。解决bug的方法 java.net.URI () < / p >

酷! !我需要一些类似这样的代码,但用于比较Linux机器上的目录路径。我发现这在父目录为目标的情况下不起作用。

下面是该方法的目录友好版本:

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


boolean isDir = false;
{
File f = new File(targetPath);
isDir = f.isDirectory();
}
//  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 - (isDir?0:1); /* only subtract 1 if it  is a file. */
//  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;
}
}
//if we are comparing directories then we
if (targetPath.length() > common.length()) {
//it's OK, it isn't a directory
relative += targetPath.substring(common.length());
}


return relative;
}

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

该解决方案使用Java 1.4进行了测试。如果你正在使用Java 1.5(或更高版本),你应该考虑用StringBuilder取代StringBuffer(如果你仍然使用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
}
}

从Java 7开始,你可以使用身上的方法:

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

递归产生一个较小的解决方案。如果结果不可能(例如不同的Windows磁盘)或不切实际(根目录只是普通目录),则抛出异常。

/**
* 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));
}
}

这里已经有很多答案了,但我发现他们并不能处理所有的情况,比如基地和目标是相同的。此函数接受一个基目录和一个目标路径,并返回相对路径。如果不存在相对路径,则返回目标路径。文件。分隔符是不必要的。

public static String getRelativePath (String baseDir, String targetPath) {
String[] base = baseDir.replace('\\', '/').split("\\/");
targetPath = targetPath.replace('\\', '/');
String[] target = targetPath.split("\\/");


// Count common elements and their length.
int commonCount = 0, commonLength = 0, maxCount = Math.min(target.length, base.length);
while (commonCount < maxCount) {
String targetElement = target[commonCount];
if (!targetElement.equals(base[commonCount])) break;
commonCount++;
commonLength += targetElement.length() + 1; // Directory name length plus slash.
}
if (commonCount == 0) return targetPath; // No common path element.


int targetLength = targetPath.length();
int dirsUp = base.length - commonCount;
StringBuffer relative = new StringBuffer(dirsUp * 3 + targetLength - commonLength + 1);
for (int i = 0; i < dirsUp; i++)
relative.append("../");
if (commonLength < targetLength) relative.append(targetPath.substring(commonLength));
return relative.toString();
}

ant有一个带有getRelativePath方法的FileUtils类。我自己还没有尝试过,但是值得一试。

http://javadoc.haefelinger.it/org.apache.ant/1.7.1/org/apache/tools/ant/util/FileUtils.html getRelativePath (java.io.File java.io.File)

下面是一个从基本路径解析相对路径的方法,不管它们在相同或不同的根路径中:

public static String GetRelativePath(String path, String base){


final String SEP = "/";


// if base is not a directory -> return empty
if (!base.endsWith(SEP)){
return "";
}


// check if path is a file -> remove last "/" at the end of the method
boolean isfile = !path.endsWith(SEP);


// get URIs and split them by using the separator
String a = "";
String b = "";
try {
a = new File(base).getCanonicalFile().toURI().getPath();
b = new File(path).getCanonicalFile().toURI().getPath();
} catch (IOException e) {
e.printStackTrace();
}
String[] basePaths = a.split(SEP);
String[] otherPaths = b.split(SEP);


// check common part
int n = 0;
for(; n < basePaths.length && n < otherPaths.length; n ++)
{
if( basePaths[n].equals(otherPaths[n]) == false )
break;
}


// compose the new path
StringBuffer tmp = new StringBuffer("");
for(int m = n; m < basePaths.length; m ++)
tmp.append(".."+SEP);
for(int m = n; m < otherPaths.length; m ++)
{
tmp.append(otherPaths[m]);
tmp.append(SEP);
}


// get path string
String result = tmp.toString();


// remove last "/" if path is a file
if (isfile && result.endsWith(SEP)){
result = result.substring(0,result.length()-1);
}


return result;
}

这里有一个其他库免费的解决方案:

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);
private String relative(String left, String right){
String[] lefts = left.split("/");
String[] rights = right.split("/");
int min = Math.min(lefts.length, rights.length);
int commonIdx = -1;
for(int i = 0; i < min; i++){
if(commonIdx < 0 && !lefts[i].equals(rights[i])){
commonIdx = i - 1;
break;
}
}
if(commonIdx < 0){
return null;
}
StringBuilder sb = new StringBuilder(Math.max(left.length(), right.length()));
sb.append(left).append("/");
for(int i = commonIdx + 1; i < lefts.length;i++){
sb.append("../");
}
for(int i = commonIdx + 1; i < rights.length;i++){
sb.append(rights[i]).append("/");
}


return sb.deleteCharAt(sb.length() -1).toString();
}

Java 7及以后的版本中,你可以简单地使用(与URI相比,它没有错误):

Path#relativize(Path)

通过Dónal的测试,唯一的变化-如果没有公共根,它将返回目标路径(它可能已经是相对的)

import static java.util.Arrays.asList;
import static java.util.Collections.nCopies;
import static org.apache.commons.io.FilenameUtils.normalizeNoEndSeparator;
import static org.apache.commons.io.FilenameUtils.separatorsToUnix;
import static org.apache.commons.lang3.StringUtils.getCommonPrefix;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.apache.commons.lang3.StringUtils.join;


import java.io.File;
import java.util.ArrayList;
import java.util.List;


public class ResourceUtils {


public static String getRelativePath(String targetPath, String basePath, String pathSeparator) {
File baseFile = new File(basePath);
if (baseFile.isFile() || !baseFile.exists() && !basePath.endsWith("/") && !basePath.endsWith("\\"))
basePath = baseFile.getParent();


String target = separatorsToUnix(normalizeNoEndSeparator(targetPath));
String base = separatorsToUnix(normalizeNoEndSeparator(basePath));


String commonPrefix = getCommonPrefix(target, base);
if (isBlank(commonPrefix))
return targetPath.replaceAll("/", pathSeparator);


target = target.replaceFirst(commonPrefix, "");
base = base.replaceFirst(commonPrefix, "");


List<String> result = new ArrayList<>();
if (isNotEmpty(base))
result.addAll(nCopies(base.split("/").length, ".."));
result.addAll(asList(target.replaceFirst("^/", "").split("/")));


return join(result, pathSeparator);
}
}

如果你正在编写一个Maven插件,你可以使用丛”PathTool:

import org.codehaus.plexus.util.PathTool;


String relativeFilePath = PathTool.getRelativeFilePath(file1, file2);

如果路径在JRE 1.5运行时或maven插件中不可用

package org.afc.util;


import java.io.File;
import java.util.LinkedList;
import java.util.List;


public class FileUtil {


public static String getRelativePath(String basePath, String filePath)  {
return getRelativePath(new File(basePath), new File(filePath));
}


public static String getRelativePath(File base, File file)  {


List<String> bases = new LinkedList<String>();
bases.add(0, base.getName());
for (File parent = base.getParentFile(); parent != null; parent = parent.getParentFile()) {
bases.add(0, parent.getName());
}


List<String> files = new LinkedList<String>();
files.add(0, file.getName());
for (File parent = file.getParentFile(); parent != null; parent = parent.getParentFile()) {
files.add(0, parent.getName());
}


int overlapIndex = 0;
while (overlapIndex < bases.size() && overlapIndex < files.size() && bases.get(overlapIndex).equals(files.get(overlapIndex))) {
overlapIndex++;
}


StringBuilder relativePath = new StringBuilder();
for (int i = overlapIndex; i < bases.size(); i++) {
relativePath.append("..").append(File.separatorChar);
}


for (int i = overlapIndex; i < files.size(); i++) {
relativePath.append(files.get(i)).append(File.separatorChar);
}


relativePath.deleteCharAt(relativePath.length() - 1);
return relativePath.toString();
}


}

我知道这有点晚了,但是,我创建了一个解决方案,适用于任何java版本。

    public static String getRealtivePath(File root, File file)
{
String path = file.getPath();
String rootPath = root.getPath();
boolean plus1 = path.contains(File.separator);
return path.substring(path.indexOf(rootPath) + rootPath.length() + (plus1 ? 1 : 0));
}