给出两条绝对路径,例如
/var/data/stuff/xyz.dat
/var/data
如何创建一个以第二条路径为基础的相对路径?在上面的例子中,结果应该是:./stuff/xyz.dat
给出两条绝对路径,例如
/var/data/stuff/xyz.dat
/var/data
如何创建一个以第二条路径为基础的相对路径?在上面的例子中,结果应该是:./stuff/xyz.dat
当前回答
Matt B的解决方案错误地获得了回溯目录的数量——它应该是基本路径的长度减去公共路径元素的数量,减去1(对于最后一个路径元素,它是一个文件名或由split生成的尾随“”)。它恰好适用于/a/b/c/和/a/x/y/,但是将参数替换为/m/n/o/a/b/c/和/m/n/o/a/x/y/,你就会发现问题。
此外,它还需要在第一个for循环中进行else中断,否则它将错误地处理碰巧具有匹配目录名的路径,例如/a/b/c/d/和/x/y/c/z——c在两个数组中的同一个槽中,但不是真正的匹配。
所有这些解决方案都缺乏处理不能相互相对化的路径的能力,因为它们具有不兼容的根,例如C:\foo\bar和D:\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 ()
Matt B的解决方案错误地获得了回溯目录的数量——它应该是基本路径的长度减去公共路径元素的数量,减去1(对于最后一个路径元素,它是一个文件名或由split生成的尾随“”)。它恰好适用于/a/b/c/和/a/x/y/,但是将参数替换为/m/n/o/a/b/c/和/m/n/o/a/x/y/,你就会发现问题。
此外,它还需要在第一个for循环中进行else中断,否则它将错误地处理碰巧具有匹配目录名的路径,例如/a/b/c/d/和/x/y/c/z——c在两个数组中的同一个槽中,但不是真正的匹配。
所有这些解决方案都缺乏处理不能相互相对化的路径的能力,因为它们具有不兼容的根,例如C:\foo\bar和D:\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);
}
如果路径在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();
}
}
下面是一个从基本路径解析相对路径的方法,不管它们在相同或不同的根路径中:
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;
}
实际上,如果目标路径不是基路径的子路径,我的另一个答案是不成立的。
这应该有用。
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/", "/"));
}
}