【问题标题】:How to construct a relative path in Java from two absolute paths (or URLs)?如何从两个绝对路径(或 URL)构造 Java 中的相对路径?
【发布时间】:2008-10-15 13:53:42
【问题描述】:

给定两个绝对路径,例如

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

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

【问题讨论】:

  • 对于 Java 7 及更高版本,请参阅@VitaliiFedorenko 的回答。
  • tl;dr answer: Paths.get(startPath).relativize(Paths.get(endPath)).toString() (顺便说一句,这似乎是在 Java 8 中对我来说工作得很好,例如“../”,所以...)

标签: java url file path


【解决方案1】:

有点迂回,但为什么不使用URI呢?它有一个 relativize 方法,可以为你做所有必要的检查。

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 Meluzinthe other answer 中指出的那样。

【讨论】:

  • 见彼得穆勒的回答。除了最简单的情况外,relativize() 似乎很糟糕。
  • 是的,它仅在基本路径是第一个路径的父路径时才有效。如果你需要像“../../relativepath”这样的层次结构,它就行不通了。我找到了解决方案:mrpmorris.blogspot.com/2007/05/…
  • 正如@VitaliiFedorenko 所写:使用java.nio.file.Path#relativize(Path),它只适用于父双点等等。
  • 考虑使用toPath() 而不是toURI()。它完全能够创建像"..\.." 这样的东西。但在询问从"C:\temp""D:\temp" 的相对路径时,请注意java.lang.IllegalArgumentException: 'other' has different root 异常。
  • 这不能按预期工作,它在我的测试用例中返回 data/stuff/xyz.dat。
【解决方案2】:

从 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

【讨论】:

  • 不错,简短,没有额外的库 +1。 Adam Crume 的解决方案(命中 1)没有通过我的测试和下一个答案(命中 2)“唯一的'工作'解决方案”添加了一个新的 jar,并且代码比我的实现更多,我后来在这里发现......总比没有好.- )
  • 但请注意this problem
  • 检查此句柄是否在必要时添加..(确实如此)。
  • 很遗憾,Android 不包含java.nio.file :(
  • 我发现如果“pathBase”在“relativize”之前没有“标准化”,你会得到奇怪的结果。虽然在这个例子中很好,但我会做pathBase.normalize().relativize(pathAbsolute); 作为一般规则。
【解决方案3】:

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

该解决方案已使用 Java 1.4 进行了测试。如果您使用的是 Java 1.5(或更高版本),则应考虑将 StringBuffer 替换为 StringBuilder(如果您仍在使用 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】:

使用 java.net.URI.relativize 时,您应该注意 Java 错误: JDK-6226081 (URI should be able to relativize paths with partial roots)

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

这实质上意味着java.net.URI.relativize 不会为您创建“..”。

【讨论】:

  • 讨厌。显然有一个解决方法:stackoverflow.com/questions/204784/…
  • Paths.get(startPath).relativize(Paths.get(endPath)).toString‌​() 似乎与例如工作得很好在 Java 8 中对我来说是“../”。
  • @skaffman 你确定吗?这个答案引用了错误 JDK-6226081,但 URIUtils.resolve() 提到了 JDK-4708535。从源代码中,我看不到任何与回溯相关的内容(即.. 段)。你混淆了这两个错误吗?
  • JDK-6920138 被标记为 JDK-4708535 的副本。
【解决方案5】:

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

Path#relativize(Path)

【讨论】:

    【解决方案6】:

    another answer 中提到的错误由URIUtilsApache HttpComponents 中解决

    public static URI resolve(URI baseURI,
                              String reference)
    

    解析一个 URI 引用 基础 URI。错误的解决方法 java.net.URI()

    【讨论】:

    • resolve 方法不是从基础路径和相对路径生成绝对 URI 吗?这种方法有什么帮助?
    【解决方案7】:

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

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

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

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

    【讨论】:

    • 字符串 s3 = "." + s1.substring(s2.length()); IMO 的可读性略高
    【解决方案8】:

    递归产生更小的解决方案。如果结果是不可能的(例如不同的 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));
        }
    }
    

    【讨论】:

    • getCanonicalPath 可能是重量级的,所以当您需要处理十万条记录时,不推荐使用此解决方案。例如,我有一些列表文件有多达百万条记录,现在我想移动它们以使用相对路径以实现可移植性。
    【解决方案9】:

    这是其他库免费的解决方案:

    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
    

    [EDIT] 实际上它输出更多 ..\ 因为源是文件而不是目录。我的情况的正确解决方案是:

    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】:

      我的版本大致基于MattSteve 的版本:

      /**
       * 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】:

      Matt B 的解决方案使回溯的目录数量错误——它应该是基本路径的长度减去公共路径元素的数量,再减去一(对于最后一个路径元素,它可以是文件名或尾随""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);
      }
      

      【讨论】:

        【解决方案12】:

        如果目标路径不是基本路径的子路径,实际上我的其他答案不起作用。

        这应该可行。

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

        【讨论】:

        • 而不是 File.pathSeparator 应该是 File.separator。 pathSeparator 应该只用于 split(正则表达式),至于“////”正则表达式(win path regex),结果路径会不正确。
        【解决方案13】:

        酷!我需要一些这样的代码,但用于比较 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;
        }
        

        【讨论】:

          【解决方案14】:

          我假设您有 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();
          }
          

          【讨论】:

            【解决方案15】:

            这里已经有很多答案,但我发现它们并没有处理所有情况,例如基础和目标相同。此函数接受一个基本 目录 和一个目标路径并返回相对路径。如果不存在相对路径,则返回目标路径。 File.separator 是不必要的。

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

            【讨论】:

              【解决方案16】:

              这里有一种方法可以从基本路径解析相对路径,无论它们是在相同的还是不同的根目录中:

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

              【讨论】:

                【解决方案17】:

                通过了 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);
                    }
                }
                

                【讨论】:

                  【解决方案18】:

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

                  import org.codehaus.plexus.util.PathTool;
                  
                  String relativeFilePath = PathTool.getRelativeFilePath(file1, file2);
                  

                  【讨论】:

                    【解决方案19】:

                    如果路径不适用于 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();
                        }
                    
                    }
                    

                    【讨论】:

                      【解决方案20】:

                      我知道这有点晚了,但是我创建了一个适用于任何 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));
                          }
                      

                      【讨论】:

                        【解决方案21】:

                        org.apache.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)

                        【讨论】:

                          【解决方案22】:
                          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();
                          }
                          

                          【讨论】:

                            【解决方案23】:

                            伪代码:

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

                            【讨论】:

                            • 这个答案充其量只是一个黑客。窗户呢?
                            猜你喜欢
                            • 2012-11-23
                            • 1970-01-01
                            • 2011-08-11
                            • 2013-04-17
                            • 1970-01-01
                            • 1970-01-01
                            • 2015-02-09
                            • 1970-01-01
                            相关资源
                            最近更新 更多