Wie erstelle ich einen relativen Pfad in Java aus zwei absoluten Pfaden (oder URLs)?

275

Bei zwei absoluten Pfaden, z

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

Wie kann man einen relativen Pfad erstellen, der den zweiten Pfad als Basis verwendet? Im obigen Beispiel sollte das Ergebnis sein:./stuff/xyz.dat

VoidPointer
quelle
3
Informationen zu Java 7 und höher finden Sie in der Antwort von @ VitaliiFedorenko.
Andy Thomas
1
tl; dr Antwort: Paths.get (startPath) .relativize (Paths.get (endPath)). toString () (was übrigens gut mit zB "../" für mich in Java 8 zu funktionieren scheint , also ...)
Andrew

Antworten:

297

Es ist ein kleiner Kreisverkehr, aber warum nicht URI verwenden? Es verfügt über eine Relativierungsmethode, die alle erforderlichen Überprüfungen für Sie durchführt.

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"

Bitte beachten Sie, dass es für den Dateipfad java.nio.file.Path#relativizeJava 1.7 gibt, wie von @Jirka Meluzin in der anderen Antwort hervorgehoben .

Adam Crume
quelle
17
Siehe Peter Muellers Antwort. relativize () erscheint für alle außer den einfachsten Fällen ziemlich kaputt.
Dave Ray
11
Ja, es funktioniert nur, wenn der Basispfad ein übergeordnetes Element des ersten Pfads ist. Wenn Sie eine hierarchische Rückwärtsrichtung wie "../../relativepath" benötigen, funktioniert dies nicht. Ich habe eine Lösung gefunden: mrpmorris.blogspot.com/2007/05/…
Aurelien Ribon
4
Wie @VitaliiFedorenko schrieb: Verwenden Sie java.nio.file.Path#relativize(Path), es funktioniert nur mit übergeordneten Doppelpunkten und allem.
Campa
Erwägen Sie die Verwendung toPath()anstelle von toURI(). Es ist perfekt in der Lage, Dinge wie zu erstellen "..\..". Beachten Sie jedoch die java.lang.IllegalArgumentException: 'other' has different rootAusnahme, wenn Sie nach dem relativen Pfad von "C:\temp"nach fragen "D:\temp".
Igor
Dies funktioniert nicht wie erwartet, sondern gibt in meinem Testfall data / stuff / xyz.dat zurück.
Unbekannter
238

Seit Java 7 können Sie die Relativierungsmethode verwenden:

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

}

Ausgabe:

stuff/xyz.dat
Vitalii Fedorenko
quelle
3
Schön, kurz, keine zusätzliche Bibliothek +1. Die Lösung von Adam Crume (Treffer 1) besteht meine Tests nicht und die nächste Antwort (Treffer 2) "Die einzige" funktionierende "Lösung" fügt ein neues Glas hinzu UND ist mehr Code als meine Implementierung. Ich finde dies hier später ... besser als nie. )
hokr
1
Aber achten Sie auf dieses Problem .
Ben3000
1
Überprüft, ob dies das Hinzufügen ..bei Bedarf übernimmt (dies ist der Fall).
Owen
Leider enthält Android nicht java.nio.file:(
Nathan Osman
1
Ich habe festgestellt, dass Sie seltsame Ergebnisse erhalten, wenn die "pathBase" vor "relativieren" nicht "normalisiert" wird. Obwohl in diesem Beispiel in Ordnung, würde ich pathBase.normalize().relativize(pathAbsolute);in der Regel tun .
Pstanton
77

Zum Zeitpunkt des Schreibens (Juni 2010) war dies die einzige Lösung, die meine Testfälle bestanden hat. Ich kann nicht garantieren, dass diese Lösung fehlerfrei ist, aber sie besteht die enthaltenen Testfälle. Die Methode und die Tests, die ich geschrieben habe, hängen von der FilenameUtilsKlasse von Apache Commons IO ab .

Die Lösung wurde mit Java 1.4 getestet. Wenn Sie mit Java 1.5 (oder höher) sollten Sie erwägen , zu ersetzen StringBuffermit StringBuilder(wenn Sie noch verwenden Java 1.4 sollten Sie einen Wechsel des Arbeitgebers statt betrachten).

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

Die Testfälle, die dies besteht, sind

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
    }
}
Dónal
quelle
5
Nett! Eine Sache bricht jedoch ab, wenn Basis und Ziel identisch sind - die gemeinsame Zeichenfolge endet in einem Trennzeichen, das der normalisierte Zielpfad nicht hat, sodass der Teilstring-Aufruf nach einer zu vielen Stelle fragt. Ich glaube, ich habe es behoben, indem ich vor den letzten beiden Zeilen der Funktion Folgendes hinzugefügt habe: if (common.length ()> = normalizedTargetPath.length ()) {return "."; }
Erhannis
4
Zu sagen, dass dies die einzige funktionierende Lösung ist, ist irreführend. Andere Antworten funktionieren besser (diese Antwort stürzt ab, wenn Basis und Ziel identisch sind), sind einfacher und basieren nicht auf Commons-Io.
NateS
26

Wenn Sie java.net.URI.relativize verwenden, sollten Sie den Java-Fehler beachten: JDK-6226081 (URI sollte in der Lage sein, Pfade mit Teilwurzeln zu relativieren)

Im Moment relativiert die relativize()Methode des URIWillens URIs nur, wenn einer ein Präfix des anderen ist.

Was im Wesentlichen bedeutet, java.net.URI.relativizedass keine ".." für Sie erstellt werden.

Christian K.
quelle
6
Böse. Es gibt eine Problemumgehung
skaffman
Paths.get (startPath) .relativize (Paths.get (endPath)). ToString‌ () scheint gut mit z. B. "../" für mich in Java 8 zu funktionieren.
Andrew
@skaffman bist du sicher? Diese Antwort verweist auf den Fehler JDK-6226081, URIUtils.resolve()erwähnt jedoch JDK-4708535. Und aus dem Quellcode sehe ich nichts im Zusammenhang mit Backtracking (dh ..Segmenten). Haben Sie die beiden Fehler verwechselt?
Garret Wilson
JDK-6920138 ist als Duplikat von JDK-4708535 markiert.
Christian K.
17

Der Fehler, auf den in einer anderen Antwort verwiesen wird, wird von URIUtils in Apache HttpComponents behoben

public static URI resolve(URI baseURI,
                          String reference)

Löst eine URI-Referenz gegen eine Basis-URI auf. Problemumgehung in java.net.URI ()

Skaffman
quelle
Generiert die Auflösungsmethode nicht einen absoluten URI aus einer Basis und einem relativen Pfad? Wie würde diese Methode helfen?
Chase
17

In Java 7 und höher können Sie einfach verwenden (und im Gegensatz dazu URIist es fehlerfrei):

Path#relativize(Path)
Müller
quelle
10

Wenn Sie wissen, dass die zweite Zeichenfolge Teil der ersten ist:

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

oder wenn Sie den Punkt am Anfang wirklich wie in Ihrem Beispiel wollen:

String s3 = ".".concat(s1.substring(s2.length()));
Keeg
quelle
3
String s3 = "." + s1.substring (s2.length ()); ist etwas besser lesbar IMO
Dónal
10

Rekursion ergibt eine kleinere Lösung. Dies löst eine Ausnahme aus, wenn das Ergebnis unmöglich (z. B. eine andere Windows-Festplatte) oder unpraktisch (root ist nur ein gemeinsames Verzeichnis) ist.

/**
 * 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));
    }
}
Burn L.
quelle
getCanonicalPath kann sehr schwer sein, daher kann diese Lösung nicht empfohlen werden, wenn Sie hunderttausend Datensätze verarbeiten müssen. Zum Beispiel habe ich einige Listendateien mit bis zu Millionen Datensätzen und möchte sie jetzt verschieben, um den relativen Pfad für die Portabilität zu verwenden.
user2305886
8

Hier ist eine Lösung andere Bibliothek kostenlos:

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

Ausgänge

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

[EDIT] gibt es tatsächlich auf mehr .. \ aus, da die Quelle Datei kein Verzeichnis ist. Die richtige Lösung für meinen Fall ist:

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);
Jirka Meluzin
quelle
6

Meine Version basiert lose auf den Versionen von Matt und Steve :

/**
 * 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());
 }
Gili
quelle
2
+1 funktioniert bei mir. Nur geringfügige Korrektur: Anstelle von "/".length()sollten Sie separator.length
leonbloy
5

Bei der Lösung von Matt B wird die Anzahl der Verzeichnisse falsch zurückverfolgt - es sollte die Länge des Basispfads minus der Anzahl der gemeinsamen Pfadelemente minus eins sein (für das letzte Pfadelement, das entweder ein Dateiname oder ein von ""generierter Trailing ist split) . Es funktioniert zufällig mit /a/b/c/und /a/x/y/, aber ersetzen Sie die Argumente durch /m/n/o/a/b/c/und /m/n/o/a/x/y/und Sie werden das Problem sehen.

Außerdem benötigt es eine else breakInside-the-First-for-Schleife, oder es werden Pfade falsch behandelt, die zufällig übereinstimmende Verzeichnisnamen haben, wie z. B. /a/b/c/d/und /x/y/c/z- das cbefindet sich in beiden Arrays im selben Slot, ist jedoch keine tatsächliche Übereinstimmung.

Allen diesen Lösungen fehlt die Fähigkeit, Pfade zu verarbeiten, die nicht miteinander relativiert werden können, da sie inkompatible Wurzeln wie C:\foo\barund haben D:\baz\quux. Wahrscheinlich nur ein Problem unter Windows, aber erwähnenswert.

Ich habe viel länger damit verbracht, als ich beabsichtigt hatte, aber das ist okay. Ich brauchte das eigentlich für die Arbeit, also danke an alle, die mitgemacht haben, und ich bin sicher, dass es auch Korrekturen an dieser Version geben wird!

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

Und hier sind Tests, um mehrere Fälle abzudecken:

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);
}
Matuszek
quelle
4

Eigentlich hat meine andere Antwort nicht funktioniert, wenn der Zielpfad kein Kind des Basispfads war.

Das sollte funktionieren.

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

}
matt b
quelle
2
Anstelle von File.pathSeparator sollte File.separator sein. pathSeparator sollte nur für split (Regex) verwendet werden, da für "////" Regex (Win Path Regex) der Ergebnispfad falsch ist.
Alex Ivasyuv
3

Cool!! Ich brauche ein bisschen Code wie diesen, aber um Verzeichnispfade auf Linux-Rechnern zu vergleichen. Ich stellte fest, dass dies in Situationen nicht funktionierte, in denen ein übergeordnetes Verzeichnis das Ziel war.

Hier ist eine verzeichnisfreundliche Version der Methode:

 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;
}
Rachel
quelle
2

Ich gehe davon aus, dass Sie fromPath (einen absoluten Pfad für einen Ordner) und toPath (einen absoluten Pfad für einen Ordner / eine Datei) haben und nach einem Pfad suchen, der die Datei / den Ordner in toPath als relativen Pfad darstellt von fromPath (Ihr aktuelles Arbeitsverzeichnis ist fromPath ) sollte dann so etwas funktionieren:

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

Hier gibt es bereits viele Antworten, aber ich habe festgestellt, dass sie nicht alle Fälle behandelt haben, z. B. dass Basis und Ziel gleich sind. Diese Funktion nimmt ein Basisverzeichnis und einen Zielpfad und gibt den relativen Pfad. Wenn kein relativer Pfad vorhanden ist, wird der Zielpfad zurückgegeben. File.separator ist nicht erforderlich.

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

Hier eine Methode, die einen relativen Pfad von einem Basispfad auflöst, unabhängig davon, ob sie sich in derselben oder in einer anderen Wurzel befinden:

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;
}
pedromateo
quelle
0

Besteht Dónals Tests, die einzige Änderung - wenn keine gemeinsame Wurzel vorhanden ist, wird der Zielpfad zurückgegeben (möglicherweise bereits relativ).

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);
    }
}
Mike
quelle
0

Wenn Sie ein Maven-Plugin schreiben, können Sie Plexus 'verwendenPathTool :

import org.codehaus.plexus.util.PathTool;

String relativeFilePath = PathTool.getRelativeFilePath(file1, file2);
Ben Hutchison
quelle
0

Wenn Paths für JRE 1.5 Runtime oder Maven Plugin nicht verfügbar ist

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

}
Alftank
quelle
-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();
}
terensu
quelle
-2

Pseudocode:

  1. Teilen Sie die Zeichenfolgen durch den Pfadtrenner ("/").
  2. Finden Sie den größten gemeinsamen Pfad, indem Sie das Ergebnis der geteilten Zeichenfolge durchlaufen (sodass Sie in Ihren beiden Beispielen "/ var / data" oder "/ a" erhalten).
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);
matt b
quelle
2
Diese Antwort ist bestenfalls ein Hack. Was ist mit Windows?
Qix - MONICA wurde am