W przypadku wersji Java 7 i nowszych zobacz odpowiedź @ VitaliiFedorenko.
Andy Thomas,
1
tl; dr answer: Paths.get (startPath) .relativize (Paths.get (endPath)). toString () (który, nawiasem mówiąc, wydaje się działać dobrze z np. „../” dla mnie w Javie 8 , więc ...)
Andrew
Odpowiedzi:
297
To trochę rondo, ale dlaczego nie użyć URI? Ma metodę relatywizacji, która wykonuje wszystkie niezbędne kontrole.
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"
Pamiętaj, że ścieżkę do pliku można znaleźć w java.nio.file.Path#relativizejęzyku Java od wersji 1.7, na co zwrócił uwagę Jirka Meluzin w drugiej odpowiedzi .
Zobacz odpowiedź Petera Muellera. relativize () wydaje się dość zepsuty we wszystkich przypadkach oprócz najprostszych.
Dave Ray
11
Tak, działa tylko wtedy, gdy ścieżka podstawowa jest rodzicem pierwszej ścieżki. Jeśli potrzebujesz hierarchicznej wstecz, takiej jak „../../relativepath”, to nie zadziała. Znalazłem rozwiązanie: mrpmorris.blogspot.com/2007/05/…
Aurelien Ribon
4
Jak napisał @VitaliiFedorenko: użyj java.nio.file.Path#relativize(Path), działa tylko z nadrzędnymi podwójnymi kropkami i wszystkim.
Campa,
Rozważ użycie toPath()zamiast toURI(). Doskonale potrafi tworzyć takie rzeczy jak "..\..". Ale pamiętaj o java.lang.IllegalArgumentException: 'other' has different rootwyjątku, prosząc o względną ścieżkę od "C:\temp"do "D:\temp".
Igor
To nie działa zgodnie z oczekiwaniami, zwraca dane / stuff / xyz.dat w moim przypadku testowym.
nieprzyzwoity
238
Od wersji Java 7 można używać metody relatywizacji :
Ładne, krótkie, bez dodatkowych lib +1. Rozwiązanie Adama Crume'a (hit 1) nie przechodzi moich testów, a następna odpowiedź (hit2) „Jedyne„ działające ”rozwiązanie dodaje nowy słoik ORAZ zawiera więcej kodu niż moja implementacja, znajduję to tutaj później ... lepiej niż nigdy. - )
Sprawdzono, czy obsługuje dodawanie w ..razie potrzeby (tak jest).
Owen,
Niestety, Android nie obejmuje java.nio.file:(
Nathan Osman
1
Odkryłem, że masz dziwne wyniki, jeśli „pathBase” nie jest „znormalizowany” przed „relatywizacją”. Chociaż w tym przykładzie jest w porządku, postąpiłbym pathBase.normalize().relativize(pathAbsolute);zgodnie z ogólną zasadą.
pstanton
77
W momencie pisania tego tekstu (czerwiec 2010 r.) Było to jedyne rozwiązanie, które przeszło moje testy. Nie mogę zagwarantować, że to rozwiązanie jest wolne od błędów, ale przechodzi pomyślnie dołączone przypadki testowe. Metoda i testy, które napisałem, zależą od FilenameUtilsklasy z Apache commons IO .
Rozwiązanie przetestowano w Javie 1.4. Jeśli używasz języka Java 1.5 (lub nowszego), powinieneś rozważyć zastąpienie StringBuffergo StringBuilder(jeśli nadal używasz języka Java 1.4, powinieneś rozważyć zmianę pracodawcy).
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);}}}
Przypadki testowe, które przechodzi, są następujące
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}}
Miły! Jedna rzecz się jednak psuje, jeśli podstawa i cel są takie same - wspólny ciąg znaków kończy się na separatorze, którego nie ma znormalizowanej ścieżki docelowej, więc wywołanie podła pyta o jedną za dużo cyfr. Myślę, że naprawiłem to, dodając następujące przed dwoma ostatnimi wierszami funkcji: if (common.length ()> = normalizedTargetPath.length ()) {return "."; }
Erhannis,
4
Mówienie, że to jedyne działające rozwiązanie, wprowadza w błąd. Inne odpowiedzi działają lepiej (ta odpowiedź ulega awarii, gdy baza i cel są takie same), są prostsze i nie polegają na commons-io.
Paths.get (startPath) .relativize (Paths.get (endPath)). Wydaje się, że toString () działa dobrze z np. „../” dla mnie w Javie 8.
Andrew
@skaffman jesteś pewien? Ta odpowiedź odwołuje się do błędu JDK-6226081, ale URIUtils.resolve()wspomina o JDK-4708535. A z kodu źródłowego nie widzę nic związanego z cofaniem (tj. ..Segmentami). Czy pomyliłeś te dwa błędy?
Garret Wilson
JDK-6920138 jest oznaczony jako duplikat JDK-4708535.
Ciąg s3 = "." + s1. podciąg (s2. długość ()); jest nieco bardziej czytelny IMO
Dónal
10
Rekurencja daje mniejsze rozwiązanie. Powoduje to wyjątek, jeśli wynik jest niemożliwy (np. Inny dysk Windows) lub niepraktyczny (root jest tylko wspólnym katalogiem).
/**
* 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 może mieć dużą wagę, więc nie można polecać tego rozwiązania, gdy trzeba przetworzyć sto tysięcy rekordów. Na przykład mam kilka plików z listami zawierających do miliona rekordów, a teraz chcę je przenieść, aby użyć ścieżki względnej do przenoszenia.
Moja wersja jest luźno oparta na wersjach Matta i Steve'a :
/**
* 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 działa dla mnie. Tylko niewielka korekta: zamiast tego "/".length()należy użyć separatora.
Długość
5
Rozwiązanie Matta B powoduje nieprawidłową liczbę katalogów do śledzenia wstecznego - powinna to być długość ścieżki bazowej minus liczba wspólnych elementów ścieżki, minus jeden (dla ostatniego elementu ścieżki, który jest nazwą pliku lub znakiem końcowym ""generowanym przez split) . Zdarza się, że działa z /a/b/c/i /a/x/y/, ale zamień argumenty na /m/n/o/a/b/c/i /m/n/o/a/x/y/zobaczysz problem.
Potrzebuje również else breakwewnątrz pierwszej pętli for, w przeciwnym razie będzie źle obsługiwać ścieżki, które akurat mają pasujące nazwy katalogów, takie jak /a/b/c/d/i /x/y/c/z- cjest w tym samym gnieździe w obu tablicach, ale nie jest faktycznym dopasowaniem.
Wszystkie te rozwiązania nie są w stanie obsłużyć ścieżek, których nie można relatywizować, ponieważ mają niekompatybilne korzenie, takie jak C:\foo\bari D:\baz\quux. Prawdopodobnie tylko problem w systemie Windows, ale warto zauważyć.
Spędziłem na tym znacznie dłużej, niż zamierzałem, ale to w porządku. Naprawdę potrzebowałem tego do pracy, więc dziękuję wszystkim, którzy się do niego przyłączyli, i jestem pewien, że będą także poprawki w tej wersji!
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;}
A oto testy obejmujące kilka przypadków:
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);}
Właściwie moja inna odpowiedź nie zadziałała, jeśli ścieżka docelowa nie była dzieckiem ścieżki podstawowej.
To powinno działać.
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);}}
Zamiast File.pathSeparator powinien być File.separator. pathSeparator powinien używać tylko do dzielenia (wyrażenia regularnego), ponieważ w przypadku wyrażenia regularnego „////” (wyrażanie regularne ścieżki wygranej) ścieżka wyniku będzie niepoprawna.
Alex Ivasyuv
3
Chłodny!! Potrzebuję trochę takiego kodu, ale do porównania ścieżek katalogów na komputerach z systemem Linux. Odkryłem, że to nie działało w sytuacjach, w których katalog nadrzędny był celem.
Oto wersja metody przyjazna dla katalogu:
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;}
Zakładam, że masz fromPath (bezwzględną ścieżkę do folderu) i toPath (bezwzględną ścieżkę do folderu / pliku) i szukasz ścieżki, która reprezentuje plik / folder w toPath jako ścieżkę względną from fromPath (bieżący katalog roboczy to fromPath ), wtedy coś takiego powinno działać:
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();}
Wiele odpowiedzi już tu jest, ale okazało się, że nie poradziły sobie ze wszystkimi przypadkami, takimi jak baza i cel są takie same. Ta funkcja pobiera katalog podstawowy i ścieżkę docelową i zwraca ścieżkę względną. Jeśli nie istnieje ścieżka względna, zwracana jest ścieżka docelowa. File.separator jest niepotrzebny.
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();}
Tutaj metoda, która rozwiązuje ścieżkę względną ze ścieżki podstawowej, niezależnie od tego, czy znajdują się w tym samym lub innym katalogu głównym:
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;}
Odpowiedzi:
To trochę rondo, ale dlaczego nie użyć URI? Ma metodę relatywizacji, która wykonuje wszystkie niezbędne kontrole.
Pamiętaj, że ścieżkę do pliku można znaleźć w
java.nio.file.Path#relativize
języku Java od wersji 1.7, na co zwrócił uwagę Jirka Meluzin w drugiej odpowiedzi .źródło
java.nio.file.Path#relativize(Path)
, działa tylko z nadrzędnymi podwójnymi kropkami i wszystkim.toPath()
zamiasttoURI()
. Doskonale potrafi tworzyć takie rzeczy jak"..\.."
. Ale pamiętaj ojava.lang.IllegalArgumentException: 'other' has different root
wyjątku, prosząc o względną ścieżkę od"C:\temp"
do"D:\temp"
.Od wersji Java 7 można używać metody relatywizacji :
Wynik:
źródło
..
razie potrzeby (tak jest).java.nio.file
:(pathBase.normalize().relativize(pathAbsolute);
zgodnie z ogólną zasadą.W momencie pisania tego tekstu (czerwiec 2010 r.) Było to jedyne rozwiązanie, które przeszło moje testy. Nie mogę zagwarantować, że to rozwiązanie jest wolne od błędów, ale przechodzi pomyślnie dołączone przypadki testowe. Metoda i testy, które napisałem, zależą od
FilenameUtils
klasy z Apache commons IO .Rozwiązanie przetestowano w Javie 1.4. Jeśli używasz języka Java 1.5 (lub nowszego), powinieneś rozważyć zastąpienie
StringBuffer
goStringBuilder
(jeśli nadal używasz języka Java 1.4, powinieneś rozważyć zmianę pracodawcy).Przypadki testowe, które przechodzi, są następujące
źródło
Korzystając z java.net.URI.relativize, powinieneś zdawać sobie sprawę z błędu Java: JDK-6226081 (URI powinien mieć możliwość relatywizacji ścieżek z częściowymi korzeniami)
Co w istocie oznacza,
java.net.URI.relativize
że nie stworzy dla ciebie „..”.źródło
URIUtils.resolve()
wspomina o JDK-4708535. A z kodu źródłowego nie widzę nic związanego z cofaniem (tj...
Segmentami). Czy pomyliłeś te dwa błędy?Błąd, o którym mowa w innej odpowiedzi, został rozwiązany przez URIUtils w Apache HttpComponents
źródło
W Javie 7 i nowszych możesz po prostu użyć (i w przeciwieństwie do
URI
tego, jest wolne od błędów):źródło
Jeśli wiesz, że drugi ciąg jest częścią pierwszego:
lub jeśli naprawdę chcesz kropkę na początku, jak w twoim przykładzie:
źródło
Rekurencja daje mniejsze rozwiązanie. Powoduje to wyjątek, jeśli wynik jest niemożliwy (np. Inny dysk Windows) lub niepraktyczny (root jest tylko wspólnym katalogiem).
źródło
Oto rozwiązanie inne biblioteki za darmo:
Wyjścia
[EDYCJA] tak naprawdę wyświetla więcej ... \ ze względu na to, że źródłem jest plik, a nie katalog. Prawidłowe rozwiązanie dla mojej sprawy to:
źródło
Moja wersja jest luźno oparta na wersjach Matta i Steve'a :
źródło
"/".length()
należy użyć separatora.Rozwiązanie Matta B powoduje nieprawidłową liczbę katalogów do śledzenia wstecznego - powinna to być długość ścieżki bazowej minus liczba wspólnych elementów ścieżki, minus jeden (dla ostatniego elementu ścieżki, który jest nazwą pliku lub znakiem końcowym
""
generowanym przezsplit
) . Zdarza się, że działa z/a/b/c/
i/a/x/y/
, ale zamień argumenty na/m/n/o/a/b/c/
i/m/n/o/a/x/y/
zobaczysz problem.Potrzebuje również
else break
wewnątrz pierwszej pętli for, w przeciwnym razie będzie źle obsługiwać ścieżki, które akurat mają pasujące nazwy katalogów, takie jak/a/b/c/d/
i/x/y/c/z
-c
jest w tym samym gnieździe w obu tablicach, ale nie jest faktycznym dopasowaniem.Wszystkie te rozwiązania nie są w stanie obsłużyć ścieżek, których nie można relatywizować, ponieważ mają niekompatybilne korzenie, takie jak
C:\foo\bar
iD:\baz\quux
. Prawdopodobnie tylko problem w systemie Windows, ale warto zauważyć.Spędziłem na tym znacznie dłużej, niż zamierzałem, ale to w porządku. Naprawdę potrzebowałem tego do pracy, więc dziękuję wszystkim, którzy się do niego przyłączyli, i jestem pewien, że będą także poprawki w tej wersji!
A oto testy obejmujące kilka przypadków:
źródło
Właściwie moja inna odpowiedź nie zadziałała, jeśli ścieżka docelowa nie była dzieckiem ścieżki podstawowej.
To powinno działać.
źródło
Chłodny!! Potrzebuję trochę takiego kodu, ale do porównania ścieżek katalogów na komputerach z systemem Linux. Odkryłem, że to nie działało w sytuacjach, w których katalog nadrzędny był celem.
Oto wersja metody przyjazna dla katalogu:
źródło
Zakładam, że masz fromPath (bezwzględną ścieżkę do folderu) i toPath (bezwzględną ścieżkę do folderu / pliku) i szukasz ścieżki, która reprezentuje plik / folder w toPath jako ścieżkę względną from fromPath (bieżący katalog roboczy to fromPath ), wtedy coś takiego powinno działać:
źródło
Wiele odpowiedzi już tu jest, ale okazało się, że nie poradziły sobie ze wszystkimi przypadkami, takimi jak baza i cel są takie same. Ta funkcja pobiera katalog podstawowy i ścieżkę docelową i zwraca ścieżkę względną. Jeśli nie istnieje ścieżka względna, zwracana jest ścieżka docelowa. File.separator jest niepotrzebny.
źródło
Tutaj metoda, która rozwiązuje ścieżkę względną ze ścieżki podstawowej, niezależnie od tego, czy znajdują się w tym samym lub innym katalogu głównym:
źródło
Przechodzi testy Dónala, jedyna zmiana - jeśli nie ma wspólnego katalogu głównego, zwraca ścieżkę docelową (może być już względna)
źródło
Jeśli piszesz wtyczkę Maven, możesz użyć Plexusa
PathTool
:źródło
Jeśli ścieżki nie są dostępne dla środowiska wykonawczego JRE 1.5 lub wtyczki maven
źródło
org.apache.ant ma klasę FileUtils z metodą getRelativePath. Sam jeszcze tego nie próbowałem, ale warto to sprawdzić.
http://javadoc.haefelinger.it/org.apache.ant/1.7.1/org/apache/tools/ant/util/FileUtils.html#getRelativePath(java.io.File , java.io.File)
źródło
źródło
Kod Psuedo:
return "." + whicheverPathIsLonger.substring(commonPath.length);
źródło