두 개의 절대 경로 (또는 URL)에서 Java로 상대 경로를 구성하는 방법은 무엇입니까?


275

예를 들어 두 개의 절대 경로

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

두 번째 경로를 기본으로 사용하는 상대 경로를 어떻게 만들 수 있습니까? 위의 예에서 결과는 다음과 같아야합니다../stuff/xyz.dat


3
Java 7 이상은 @VitaliiFedorenko의 답변을 참조하십시오.
Andy Thomas

1
tl; dr 답변 : Paths.get (startPath) .relativize (Paths.get (endPath)). toString () (그런데 Java 8에서 나를 위해 "../"와 잘 작동하는 것 같습니다. , 그래서 ...)
Andrew

답변:


297

약간 우회하지만 URI를 사용하지 않는 이유는 무엇입니까? 여기에는 필요한 모든 검사를 수행하는 상대화 방법이 있습니다.

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.nio.file.Path#relativize가리키는 아웃로, 자바 1.7 이후 @Jirka Meluzin다른 대답 .


17
Peter Mueller의 답변을 참조하십시오. relativize ()는 가장 간단한 경우를 제외하고는 상당히 깨진 것처럼 보입니다.
Dave Ray

11
그러나 기본 경로가 첫 번째 경로의 부모 인 경우에만 작동합니다. "../../relativepath"와 같은 일부 계층 적 역방향이 필요한 경우 작동하지 않습니다. 해결책을 찾았습니다 : mrpmorris.blogspot.com/2007/05/…
Aurelien Ribon

4
@VitaliiFedorenko가 쓴 것처럼 : use java.nio.file.Path#relativize(Path), 그것은 부모 더블 도트와 함께 작동합니다.
Campa

toPath()대신 사용 을 고려하십시오 toURI(). 와 같은 것을 완벽하게 만들 수 있습니다 "..\..". 그러나 인식 java.lang.IllegalArgumentException: 'other' has different root에서 상대 경로를 요청하는 경우 예외 "C:\temp""D:\temp".
Igor

예상대로 작동하지 않으며 테스트 사례에서 data / stuff / xyz.dat를 반환합니다.
unbekant

238

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

3
좋고 짧고 여분의 lib +1이 없습니다. Adam Crume의 솔루션 (hit 1)이 내 테스트를 통과하지 못하고 다음 답변 (hit2) "The Only 'Working'Solution"에 새로운 항아리가 추가되고 구현보다 더 많은 코드가 있습니다. 나중에 이것을 여기에서 찾을 수 있습니다. )
hokr


1
..필요한 곳에서 추가를 처리 하는지 확인했습니다 (그렇습니다 ).
Owen

불행히도, 안드로이드는 포함하지 않습니다 java.nio.file:(
Nathan Osman

1
"pathla"가 "relativize"이전에 "normalized"가 아닌 경우 이상한 결과가 나타납니다. 이 예제에서는 괜찮지 만 pathBase.normalize().relativize(pathAbsolute);일반적인 규칙으로 사용합니다.
pstanton

77

글을 쓰는 시점 (2010 년 6 월)은 이것이 테스트 사례를 통과 한 유일한 솔루션이었습니다. 이 솔루션에 버그가 없음을 보장 할 수는 없지만 포함 된 테스트 사례를 통과합니다. 필자가 작성한 방법과 테스트 FilenameUtilsApache commons IO 의 클래스에 따라 다릅니다 .

솔루션은 Java 1.4로 테스트되었습니다. 당신은 당신이 교체를 고려해야합니다 (더 이상) 자바 1.5를 사용하는 경우 StringBufferStringBuilder(당신은 여전히 자바 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
    }
}

5
좋은! 그러나 기본과 대상이 동일하면 중단됩니다. 일반 문자열은 구분 기호로 끝나기 때문에 정규화 된 대상 경로에는없는 문자가 있으므로 하위 문자열 호출은 너무 많은 숫자를 요구합니다. 함수의 마지막 두 줄 앞에 다음을 추가하여 수정했다고 생각하십시오. if (common.length ()> = normalizedTargetPath.length ()) {return "."; }
Erhannis

4
이것이 유일한 해결책이라고 오해의 소지가 있습니다. 다른 답변이 더 잘 작동합니다 (이 답변은 기본과 대상이 동일 할 때 충돌합니다). 더 간단하고 commons-io에 의존하지 않습니다.
NateS

26

java.net.URI.relativize를 사용할 때 Java 버그를 알고 있어야합니다. JDK-6226081 (URI는 부분 루트가있는 경로를 상대화 할 수 있어야합니다)

현재의 relativize()방법은 URI하나가 다른 것의 접두사 일 때만 URI를 상대화합니다.

본질적으로 java.net.URI.relativize".."를 만들지 않습니다.


6
추잡한. 이것에 대한 해결 방법이 있습니다 : stackoverflow.com/questions/204784/…
skaffman

Paths.get (startPath) .relativize (Paths.get (endPath)). toString‌ ()은 Java 8에서 "../"와 같이 제대로 작동하는 것 같습니다.
Andrew

@skaffman 확실합니까? 이 답변은 버그 JDK-6226081을 참조하지만 URIUtils.resolve()JDK-4708535는 언급합니다. 그리고 소스 코드에서 역 추적 (예 : ..세그먼트) 과 관련된 것은 보이지 않습니다 . 두 버그를 혼동 했습니까?
Garret Wilson

JDK-6920138은 JDK-4708535의 복제본으로 표시됩니다.
Christian K.

17

다른 답변 에서 언급 된 버그 는 Apache HttpComponents의 URIUtils 로 해결됩니다.

public static URI resolve(URI baseURI,
                          String reference)

기본 URI에 대한 URI 참조를 해결합니다. java.net.URI ()의 버그에 대한 해결 방법


resolve 메소드가 기본 및 상대 경로에서 절대 URI를 생성하지 않습니까? 이 방법이 어떻게 도움이됩니까?
Chase

17

Java 7 이상 에서는 간단히 사용할 수 있습니다 (와 달리 URI버그가 없음).

Path#relativize(Path)

10

두 번째 문자열이 첫 번째 문자열의 일부라는 것을 알고 있다면 :

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

또는 예제 에서처럼 처음에 마침표를 원한다면 :

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

3
문자열 s3 = "." + s1.substring (s2.length ()); 약간 더 읽기 쉬운 IMO
Dónal

10

재귀는 더 작은 솔루션을 생성합니다. 결과가 불가능하거나 (예 : 다른 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는 중량이 클 수 있으므로 수십만 개의 레코드를 처리해야 할 때는이 솔루션을 권장하지 않습니다. 예를 들어 최대 백만 개의 레코드가있는 리스팅 파일이 있는데 이제 이식성을 위해 상대 경로를 사용하도록 파일을 이동하려고합니다.
user2305886

8

다른 라이브러리가없는 솔루션은 다음과 같습니다.

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

[편집] 실제로 소스가 디렉토리가 아닌 파일이기 때문에 더 많은 .. \에서 출력됩니다. 내 경우에 대한 올바른 해결책은 다음과 같습니다.

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

내 버전은 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());
 }

2
+1이 나를 위해 일합니다. 사소한 수정 만 : 대신 "/".length()separator.length
leonbloy

5

Matt B의 솔루션은 잘못 추적 할 수있는 디렉토리 수를 얻습니다. 기본 경로의 길이에서 공통 경로 요소의 수를 뺀 값에서 1을 뺀 값입니다 (마지막 경로 요소의 경우 파일 이름 또는에 ""의해 생성 된 후행 split) . 이 작업을하려면 발생 /a/b/c/하고 /a/x/y/있지만, 함께 인수를 대체 /m/n/o/a/b/c/하고 /m/n/o/a/x/y/당신이 문제를 볼 수 있습니다.

또한, 그것은 필요 else break루프 최초의 내부를하거나 같은 일치하는 디렉토리 이름,이 일이 경로를 잘못 처리됩니다 /a/b/c/d//x/y/c/z- (가) c두 배열에서 동일한 슬롯에 있지만 실제 일치하지 않습니다.

이러한 모든 솔루션에는 C:\foo\barand와 같은 호환되지 않는 루트가 있기 때문에 서로 상대성이 될 수없는 경로를 처리 할 수있는 기능이 없습니다 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);
}

4

실제로 대상 경로가 기본 경로의 자식이 아닌 경우 다른 대답이 작동하지 않았습니다.

이 작동합니다.

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
File.pathSeparator 대신 File.separator 여야합니다. pathSeparator는 "////"정규식 (win path 정규식)과 같이 split (regex)에만 사용해야합니다. 결과 경로가 올바르지 않습니다.
Alex Ivasyuv

3

멋있는!! 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;
}

2

나는 당신이 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();
}

1

이미 많은 답변이 있지만 기본 및 대상이 같은 모든 경우를 처리하지는 못했습니다. 이 함수는 기본 디렉토리 와 대상 경로를 가져와 상대 경로를 반환합니다. 상대 경로가 없으면 대상 경로가 반환됩니다. 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();
}

0

다음은 동일한 루트 또는 다른 루트에있는 기본 경로에서 상대 경로를 확인하는 방법입니다.

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

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

0

Maven 플러그인을 작성하는 경우 Plexus '를PathTool 사용할 수 있습니다 .

import org.codehaus.plexus.util.PathTool;

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

0

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

}


-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

슈도 코드 :

  1. 경로 구분 기호 ( "/")로 문자열 분리
  2. 분할 문자열의 결과를 반복하여 가장 큰 공통 경로를 찾으십시오 (따라서 두 예제에서 "/ var / data"또는 "/ a"로 끝남)
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);

2
이 답변은 기껏해야 해킹입니다. 창문은 어때?
Qix-MONICA가 MISTREATED
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.