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 =newFile(base).toURI().relativize(newFile(path).toURI()).getPath();// relative == "stuff/xyz.dat"
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.
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. )
Ü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;publicclassResourceUtils{/**
* 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
*/publicstaticString getRelativePath(String targetPath,String basePath,String pathSeparator){// Normalize the pathsString normalizedTargetPath =FilenameUtils.normalizeNoEndSeparator(targetPath);String normalizedBasePath =FilenameUtils.normalizeNoEndSeparator(basePath);// Undo the changes to the separators made by normalizationif(pathSeparator.equals("/")){
normalizedTargetPath =FilenameUtils.separatorsToUnix(normalizedTargetPath);
normalizedBasePath =FilenameUtils.separatorsToUnix(normalizedBasePath);}elseif(pathSeparator.equals("\\")){
normalizedTargetPath =FilenameUtils.separatorsToWindows(normalizedTargetPath);
normalizedBasePath =FilenameUtils.separatorsToWindows(normalizedBasePath);}else{thrownewIllegalArgumentException("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 =newStringBuffer();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.thrownewPathResolutionException("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 doboolean baseIsFile =true;File baseResource =newFile(normalizedBasePath);if(baseResource.exists()){
baseIsFile = baseResource.isFile();}elseif(basePath.endsWith(pathSeparator)){
baseIsFile =false;}StringBuffer relative =newStringBuffer();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();}staticclassPathResolutionExceptionextendsRuntimeException{PathResolutionException(String msg){super(msg);}}}
Die Testfälle, die dies besteht, sind
publicvoid 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/","/"));}publicvoid 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);}publicvoid 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);}publicvoid 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);}publicvoid testGetRelativePathDirectoryToDirectory(){String target ="C:\\Windows\\Boot\\";String base ="C:\\Windows\\Speech\\Common\\";String expected ="..\\..\\Boot";String relPath =ResourceUtils.getRelativePath(target, base,"\\");
assertEquals(expected, relPath);}publicvoid 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}}
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.
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.
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:\"
*/publicstaticString getRelativePath(File base,File name)throwsIOException{File parent = base.getParentFile();if(parent ==null){thrownewIOException("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 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.
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
*/publicstaticFile getRelativeFile(File target,File base)throwsIOException{String[] baseComponents = base.getCanonicalPath().split(Pattern.quote(File.separator));String[] targetComponents = target.getCanonicalPath().split(Pattern.quote(File.separator));// skip common componentsint index =0;for(; index < targetComponents.length && index < baseComponents.length;++index){if(!targetComponents[index].equals(baseComponents[index]))break;}StringBuilder result =newStringBuilder();if(index != baseComponents.length){// backtrack to base directoryfor(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());}returnnewFile(result.toString());}
+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!
publicstaticString 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++;}elsebreak;}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:
publicvoid 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/","/"));}publicvoid 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);}publicvoid 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);}publicvoid 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);}
Eigentlich hat meine andere Antwort nicht funktioniert, wenn der Zielpfad kein Kind des Basispfads war.
Das sollte funktionieren.
publicclassRelativePathFinder{publicstaticString getRelativePath(String targetPath,String basePath,String pathSeparator){// find common pathString[] 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 backtrackfor(int i =1; i <= commonIndex; i++){
relative +=".."+ pathSeparator;}
relative += targetPath.substring(common.length());}return relative;}publicstaticString getRelativePath(String targetPath,String basePath){return getRelativePath(targetPath, basePath,File.pathSeparator);}}
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:
publicstaticString getRelativePath(String targetPath,String basePath,String pathSeparator){boolean isDir =false;{File f =newFile(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++;}elsebreak;}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;}
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:
publicstaticString 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 pathint common =0;while(fromSplit[common].equals(toSplit[common])){
common++;}StringBuffer result =newStringBuffer(".");// Work your way up the FROM path to common groundfor(int i = common; i < fromSplit.length; i++){
result.append(File.separatorChar).append("..");}// Work your way down the TO pathfor(int i = common; i < toSplit.length; i++){
result.append(File.separatorChar).append(toSplit[i]);}return result.toString();}
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.
publicstaticString 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 =newStringBuffer(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();}
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:
publicstaticStringGetRelativePath(String path,String base){finalString SEP ="/";// if base is not a directory -> return emptyif(!base.endsWith(SEP)){return"";}// check if path is a file -> remove last "/" at the end of the methodboolean isfile =!path.endsWith(SEP);// get URIs and split them by using the separatorString a ="";String b ="";try{
a =newFile(base).getCanonicalFile().toURI().getPath();
b =newFile(path).getCanonicalFile().toURI().getPath();}catch(IOException e){
e.printStackTrace();}String[] basePaths = a.split(SEP);String[] otherPaths = b.split(SEP);// check common partint n =0;for(; n < basePaths.length && n < otherPaths.length; n ++){if( basePaths[n].equals(otherPaths[n])==false)break;}// compose the new pathStringBuffer tmp =newStringBuffer("");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 stringString result = tmp.toString();// remove last "/" if path is a fileif(isfile && result.endsWith(SEP)){
result = result.substring(0,result.length()-1);}return result;}
Besteht Dónals Tests, die einzige Änderung - wenn keine gemeinsame Wurzel vorhanden ist, wird der Zielpfad zurückgegeben (möglicherweise bereits relativ).
org.apache.ant verfügt über eine FileUtils-Klasse mit einer getRelativePath-Methode. Ich habe es noch nicht selbst ausprobiert, könnte sich aber lohnen, es auszuprobieren.
Teilen Sie die Zeichenfolgen durch den Pfadtrenner ("/").
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).
Antworten:
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.
Bitte beachten Sie, dass es für den Dateipfad
java.nio.file.Path#relativize
Java 1.7 gibt, wie von @Jirka Meluzin in der anderen Antwort hervorgehoben .quelle
java.nio.file.Path#relativize(Path)
, es funktioniert nur mit übergeordneten Doppelpunkten und allem.toPath()
anstelle vontoURI()
. Es ist perfekt in der Lage, Dinge wie zu erstellen"..\.."
. Beachten Sie jedoch diejava.lang.IllegalArgumentException: 'other' has different root
Ausnahme, wenn Sie nach dem relativen Pfad von"C:\temp"
nach fragen"D:\temp"
.Seit Java 7 können Sie die Relativierungsmethode verwenden:
Ausgabe:
quelle
..
bei Bedarf übernimmt (dies ist der Fall).java.nio.file
:(pathBase.normalize().relativize(pathAbsolute);
in der Regel tun .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
FilenameUtils
Klasse 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
StringBuffer
mitStringBuilder
(wenn Sie noch verwenden Java 1.4 sollten Sie einen Wechsel des Arbeitgebers statt betrachten).Die Testfälle, die dies besteht, sind
quelle
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)
Was im Wesentlichen bedeutet,
java.net.URI.relativize
dass keine ".." für Sie erstellt werden.quelle
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?Der Fehler, auf den in einer anderen Antwort verwiesen wird, wird von URIUtils in Apache HttpComponents behoben
quelle
In Java 7 und höher können Sie einfach verwenden (und im Gegensatz dazu
URI
ist es fehlerfrei):quelle
Wenn Sie wissen, dass die zweite Zeichenfolge Teil der ersten ist:
oder wenn Sie den Punkt am Anfang wirklich wie in Ihrem Beispiel wollen:
quelle
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.
quelle
Hier ist eine Lösung andere Bibliothek kostenlos:
Ausgänge
[EDIT] gibt es tatsächlich auf mehr .. \ aus, da die Quelle Datei kein Verzeichnis ist. Die richtige Lösung für meinen Fall ist:
quelle
Meine Version basiert lose auf den Versionen von Matt und Steve :
quelle
"/".length()
sollten Sie separator.lengthBei 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 istsplit
) . 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 break
Inside-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
- dasc
befindet 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\bar
und habenD:\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!
Und hier sind Tests, um mehrere Fälle abzudecken:
quelle
Eigentlich hat meine andere Antwort nicht funktioniert, wenn der Zielpfad kein Kind des Basispfads war.
Das sollte funktionieren.
quelle
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:
quelle
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:
quelle
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.
quelle
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:
quelle
Besteht Dónals Tests, die einzige Änderung - wenn keine gemeinsame Wurzel vorhanden ist, wird der Zielpfad zurückgegeben (möglicherweise bereits relativ).
quelle
Wenn Sie ein Maven-Plugin schreiben, können Sie Plexus 'verwenden
PathTool
:quelle
Wenn Paths für JRE 1.5 Runtime oder Maven Plugin nicht verfügbar ist
quelle
org.apache.ant verfügt über eine FileUtils-Klasse mit einer getRelativePath-Methode. Ich habe es noch nicht selbst ausprobiert, könnte sich aber lohnen, es auszuprobieren.
http://javadoc.haefelinger.it/org.apache.ant/1.7.1/org/apache/tools/ant/util/FileUtils.html#getRelativePath(java.io.File , java.io.File)
quelle
quelle
Pseudocode:
return "." + whicheverPathIsLonger.substring(commonPath.length);
quelle