How to construct a relative path in Java from two absolute paths (or URLs)?
给定两条绝对路径,例如
1 2 | /var/data/stuff/xyz.dat /var/data |
如何创建以第二条路径为基础的相对路径?在上面的例子中,结果应该是:
有点迂回,但为什么不使用uri?它有一个相对化的方法,可以为您进行所有必要的检查。
1 2 3 4 |
请注意,对于文件路径,自从Java 1.7以来,EDOCX1是1的,如Jjka MeluZin指出的。
由于Java 7,可以使用相对化方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); } } |
输出:
1 | stuff/xyz.dat |
在编写时(2010年6月),这是唯一通过测试用例的解决方案。我不能保证这个解决方案是无缺陷的,但是它通过了包含的测试用例。我编写的方法和测试依赖于ApacheCommonsIO中的
用Java 1.4测试了该解决方案。如果你正在使用Java 1.5(或更高),你应该考虑用EDCOX1×17来替换EDCOX1×16(如果你仍然使用Java 1.4,你应该考虑换一个雇主)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | 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); } } } |
通过的测试用例是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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\ ecovery\ ecEnv.exe"; String base ="C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\"; try { ResourceUtils.getRelativePath(target, base,"\"); fail(); } catch (PathResolutionException ex) { // expected exception } } |
当使用Java.NET.URI时,你应该意识到Java错误:JDK-6226081(URI应该能够将路径与部分根相对化)
At the moment, the
relativize() method ofURI will only relativize URIs when one is a prefix of the other.
这基本上意味着
另一个答案中提到的bug由Apachehttpcomponents中的uriutils解决。
1 2 |
Resolves a URI reference against a
base URI. Work-around for bug in
java.net.URI ()
在Java 8中,您可以做简单的操作(与EDCOX1,0)相反,它是无bug的:
1 | Path#relativize(Path) |
如果知道第二个字符串是第一个字符串的一部分:
1 2 3 |
或者,如果您真的像在示例中那样希望在开始时使用句点:
1 |
递归产生一个较小的解决方案。如果结果不可能(例如不同的Windows磁盘)或不可行(根目录只是公用目录),则会引发异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /** * 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)); } } |
以下是其他免费图书馆的解决方案:
1 2 3 4 | 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); |
输出
1 | ..\..\..\..\d\e\f2.txt |
[编辑]实际上它输出更多..因为源文件不是目录。正确的解决方案是:
1 2 3 4 |
我的版本大致基于Matt和Steve的版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /** * 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(对于最后一个路径元素,它是由
另外,它在第一个for循环中需要一个
所有这些解决方案都缺乏处理不能相互关联的路径的能力,因为它们具有不兼容的根,如
我在这件事上花的时间比我预想的要长,但没关系。我真的需要这个来工作,所以感谢所有参与进来的人,我相信这个版本也会有修正!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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; } |
下面是几个案例的测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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\ ecovery\ ecEnv.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); } |
实际上,如果目标路径不是基路径的子路径,我的另一个答案就不起作用。
这应该有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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); } } |
1 2 3 4 5 6 7 8 9 10 | 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/","/")); } } |
酷!!我需要一些这样的代码,但用于比较Linux机器上的目录路径。我发现在以父目录为目标的情况下,这不起作用。
以下是方法的目录友好版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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; } |
我假设您有from path(文件夹的绝对路径)和topath(文件夹/文件的绝对路径),并且您正在查找一个路径,该路径将topath中的文件/文件夹表示为from path(当前工作目录为from path)的相对路径,那么这样的操作应该可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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(); } |
这里已经有很多答案了,但是我发现它们并不能处理所有的情况,比如基础和目标是一样的。此函数获取基目录和目标路径,并返回相对路径。如果不存在相对路径,则返回目标路径。不需要file.separator。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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(); } |
如果路径不适用于JRE 1.5运行时或Maven插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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(); } } |
这里有一个方法,它从基路径解析相对路径,而不管它们在同一根目录中还是在不同的根目录中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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; } |
如果您正在编写Maven插件,可以使用Plexus的
1 2 3 | import org.codehaus.plexus.util.PathTool; String relativeFilePath = PathTool.getRelativeFilePath(file1, file2); |
通过了d_nal的测试,这是唯一的更改-如果没有公共根,它将返回目标路径(它可能已经是相对的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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); } } |
org.apache.ant有一个带有getrelativePath方法的fileutils类。我自己还没试过,但值得一试。
http://javADO.HeFelelg.I/Org.Apache .ANT/1.7.1/ORG/APACHE/TooS/ANT/UTIL/FILUTILLS.HTML*GETRealValpEPATH(JavaIO.Form,Java.IO文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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(); } |
伪代码: