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

前端 未结 22 2266
小蘑菇
小蘑菇 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 10:55

    The bug referred to in another answer is addressed by URIUtils in Apache HttpComponents

    public static URI resolve(URI baseURI,
                              String reference)
    

    Resolves a URI reference against a base URI. Work-around for bug in java.net.URI ()

    0 讨论(0)
  • 2020-11-22 10:56

    If you're writing a Maven plugin, you can use Plexus' PathTool:

    import org.codehaus.plexus.util.PathTool;
    
    String relativeFilePath = PathTool.getRelativeFilePath(file1, file2);
    
    0 讨论(0)
  • 2020-11-22 10:57

    Actually my other answer didn't work if the target path wasn't a child of the base path.

    This should work.

    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/", "/"));
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 10:57

    Cool!! I need a bit of code like this but for comparing directory paths on Linux machines. I found that this wasn't working in situations where a parent directory was the target.

    Here is a directory friendly version of the method:

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

    Since Java 7 you can use the relativize method:

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

    Output:

    stuff/xyz.dat
    
    0 讨论(0)
  • 2020-11-22 10:58
    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();
    }
    
    0 讨论(0)
提交回复
热议问题