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

前端 未结 22 2265
小蘑菇
小蘑菇 2020-11-22 10:30

Given two absolute paths, e.g.

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

How can one create a relative path that uses the second path as its base?

相关标签:
22条回答
  • 2020-11-22 11:02

    My version is loosely based on Matt and Steve's versions:

    /**
     * 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());
     }
    
    0 讨论(0)
  • 2020-11-22 11:02

    Lots of answers already here, but I found they didn't handle all cases, such as the base and target being the same. This function takes a base directory and a target path and returns the relative path. If no relative path exists, the target path is returned. File.separator is unnecessary.

    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();
    }
    
    0 讨论(0)
  • 2020-11-22 11:03

    Here is a solution other library free:

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

    Outputs

    ..\..\..\..\d\e\f2.txt
    

    [EDIT] actually it outputs on more ..\ because of the source is file not a directory. Correct solution for my case is:

    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);
    
    0 讨论(0)
  • 2020-11-22 11:05

    When using java.net.URI.relativize you should be aware of Java bug: JDK-6226081 (URI should be able to relativize paths with partial roots)

    At the moment, the relativize() method of URI will only relativize URIs when one is a prefix of the other.

    Which essentially means java.net.URI.relativize will not create ".."'s for you.

    0 讨论(0)
  • 2020-11-22 11:05

    Passes Dónal's tests, the only change - if no common root it returns target path (it could be already relative)

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-22 11:07

    I'm assuming you have fromPath (an absolute path for a folder), and toPath (an absolute path for a folder/file), and your're looking for a path that with represent the file/folder in toPath as a relative path from fromPath (your current working directory is fromPath) then something like this should work:

    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();
    }
    
    0 讨论(0)
提交回复
热议问题