Pour Java 7 et versions ultérieures, voir la réponse de @ VitaliiFedorenko.
Andy Thomas
1
tl; dr réponse: Paths.get (startPath) .relativize (Paths.get (endPath)). toString () (qui, soit dit en passant, semble très bien fonctionner avec par exemple "../" pour moi en Java 8 , alors ...)
Andrew
Réponses:
298
C'est un petit rond-point, mais pourquoi ne pas utiliser l'URI? Il dispose d'une méthode de relativisation qui effectue toutes les vérifications nécessaires pour vous.
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"
Veuillez noter que pour le chemin du fichier, il y a java.nio.file.Path#relativizedepuis Java 1.7, comme l'a souligné @Jirka Meluzin dans l'autre réponse .
Voir la réponse de Peter Mueller. relativize () semble assez cassé pour tous sauf les cas les plus simples.
Dave Ray
11
Oui, cela ne fonctionne que si le chemin de base est un parent du premier chemin. Si vous avez besoin d'un retour en arrière hiérarchique comme "../../relativepath", cela ne fonctionnera pas. J'ai trouvé une solution: mrpmorris.blogspot.com/2007/05/…
Aurelien Ribon
4
Comme @VitaliiFedorenko l'a écrit: utilisez java.nio.file.Path#relativize(Path), cela fonctionne uniquement avec les points doubles parents et tout.
Campa
Envisagez d'utiliser toPath()au lieu de toURI(). Il est parfaitement capable de créer des trucs comme "..\..". Mais soyez conscient de l' java.lang.IllegalArgumentException: 'other' has different rootexception lorsque vous demandez un chemin relatif de "C:\temp"à "D:\temp".
Igor
Cela ne fonctionne pas comme prévu, il renvoie data / stuff / xyz.dat dans mon cas de test.
insouciant
238
Depuis Java 7, vous pouvez utiliser la méthode relativize :
Nice, bref, pas de lib supplémentaire +1. La solution d'Adam Crume (hit 1) ne passe pas mes tests et la réponse suivante (hit2) "The Only 'Working' Solution" ajoute un nouveau pot ET est plus de code que mon implémentation, je le trouve ici après ... mieux que jamais .- )
Vérifié que cela gère l'ajout ..si nécessaire (c'est le cas).
Owen
Malheureusement, Android n'inclut pas java.nio.file:(
Nathan Osman
1
J'ai trouvé que vous obtenez des résultats étranges si la "pathBase" n'est pas "normalisée" avant de "relativiser". Bien que très bien dans cet exemple, je le ferais pathBase.normalize().relativize(pathAbsolute);en règle générale.
pstanton
77
Au moment de la rédaction (juin 2010), c'était la seule solution qui avait réussi mes tests. Je ne peux pas garantir que cette solution est exempte de bogues, mais elle réussit les cas de test inclus. La méthode et les tests que j'ai écrits dépendent de la FilenameUtilsclasse d' Apache commons IO .
La solution a été testée avec Java 1.4. Si vous utilisez Java 1.5 (ou supérieur), vous devriez envisager de remplacer StringBufferpar StringBuilder(si vous utilisez toujours Java 1.4, vous devriez plutôt envisager un changement d'employeur).
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);}}}
Les cas de test que cela réussit sont
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}}
Agréable! Une chose, cependant, il casse si la base et la cible sont identiques - la chaîne commune est faite pour se terminer dans un séparateur, que le chemin cible normalisé n'a pas, donc l'appel de sous-chaîne demande un trop de chiffres. Je pense que je l'ai corrigé en ajoutant ce qui suit avant les deux dernières lignes de la fonction: if (common.length ()> = normalizedTargetPath.length ()) {return "."; }
Erhannis
4
Dire que c'est la seule solution de travail est trompeur. Les autres réponses fonctionnent mieux (cette réponse se bloque lorsque la base et la cible sont identiques), sont plus simples et ne dépendent pas de commons-io.
Paths.get (startPath) .relativize (Paths.get (endPath)). ToString () semble fonctionner très bien avec par exemple "../" pour moi en Java 8.
Andrew
@skaffman êtes-vous sûr? Cette réponse fait référence au bogue JDK-6226081, mais URIUtils.resolve()mentionne JDK-4708535. Et à partir du code source, je ne vois rien de lié au backtracking (ie les ..segments). Avez-vous confondu les deux bogues?
Garret Wilson
JDK-6920138 est marqué comme un double de JDK-4708535.
Christian K.
18
Dans Java 7 et versions ultérieures, vous pouvez simplement utiliser (et contrairement à URI, il est sans bug):
Chaîne s3 = "." + s1.sous-chaîne (s2.length ()); est un peu plus lisible IMO
Dónal
10
La récursivité produit une solution plus petite. Cela lève une exception si le résultat est impossible (par exemple, un disque Windows différent) ou impossible (la racine n'est que le répertoire commun.)
/**
* 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 peut être lourd, donc cette solution peut être déconseillée lorsque vous devez traiter des centaines de milliers d'enregistrements. Par exemple, j'ai des fichiers de liste contenant jusqu'à un million d'enregistrements et maintenant je veux les déplacer pour utiliser un chemin relatif pour la portabilité.
Ma version est vaguement basée sur les versions de Matt et 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 fonctionne pour moi. Seule correction mineure: au lieu de "/".length()vous, utilisez separator.length
leonbloy
5
La solution de Matt B obtient le nombre de répertoires à reculer incorrectement - il devrait être la longueur du chemin de base moins le nombre d'éléments de chemin communs, moins un (pour le dernier élément de chemin, qui est soit un nom de fichier soit un suivi ""généré par split) . Il se trouve que cela fonctionne avec /a/b/c/et /a/x/y/, mais remplacez les arguments par /m/n/o/a/b/c/et/m/n/o/a/x/y/ et vous verrez le problème.
En outre, il a besoin d'un else breakà l'intérieur de la première boucle for, ou il gèrera mal les chemins qui se trouvent avoir des noms de répertoire correspondants, tels que /a/b/c/d/et /x/y/c/z- le cest dans le même emplacement dans les deux tableaux, mais n'est pas une correspondance réelle.
Toutes ces solutions n'ont pas la capacité de gérer des chemins qui ne peuvent pas être relativisés les uns aux autres car ils ont des racines incompatibles, telles que C:\foo\baret D:\baz\quux. Probablement seulement un problème sur Windows, mais mérite d'être noté.
J'ai passé beaucoup plus de temps que prévu à ce sujet, mais ça va. J'avais en fait besoin de cela pour le travail, alors merci à tous ceux qui sont intervenus, et je suis sûr qu'il y aura aussi des corrections à cette version!
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;}
Et voici des tests pour couvrir plusieurs cas:
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);}
En fait, mon autre réponse n'a pas fonctionné si le chemin cible n'était pas un enfant du chemin de base.
Cela devrait fonctionner.
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);}}
Au lieu de File.pathSeparator devrait être File.separator. pathSeparator ne doit utiliser que pour split (regex), comme pour "////" regex (win path regex), le chemin du résultat sera incorrect.
Alex Ivasyuv
3
Cool!! J'ai besoin d'un peu de code comme celui-ci, mais pour comparer les chemins de répertoire sur les machines Linux. J'ai trouvé que cela ne fonctionnait pas dans les situations où un répertoire parent était la cible.
Voici une version de la méthode adaptée aux répertoires:
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;}
Je suppose que vous avez fromPath (un chemin absolu pour un dossier) et toPath (un chemin absolu pour un dossier / fichier), et que vous recherchez un chemin qui représente le fichier / dossier dans toPath comme chemin relatif de fromPath (votre répertoire de travail actuel est fromPath ) alors quelque chose comme ça devrait fonctionner:
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();}
Beaucoup de réponses sont déjà là, mais j'ai trouvé qu'elles ne traitaient pas tous les cas, comme la base et la cible étant les mêmes. Cette fonction prend un répertoire de base et un chemin cible et renvoie le chemin relatif. Si aucun chemin relatif n'existe, le chemin cible est renvoyé. File.separator n'est pas nécessaire.
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();}
Voici une méthode qui résout un chemin relatif à partir d'un chemin de base, qu'ils soient dans la même racine ou dans une racine différente:
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;}
org.apache.ant a une classe FileUtils avec une méthode getRelativePath. Je ne l'ai pas encore essayé moi-même, mais cela pourrait valoir la peine de le vérifier.
Séparez les chaînes par le séparateur de chemin ("/")
Trouvez le plus grand chemin commun en itérant à travers le résultat de la chaîne fractionnée (vous vous retrouveriez donc avec "/ var / data" ou "/ a" dans vos deux exemples)
Réponses:
C'est un petit rond-point, mais pourquoi ne pas utiliser l'URI? Il dispose d'une méthode de relativisation qui effectue toutes les vérifications nécessaires pour vous.
Veuillez noter que pour le chemin du fichier, il y a
java.nio.file.Path#relativize
depuis Java 1.7, comme l'a souligné @Jirka Meluzin dans l'autre réponse .la source
java.nio.file.Path#relativize(Path)
, cela fonctionne uniquement avec les points doubles parents et tout.toPath()
au lieu detoURI()
. Il est parfaitement capable de créer des trucs comme"..\.."
. Mais soyez conscient de l'java.lang.IllegalArgumentException: 'other' has different root
exception lorsque vous demandez un chemin relatif de"C:\temp"
à"D:\temp"
.Depuis Java 7, vous pouvez utiliser la méthode relativize :
Production:
la source
..
si nécessaire (c'est le cas).java.nio.file
:(pathBase.normalize().relativize(pathAbsolute);
en règle générale.Au moment de la rédaction (juin 2010), c'était la seule solution qui avait réussi mes tests. Je ne peux pas garantir que cette solution est exempte de bogues, mais elle réussit les cas de test inclus. La méthode et les tests que j'ai écrits dépendent de la
FilenameUtils
classe d' Apache commons IO .La solution a été testée avec Java 1.4. Si vous utilisez Java 1.5 (ou supérieur), vous devriez envisager de remplacer
StringBuffer
parStringBuilder
(si vous utilisez toujours Java 1.4, vous devriez plutôt envisager un changement d'employeur).Les cas de test que cela réussit sont
la source
Lorsque vous utilisez java.net.URI.relativize, vous devez être conscient du bogue Java: JDK-6226081 (l'URI devrait être en mesure de relativiser les chemins avec des racines partielles)
Ce qui signifie essentiellement
java.net.URI.relativize
qu'il ne créera pas de ".." pour vous.la source
URIUtils.resolve()
mentionne JDK-4708535. Et à partir du code source, je ne vois rien de lié au backtracking (ie les..
segments). Avez-vous confondu les deux bogues?Dans Java 7 et versions ultérieures, vous pouvez simplement utiliser (et contrairement à
URI
, il est sans bug):la source
Le bogue mentionné dans une autre réponse est résolu par URIUtils dans Apache HttpComponents
la source
Si vous savez que la deuxième chaîne fait partie de la première:
ou si vous voulez vraiment la période au début comme dans votre exemple:
la source
La récursivité produit une solution plus petite. Cela lève une exception si le résultat est impossible (par exemple, un disque Windows différent) ou impossible (la racine n'est que le répertoire commun.)
la source
Voici une solution autre librairie gratuite:
Les sorties
[EDIT] en fait, il sort sur plus .. \ en raison de la source est fichier n'est pas un répertoire. La bonne solution pour mon cas est:
la source
Ma version est vaguement basée sur les versions de Matt et Steve :
la source
"/".length()
vous, utilisez separator.lengthLa solution de Matt B obtient le nombre de répertoires à reculer incorrectement - il devrait être la longueur du chemin de base moins le nombre d'éléments de chemin communs, moins un (pour le dernier élément de chemin, qui est soit un nom de fichier soit un suivi
""
généré parsplit
) . Il se trouve que cela fonctionne avec/a/b/c/
et/a/x/y/
, mais remplacez les arguments par/m/n/o/a/b/c/
et/m/n/o/a/x/y/
et vous verrez le problème.En outre, il a besoin d'un
else break
à l'intérieur de la première boucle for, ou il gèrera mal les chemins qui se trouvent avoir des noms de répertoire correspondants, tels que/a/b/c/d/
et/x/y/c/z
- lec
est dans le même emplacement dans les deux tableaux, mais n'est pas une correspondance réelle.Toutes ces solutions n'ont pas la capacité de gérer des chemins qui ne peuvent pas être relativisés les uns aux autres car ils ont des racines incompatibles, telles que
C:\foo\bar
etD:\baz\quux
. Probablement seulement un problème sur Windows, mais mérite d'être noté.J'ai passé beaucoup plus de temps que prévu à ce sujet, mais ça va. J'avais en fait besoin de cela pour le travail, alors merci à tous ceux qui sont intervenus, et je suis sûr qu'il y aura aussi des corrections à cette version!
Et voici des tests pour couvrir plusieurs cas:
la source
En fait, mon autre réponse n'a pas fonctionné si le chemin cible n'était pas un enfant du chemin de base.
Cela devrait fonctionner.
la source
Cool!! J'ai besoin d'un peu de code comme celui-ci, mais pour comparer les chemins de répertoire sur les machines Linux. J'ai trouvé que cela ne fonctionnait pas dans les situations où un répertoire parent était la cible.
Voici une version de la méthode adaptée aux répertoires:
la source
Je suppose que vous avez fromPath (un chemin absolu pour un dossier) et toPath (un chemin absolu pour un dossier / fichier), et que vous recherchez un chemin qui représente le fichier / dossier dans toPath comme chemin relatif de fromPath (votre répertoire de travail actuel est fromPath ) alors quelque chose comme ça devrait fonctionner:
la source
Beaucoup de réponses sont déjà là, mais j'ai trouvé qu'elles ne traitaient pas tous les cas, comme la base et la cible étant les mêmes. Cette fonction prend un répertoire de base et un chemin cible et renvoie le chemin relatif. Si aucun chemin relatif n'existe, le chemin cible est renvoyé. File.separator n'est pas nécessaire.
la source
Voici une méthode qui résout un chemin relatif à partir d'un chemin de base, qu'ils soient dans la même racine ou dans une racine différente:
la source
Réussit les tests de Dónal, le seul changement - s'il n'y a pas de racine commune, il retourne le chemin cible (il pourrait être déjà relatif)
la source
Si vous écrivez un plugin Maven, vous pouvez utiliser Plexus
PathTool
:la source
Si Paths n'est pas disponible pour le runtime JRE 1.5 ou le plug-in maven
la source
org.apache.ant a une classe FileUtils avec une méthode getRelativePath. Je ne l'ai pas encore essayé moi-même, mais cela pourrait valoir la peine de le vérifier.
http://javadoc.haefelinger.it/org.apache.ant/1.7.1/org/apache/tools/ant/util/FileUtils.html#getRelativePath(java.io.File , java.io.File)
la source
la source
Code pseudo:
return "." + whicheverPathIsLonger.substring(commonPath.length);
la source