Làm cách nào để xây dựng một đường dẫn tương đối trong Java từ hai đường dẫn tuyệt đối (hoặc URL)?


275

Cho hai đường dẫn tuyệt đối, vd

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

Làm thế nào người ta có thể tạo một đường dẫn tương đối sử dụng đường dẫn thứ hai làm cơ sở của nó? Trong ví dụ trên, kết quả sẽ là:./stuff/xyz.dat


3
Đối với Java 7 trở lên, hãy xem câu trả lời của @ VitaliiFedorenko.
Andy Thomas

1
tl; dr answer: Paths.get (startPath) .relativize (Paths.get (endPath)). toString () (mà, bằng cách này, dường như hoạt động tốt với ví dụ "../" đối với tôi trong Java 8 , vì vậy ...)
Andrew

Câu trả lời:


297

Đó là một bùng binh nhỏ, nhưng tại sao không sử dụng URI? Nó có một phương pháp tương đối hóa mà thực hiện tất cả các kiểm tra cần thiết cho bạn.

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"

Xin lưu ý rằng đối với đường dẫn tệp có java.nio.file.Path#relativizetừ Java 1.7, như được chỉ ra bởi @Jirka Meluzin trong câu trả lời khác .


17
Xem câu trả lời của Peter Mueller. Relativize () xuất hiện khá bị hỏng đối với tất cả nhưng trường hợp đơn giản nhất.
Dave Ray

11
Đúng, nó chỉ hoạt động nếu đường dẫn cơ sở là cha mẹ của đường dẫn đầu tiên. Nếu bạn cần một số thứ bậc lạc hậu như "../../relativepath", nó sẽ không hoạt động. Tôi đã tìm thấy một giải pháp: mrpmorris.blogspot.com/2007/05/ từ
Aurelien Ribon

4
Như @VitaliiFedorenko đã viết: sử dụng java.nio.file.Path#relativize(Path), nó chỉ hoạt động với dấu chấm kép cha mẹ và tất cả.
Campa

Cân nhắc sử dụng toPath()thay vì toURI(). Nó hoàn toàn có thể tạo ra những thứ như "..\..". Nhưng hãy lưu ý java.lang.IllegalArgumentException: 'other' has different rootngoại lệ khi yêu cầu đường dẫn tương đối từ "C:\temp"đến "D:\temp".
Igor

Điều này không hoạt động như mong đợi, nó trả về dữ liệu / Stuff / xyz.dat trong trường hợp thử nghiệm của tôi.
biết

238

Vì Java 7, bạn có thể sử dụng phương thức tương đối hóa :

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

}

Đầu ra:

stuff/xyz.dat

3
Đẹp, ngắn, không có lib thêm +1. Giải pháp của Adam Crume (hit 1) không vượt qua các thử nghiệm của tôi và câu trả lời tiếp theo (hit2) "Giải pháp 'làm việc duy nhất" thêm một bình mới VÀ có nhiều mã hơn so với triển khai của tôi, sau đó tôi thấy điều này tốt hơn ... )
hokr

1
Nhưng xem ra vấn đề này .
ben3000

1
Đã kiểm tra rằng điều này xử lý thêm ..khi cần thiết (nó làm).
Owen

Thật không may, Android không bao gồm java.nio.file:(
Nathan Osman

1
Tôi thấy bạn nhận được kết quả lạ nếu "pathBase" không được "bình thường hóa" trước khi "tương đối hóa". Mặc dù tốt trong ví dụ này, tôi sẽ làm pathBase.normalize().relativize(pathAbsolute);như một quy tắc chung.
pstanton

77

Tại thời điểm viết (tháng 6 năm 2010), đây là giải pháp duy nhất vượt qua các trường hợp thử nghiệm của tôi. Tôi không thể đảm bảo rằng giải pháp này không có lỗi, nhưng nó vượt qua các trường hợp thử nghiệm đi kèm. Phương thức và các bài kiểm tra mà tôi đã viết phụ thuộc vào FilenameUtilslớp từ Apache commons IO .

Giải pháp đã được thử nghiệm với Java 1.4. Nếu bạn đang sử dụng Java 1.5 (hoặc cao hơn), bạn nên xem xét thay thế StringBufferbằng StringBuilder(nếu bạn vẫn đang sử dụng Java 1.4, bạn nên xem xét thay đổi chủ nhân thay thế).

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

Các trường hợp thử nghiệm mà điều này vượt qua là

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

5
Đẹp! Tuy nhiên, một điều sẽ bị phá vỡ nếu cơ sở và mục tiêu giống nhau - chuỗi chung được tạo để kết thúc bằng dấu phân cách, mà đường dẫn đích được chuẩn hóa không có, vì vậy lệnh gọi chuỗi con yêu cầu quá nhiều chữ số. Hãy nghĩ rằng tôi đã sửa nó bằng cách thêm dòng sau vào hai dòng cuối cùng của hàm: if (common.length ()> = normalizedTargetPath.length ()) {return "."; }
Erhannis

4
Nói rằng đây là giải pháp làm việc duy nhất là sai lệch. Các câu trả lời khác hoạt động tốt hơn (câu trả lời này gặp sự cố khi cơ sở và mục tiêu giống nhau), đơn giản hơn và không dựa vào commons-io.
NateS

26

Khi sử dụng java.net.URI.relativize, bạn nên biết về lỗi Java: JDK-6226081 (URI sẽ có thể tương đối hóa các đường dẫn với một phần gốc)

Hiện tại, relativize()phương thức URIsẽ chỉ tương đối hóa các URI khi một là tiền tố của cái kia.

Điều đó có nghĩa là java.net.URI.relativizesẽ không tạo ra ".." cho bạn.


6
Bẩn thỉu. Có một cách giải quyết cho vấn đề này, rõ ràng là: stackoverflow.com/questions/204784/NH
skaffman

Paths.get (startPath) .relativize (Paths.get (endPath)). ToString‌ () dường như chỉ hoạt động tốt với ví dụ "../" đối với tôi trong Java 8.
Andrew

@skaffman bạn có chắc không? Câu trả lời này tham khảo lỗi JDK-6226081, nhưng URIUtils.resolve()đề cập đến JDK-4708535. Và từ mã nguồn, tôi không thấy bất cứ điều gì liên quan đến quay lui (tức là ..các phân đoạn). Bạn đã nhầm lẫn hai lỗi?
Garret Wilson

JDK-6920138 được đánh dấu là bản sao của JDK-4708535.
Christian K.

17

Lỗi được đề cập trong một câu trả lời khác được giải quyết bởi URIUtils trong Apache httpComponents

public static URI resolve(URI baseURI,
                          String reference)

Giải quyết một tham chiếu URI dựa trên URI cơ sở. Xử lý lỗi trong java.net.URI ()


Không phải phương thức giải quyết tạo ra một URI tuyệt đối từ một cơ sở và một đường dẫn tương đối sao? Phương pháp này sẽ giúp như thế nào?
Đuổi theo

17

Trong Java 7 trở lên, bạn có thể chỉ cần sử dụng (và ngược lại URI, nó không có lỗi):

Path#relativize(Path)

10

Nếu bạn biết chuỗi thứ hai là một phần của chuỗi thứ nhất:

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

hoặc nếu bạn thực sự muốn khoảng thời gian đầu như trong ví dụ của bạn:

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

3
Chuỗi s3 = "." + s1.sub chuỗi (s2.length ()); IMO dễ đọc hơn một chút
Dónal

10

Đệ quy tạo ra một giải pháp nhỏ hơn. Điều này đưa ra một ngoại lệ nếu kết quả là không thể (ví dụ: đĩa Windows khác nhau) hoặc không thực tế (root chỉ là thư mục chung.)

/**
 * 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 có thể nặng, vì vậy giải pháp này không thể được khuyến nghị khi bạn cần xử lý hàng trăm nghìn hồ sơ. Ví dụ: tôi có một số tệp liệt kê có tới hàng triệu bản ghi và bây giờ tôi muốn di chuyển chúng để sử dụng đường dẫn tương đối cho tính di động.
dùng2305886

8

Đây là một giải pháp thư viện khác miễn phí:

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

Đầu ra

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

[EDIT] thực sự nó xuất ra nhiều hơn .. \ vì nguồn là tệp không phải là thư mục. Giải pháp đúng cho trường hợp của tôi là:

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

6

Phiên bản của tôi dựa trên các phiên bản của MattSteve một cách lỏng lẻo :

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

2
+1 hoạt động với tôi. Chỉ sửa chữa nhỏ: thay vì "/".length()bạn nên sử dụng separator.length
leonbloy

5

Giải pháp của Matt B khiến số lượng thư mục quay lại sai - nó phải là độ dài của đường dẫn cơ sở trừ đi số lượng phần tử đường dẫn phổ biến, trừ đi một phần tử (đối với phần tử đường dẫn cuối cùng, là tên tệp hoặc dấu vết ""được tạo bởi split) . Nó xảy ra để làm việc với /a/b/c//a/x/y/, nhưng thay thế các đối số với /m/n/o/a/b/c//m/n/o/a/x/y/và bạn sẽ thấy vấn đề.

Ngoài ra, nó cần một else breakvòng lặp for đầu tiên, hoặc nó sẽ xử lý sai các đường dẫn có tên thư mục phù hợp, chẳng hạn như /a/b/c/d//x/y/c/z- cnằm trong cùng một vị trí trong cả hai mảng, nhưng không phải là một kết quả khớp thực tế.

Tất cả các giải pháp này thiếu khả năng xử lý các đường dẫn không thể tương đối hóa với nhau vì chúng có gốc không tương thích, chẳng hạn như C:\foo\barD:\baz\quux. Có lẽ chỉ là một vấn đề trên Windows, nhưng đáng chú ý.

Tôi đã dành nhiều thời gian cho việc này hơn tôi dự định, nhưng không sao. Tôi thực sự cần điều này cho công việc, vì vậy cảm ơn tất cả những người đã theo đuổi và tôi chắc chắn cũng sẽ có sửa chữa cho phiên bản này!

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

Và đây là các thử nghiệm để bao gồm một số trường hợp:

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

4

Trên thực tế, câu trả lời khác của tôi không hoạt động nếu đường dẫn đích không phải là con của đường cơ sở.

Điều này nên làm việc.

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

}

2
Thay vì File.pathSeparator nên là File.separator. pathSeparator chỉ nên sử dụng cho split (regex), vì đối với "////" regex (regex đường dẫn regex), đường dẫn kết quả sẽ không chính xác.
Alex Ivasyuv

3

Mát mẻ!! Tôi cần một chút mã như thế này nhưng để so sánh các đường dẫn thư mục trên các máy Linux. Tôi thấy rằng điều này không hoạt động trong các tình huống mà thư mục mẹ là mục tiêu.

Đây là một thư mục phiên bản thân thiện của phương thức:

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

2

Tôi giả sử bạn có fromPath (một đường dẫn tuyệt đối cho một thư mục) và toPath (một đường dẫn tuyệt đối cho một thư mục / tệp) và bạn đang tìm một đường dẫn đại diện cho tệp / thư mục trong toPath như một đường dẫn tương đối từ fromPath (thư mục làm việc hiện tại của bạn là fromPath ) sau đó một cái gì đó như thế này sẽ hoạt động:

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

1

Có rất nhiều câu trả lời ở đây, nhưng tôi thấy họ không xử lý tất cả các trường hợp, chẳng hạn như cơ sở và mục tiêu là như nhau. Hàm này lấy một thư mục cơ sở và một đường dẫn đích và trả về đường dẫn tương đối. Nếu không có đường dẫn tương đối tồn tại, đường dẫn đích được trả về. File.separator là không cần thiết.

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

Ở đây một phương thức giải quyết một đường dẫn tương đối từ một đường dẫn cơ sở bất kể chúng ở cùng hoặc trong một gốc khác:

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

0

Vượt qua các thử nghiệm của Dónal, thay đổi duy nhất - nếu không có gốc chung, nó sẽ trả về đường dẫn đích (nó có thể đã tương đối)

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

Nếu bạn đang viết plugin Maven, bạn có thể sử dụng Plexus 'PathTool :

import org.codehaus.plexus.util.PathTool;

String relativeFilePath = PathTool.getRelativeFilePath(file1, file2);

0

Nếu Đường dẫn không khả dụng cho thời gian chạy JRE 1.5 hoặc plugin 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();
    }

}


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

-2

Mã Psuedo:

  1. Tách các chuỗi theo đường dẫn tách biệt ("/")
  2. Tìm đường dẫn chung lớn nhất bằng cách lặp lại thông qua kết quả của chuỗi phân tách (để bạn kết thúc bằng "/ var / data" hoặc "/ a" trong hai ví dụ của bạn)
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);

2
Câu trả lời này là một hack tốt nhất. Còn cửa sổ thì sao?
Qix - MONICA ĐƯỢC PHÂN PHỐI
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.