Java'da iki mutlak yoldan (veya URL'den) göreli bir yol nasıl oluşturulur?


275

İki mutlak yol verildiğinde, örn.

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

Temel olarak ikinci yolu kullanan göreceli bir yol nasıl oluşturulabilir? Yukarıdaki örnekte sonuç şöyle olmalıdır:./stuff/xyz.dat


3
Java 7 ve üstü için @ VitaliiFedorenko'nun cevabına bakınız.
Andy Thomas

1
tl; dr yanıt: Paths.get (startPath) .relativize (Paths.get (endPath)). toString () (bu arada, Java 8'de benim için "../" ile iyi çalışıyor gibi görünüyor , yani ...)
Andrew

Yanıtlar:


298

Biraz dolambaçlı, ama neden URI kullanmıyorsunuz? Sizin için gerekli tüm kontrolleri yapan bir relativize yöntemine sahiptir.

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"

Dosya yolu için var olduğunu unutmayınız java.nio.file.Path#relativizetarafından sivri out gibi, Java 1.7 beri @Jirka Meluzin içinde diğer cevap .


17
Peter Mueller'in cevabına bakınız. relativize () en basit durumlar dışında herkes için oldukça bozuk görünüyor.
Dave Ray

11
Evet, yalnızca temel yol ilk yolun bir üst öğesiyse çalışır. "../../Relativepath" gibi bir hiyerarşik geriye ihtiyacınız varsa, çalışmaz. Bir çözüm buldum: mrpmorris.blogspot.com/2007/05/…
Aurelien Ribon

4
@VitaliiFedorenko'nun yazdığı gibi: kullanın java.nio.file.Path#relativize(Path), sadece üst çift nokta ve hepsi ile çalışır.
Campa

Kullanmayı düşünün toPath()yerine toURI(). Gibi şeyler oluşturabilir "..\..". Ancak java.lang.IllegalArgumentException: 'other' has different root' "C:\temp"den göreli yol isterken istisnanın farkında olun "D:\temp".
Igor

Bu beklendiği gibi çalışmıyor, test durumumda data / stuff / xyz.dat döndürür.
unbekant

238

Java 7'den beri relativize yöntemini kullanabilirsiniz :

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

}

Çıktı:

stuff/xyz.dat

3
Güzel, kısa, ekstra lib yok +1. Adam Crume'nin çözümü (hit 1) testlerimi ve sonraki cevabımı (hit2) geçmiyor "Only 'Working' Solution" yeni bir kavanoz ekliyor ve benim uygulamadan daha fazla kod, bunu daha sonra burada buluyorum ... hiç olmadığı kadar iyi. - )
hokr

1
Ama bu soruna dikkat et .
ben3000

1
Bunun ..gerektiğinde eklemeyi işlediğini kontrol etti (yapar).
Owen

Ne yazık ki, Android şunları içermez java.nio.file:(
Nathan Osman

1
Ben "relativize" önce "pathBase" "normalize" değilse garip sonuçlar almak buldum. Bu örnekte iyi olmasına rağmen, pathBase.normalize().relativize(pathAbsolute);genel bir kural olarak yapardım .
pstanton

77

Yazma sırasında (Haziran 2010), test davalarımı geçen tek çözüm buydu. Bu çözümün hatasız olduğunu garanti edemiyorum, ancak dahil edilen test örneklerini geçiyor. Yazdığım yöntem ve testler Apache ortak IOFilenameUtils sınıfına bağlıdır .

Çözüm Java 1.4 ile test edildi. Eğer değiştirmeyi düşünmelisiniz (yüksek veya) Java 1.5 kullanıyorsanız StringBufferile StringBuilder(yine Java 1.4 kullanıyorsanız eğer işveren değişikliği yerine düşünmelisiniz).

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

Bunun geçtiği test senaryoları

public void testGetRelativePathsUnix() {
    assertEquals("stuff/xyz.dat", ResourceUtils.getRelativePath("/var/data/stuff/xyz.dat", "/var/data/", "/"));
    assertEquals("../../b/c", ResourceUtils.getRelativePath("/a/b/c", "/a/x/y/", "/"));
    assertEquals("../../b/c", ResourceUtils.getRelativePath("/m/n/o/a/b/c", "/m/n/o/a/x/y/", "/"));
}

public void testGetRelativePathFileToFile() {
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\sapisvr.exe";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDirectoryToFile() {
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathFileToDirectory() {
    String target = "C:\\Windows\\Boot\\Fonts";
    String base = "C:\\Windows\\Speech\\Common\\foo.txt";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts", relPath);
}

public void testGetRelativePathDirectoryToDirectory() {
    String target = "C:\\Windows\\Boot\\";
    String base = "C:\\Windows\\Speech\\Common\\";
    String expected = "..\\..\\Boot";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals(expected, relPath);
}

public void testGetRelativePathDifferentDriveLetters() {
    String target = "D:\\sources\\recovery\\RecEnv.exe";
    String base = "C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";

    try {
        ResourceUtils.getRelativePath(target, base, "\\");
        fail();

    } catch (PathResolutionException ex) {
        // expected exception
    }
}

5
Güzel! Bununla birlikte, bir şey, taban ve hedef aynı ise kırılır - ortak dize, normalize edilmiş hedef yolunun sahip olmadığı bir ayırıcıda sona erdirilir, bu nedenle alt dize çağrısı çok fazla basamak ister. İşlevin son iki satırından önce aşağıdakileri ekleyerek düzelttiğimi düşünüyorum: if (common.length ()> = normalizedTargetPath.length ()) {return "."; }
Erhannis

4
Bunun tek çalışan çözüm olduğunu söylemek yanıltıcıdır. Diğer cevaplar daha iyi sonuç verir (taban ve hedef aynı olduğunda bu cevap çöker), daha basittir ve ortak kullanıma dayanmaz.
NateS

26

Java.net.URI.relativize komutunu kullanırken Java hatasının farkında olmalısınız: JDK-6226081 (URI, kısmi köklü yolları yeniden aktive edebilmelidir)

Şu anda, relativize()yöntemi URIsadece biri diğerinin öneki olduğunda URI'leri yeniden aktive edecektir.

Bu aslında java.net.URI.relativizesizin için ".." oluşturmayacak demektir .


6
Pis. Bunun için bir çözüm var, görünüşe göre: stackoverflow.com/questions/204784/…
skaffman

Paths.get (startPath) .relativize (Paths.get (endPath)). ToString‌ () Java 8'de benim için "../" ile gayet iyi çalışıyor gibi görünüyor.
Andrew

@skaffman emin misin? Bu yanıt, JDK-6226081 hatasını belirtir, ancak URIUtils.resolve()JDK-4708535'ten bahseder. Ve kaynak kodundan, geri izleme (yani ..segmentler) ile ilgili bir şey görmüyorum . İki hatayı karıştırdınız mı?
Garret Wilson

JDK-6920138, JDK-4708535'in kopyası olarak işaretlenmiştir.
Christian K.

17

Belirtilen hata başka bir yanıt tarafından ele alınmaktadır URIUtils içinde Apache HttpComponents

public static URI resolve(URI baseURI,
                          String reference)

Bir URI referansını bir temel URI'ya karşı çözümler. Java.net.URI () hata için geçici çözüm


Çözümleme yöntemi bir tabandan ve göreli yoldan mutlak bir URI oluşturmuyor mu? Bu yöntem nasıl yardımcı olabilir?
Chase

17

In Java 7 ve üstü basitçe (ve aksine kullanabilirsiniz URI, bu hata ücretsizdir):

Path#relativize(Path)

10

İkinci dizenin birincinin parçası olduğunu biliyorsanız:

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

ya da örneğin örnekte olduğu gibi başlangıçta dönemi gerçekten istiyorsanız:

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

3
Dize s3 = "." + s1.substring (s2.length ()); biraz daha okunabilir IMO
Dónal

10

Özyineleme daha küçük bir çözüm üretir. Sonuç imkansızsa (örn. Farklı Windows diski) veya uygulanamazsa (kök yalnızca ortak dizindir) bir istisna atar.

/**
 * Computes the path for a file relative to a given base, or fails if the only shared 
 * directory is the root and the absolute form is better.
 * 
 * @param base File that is the base for the result
 * @param name File to be "relativized"
 * @return the relative name
 * @throws IOException if files have no common sub-directories, i.e. at best share the
 *                     root prefix "/" or "C:\"
 */

public static String getRelativePath(File base, File name) throws IOException  {
    File parent = base.getParentFile();

    if (parent == null) {
        throw new IOException("No common directory");
    }

    String bpath = base.getCanonicalPath();
    String fpath = name.getCanonicalPath();

    if (fpath.startsWith(bpath)) {
        return fpath.substring(bpath.length() + 1);
    } else {
        return (".." + File.separator + getRelativePath(parent, name));
    }
}

getCanonicalPath ağır olabilir, bu yüzden yüz bin kaydı işlemeniz gerektiğinde bu çözüm önerilmez. Örneğin, milyonlarca kayda sahip bazı liste dosyalarım var ve şimdi taşınabilirlik için göreceli yolu kullanmak üzere onları taşımak istiyorum.
user2305886

8

İşte diğer kütüphane ücretsiz bir çözüm:

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

çıktılar

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

[EDIT] aslında kaynak bir dizin değil dosya olduğundan daha fazla .. \ çıktı. Benim durumum için doğru çözüm:

Path sourceFile = Paths.get(new File("some/common/path/example/a/b/c/f1.txt").parent());
Path targetFile = Paths.get("some/common/path/example/d/e/f2.txt"); 
Path relativePath = sourceFile.relativize(targetFile);
System.out.println(relativePath);

6

Sürümüm, Matt ve Steve'in sürümlerine dayanıyor :

/**
 * Returns the path of one File relative to another.
 *
 * @param target the target directory
 * @param base the base directory
 * @return target's path relative to the base directory
 * @throws IOException if an error occurs while resolving the files' canonical names
 */
 public static File getRelativeFile(File target, File base) throws IOException
 {
   String[] baseComponents = base.getCanonicalPath().split(Pattern.quote(File.separator));
   String[] targetComponents = target.getCanonicalPath().split(Pattern.quote(File.separator));

   // skip common components
   int index = 0;
   for (; index < targetComponents.length && index < baseComponents.length; ++index)
   {
     if (!targetComponents[index].equals(baseComponents[index]))
       break;
   }

   StringBuilder result = new StringBuilder();
   if (index != baseComponents.length)
   {
     // backtrack to base directory
     for (int i = index; i < baseComponents.length; ++i)
       result.append(".." + File.separator);
   }
   for (; index < targetComponents.length; ++index)
     result.append(targetComponents[index] + File.separator);
   if (!target.getPath().endsWith("/") && !target.getPath().endsWith("\\"))
   {
     // remove final path separator
     result.delete(result.length() - File.separator.length(), result.length());
   }
   return new File(result.toString());
 }

2
+1 benim için çalışıyor. Sadece küçük düzeltme: Bunun yerine "/".length()separator.length kullanmalısınız
leonbloy

5

Matt B'nin çözümü, geriye doğru izlemek için dizin sayısını alır - temel yolun uzunluğu eksi ortak yol öğelerinin sayısı eksi bir (bir dosya adı veya ""tarafından oluşturulan son yol öğesi için split) olmalıdır . Bu, /a/b/c/ve ile çalışır /a/x/y/, ancak argümanları /m/n/o/a/b/c/ve ile değiştirir /m/n/o/a/x/y/ve sorunu görürsünüz.

Ayrıca, else breakdöngü için ilkinin içine ihtiyaç duyar veya eşleşen dizin adlarına sahip olan /a/b/c/d/ve /x/y/c/z- cher iki dizide de aynı yuvada olduğu, ancak gerçek bir eşleşme olmadığı yolları yanlış kullanır .

Tüm bu çözümler, birbirleriyle göreli olamayan yolları ele alma yeteneğinden yoksundur, çünkü C:\foo\barve gibi uyumsuz kökleri vardır D:\baz\quux. Muhtemelen sadece Windows'da bir sorun, ancak kayda değer.

Bunu istediğimden çok daha uzun süre geçirdim, ama sorun değil. Aslında iş için buna ihtiyacım vardı, bu yüzden chim yapan herkese teşekkür ederim ve eminim bu versiyonda da düzeltmeler olacak!

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

Ve burada birkaç durumu kapsayan testler:

public void testGetRelativePathsUnixy() 
{        
    assertEquals("stuff/xyz.dat", FileUtils.getRelativePath(
            "/var/data/stuff/xyz.dat", "/var/data/", "/"));
    assertEquals("../../b/c", FileUtils.getRelativePath(
            "/a/b/c", "/a/x/y/", "/"));
    assertEquals("../../b/c", FileUtils.getRelativePath(
            "/m/n/o/a/b/c", "/m/n/o/a/x/y/", "/"));
}

public void testGetRelativePathFileToFile() 
{
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\sapisvr.exe";

    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDirectoryToFile() 
{
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common";

    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDifferentDriveLetters() 
{
    String target = "D:\\sources\\recovery\\RecEnv.exe";
    String base   = "C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";

    //  Should just return the target path because of the incompatible roots.
    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals(target, relPath);
}

4

Aslında hedef yolun temel yolun alt öğesi olmaması durumunda diğer yanıtım işe yaramadı.

Bu çalışmalı.

public class RelativePathFinder {

    public static String getRelativePath(String targetPath, String basePath, 
       String pathSeparator) {

        // find common path
        String[] target = targetPath.split(pathSeparator);
        String[] base = basePath.split(pathSeparator);

        String common = "";
        int commonIndex = 0;
        for (int i = 0; i < target.length && i < base.length; i++) {

            if (target[i].equals(base[i])) {
                common += target[i] + pathSeparator;
                commonIndex++;
            }
        }


        String relative = "";
        // is the target a child directory of the base directory?
        // i.e., target = /a/b/c/d, base = /a/b/
        if (commonIndex == base.length) {
            relative = "." + pathSeparator + targetPath.substring(common.length());
        }
        else {
            // determine how many directories we have to backtrack
            for (int i = 1; i <= commonIndex; i++) {
                relative += ".." + pathSeparator;
            }
            relative += targetPath.substring(common.length());
        }

        return relative;
    }

    public static String getRelativePath(String targetPath, String basePath) {
        return getRelativePath(targetPath, basePath, File.pathSeparator);
    }
}

public class RelativePathFinderTest extends TestCase {

    public void testGetRelativePath() {
        assertEquals("./stuff/xyz.dat", RelativePathFinder.getRelativePath(
                "/var/data/stuff/xyz.dat", "/var/data/", "/"));
        assertEquals("../../b/c", RelativePathFinder.getRelativePath("/a/b/c",
                "/a/x/y/", "/"));
    }

}

2
File.pathSeparator yerine File.separator olmalıdır. pathSeparator yalnızca "////" normal ifadesi (kazanma yolu normal ifadesi) için olduğu gibi yalnızca bölme (normal ifade) için kullanılmalıdır, sonuç yolu yanlış olur.
Alex Ivasyuv

3

Güzel!! Böyle bir kod biraz ama Linux makinelerde dizin yollarını karşılaştırmak için ihtiyacım var. Bunun bir üst dizinin hedef olduğu durumlarda çalışmadığını gördüm.

İşte yöntemin dizin dostu bir sürümü:

 public static String getRelativePath(String targetPath, String basePath, 
     String pathSeparator) {

 boolean isDir = false;
 {
   File f = new File(targetPath);
   isDir = f.isDirectory();
 }
 //  We need the -1 argument to split to make sure we get a trailing 
 //  "" token if the base ends in the path separator and is therefore
 //  a directory. We require directory paths to end in the path
 //  separator -- otherwise they are indistinguishable from files.
 String[] base = basePath.split(Pattern.quote(pathSeparator), -1);
 String[] target = targetPath.split(Pattern.quote(pathSeparator), 0);

 //  First get all the common elements. Store them as a string,
 //  and also count how many of them there are. 
 String common = "";
 int commonIndex = 0;
 for (int i = 0; i < target.length && i < base.length; i++) {
     if (target[i].equals(base[i])) {
         common += target[i] + pathSeparator;
         commonIndex++;
     }
     else break;
 }

 if (commonIndex == 0)
 {
     //  Whoops -- not even a single common path element. This most
     //  likely indicates differing drive letters, like C: and D:. 
     //  These paths cannot be relativized. Return the target path.
     return targetPath;
     //  This should never happen when all absolute paths
     //  begin with / as in *nix. 
 }

 String relative = "";
 if (base.length == commonIndex) {
     //  Comment this out if you prefer that a relative path not start with ./
     relative = "." + pathSeparator;
 }
 else {
     int numDirsUp = base.length - commonIndex - (isDir?0:1); /* only subtract 1 if it  is a file. */
     //  The number of directories we have to backtrack is the length of 
     //  the base path MINUS the number of common path elements, minus
     //  one because the last element in the path isn't a directory.
     for (int i = 1; i <= (numDirsUp); i++) {
         relative += ".." + pathSeparator;
     }
 }
 //if we are comparing directories then we 
 if (targetPath.length() > common.length()) {
  //it's OK, it isn't a directory
  relative += targetPath.substring(common.length());
 }

 return relative;
}

2

Elinizde farz ediyorum fromPath (bir klasör için mutlak yol) ve toPath (Bir klasör / dosya için mutlak yol) ve İlgilendiğiniz ile dosyayı / klasör temsil ettiğini bir yol arayan toPath göreceli yol olarak dan fromPath (geçerli çalışma dizini olan fromPath sonra böyle bir şey çalışması gerekir):

public static String getRelativePath(String fromPath, String toPath) {

  // This weirdness is because a separator of '/' messes with String.split()
  String regexCharacter = File.separator;
  if (File.separatorChar == '\\') {
    regexCharacter = "\\\\";
  }

  String[] fromSplit = fromPath.split(regexCharacter);
  String[] toSplit = toPath.split(regexCharacter);

  // Find the common path
  int common = 0;
  while (fromSplit[common].equals(toSplit[common])) {
    common++;
  }

  StringBuffer result = new StringBuffer(".");

  // Work your way up the FROM path to common ground
  for (int i = common; i < fromSplit.length; i++) {
    result.append(File.separatorChar).append("..");
  }

  // Work your way down the TO path
  for (int i = common; i < toSplit.length; i++) {
    result.append(File.separatorChar).append(toSplit[i]);
  }

  return result.toString();
}

1

Zaten burada bir sürü cevap, ama temel ve hedef aynı olmak gibi tüm davaları ele almadığını buldum. Bu işlev, bir temel dizini ve bir hedef yolu alır ve göreli yolu döndürür. Göreli yol yoksa, hedef yol döndürülür. File.separator gereksiz.

public static String getRelativePath (String baseDir, String targetPath) {
    String[] base = baseDir.replace('\\', '/').split("\\/");
    targetPath = targetPath.replace('\\', '/');
    String[] target = targetPath.split("\\/");

    // Count common elements and their length.
    int commonCount = 0, commonLength = 0, maxCount = Math.min(target.length, base.length);
    while (commonCount < maxCount) {
        String targetElement = target[commonCount];
        if (!targetElement.equals(base[commonCount])) break;
        commonCount++;
        commonLength += targetElement.length() + 1; // Directory name length plus slash.
    }
    if (commonCount == 0) return targetPath; // No common path element.

    int targetLength = targetPath.length();
    int dirsUp = base.length - commonCount;
    StringBuffer relative = new StringBuffer(dirsUp * 3 + targetLength - commonLength + 1);
    for (int i = 0; i < dirsUp; i++)
        relative.append("../");
    if (commonLength < targetLength) relative.append(targetPath.substring(commonLength));
    return relative.toString();
}

0

Burada, aynı veya farklı bir kökte olduklarına bakılmaksızın, bir temel yoldan göreceli bir yolu çözen bir yöntem:

public static String GetRelativePath(String path, String base){

    final String SEP = "/";

    // if base is not a directory -> return empty
    if (!base.endsWith(SEP)){
        return "";
    }

    // check if path is a file -> remove last "/" at the end of the method
    boolean isfile = !path.endsWith(SEP);

    // get URIs and split them by using the separator
    String a = "";
    String b = "";
    try {
        a = new File(base).getCanonicalFile().toURI().getPath();
        b = new File(path).getCanonicalFile().toURI().getPath();
    } catch (IOException e) {
        e.printStackTrace();
    }
    String[] basePaths = a.split(SEP);
    String[] otherPaths = b.split(SEP);

    // check common part
    int n = 0;
    for(; n < basePaths.length && n < otherPaths.length; n ++)
    {
        if( basePaths[n].equals(otherPaths[n]) == false )
            break;
    }

    // compose the new path
    StringBuffer tmp = new StringBuffer("");
    for(int m = n; m < basePaths.length; m ++)
        tmp.append(".."+SEP);
    for(int m = n; m < otherPaths.length; m ++)
    {
        tmp.append(otherPaths[m]);
        tmp.append(SEP);
    }

    // get path string
    String result = tmp.toString();

    // remove last "/" if path is a file
    if (isfile && result.endsWith(SEP)){
        result = result.substring(0,result.length()-1);
    }

    return result;
}

0

Tek değişiklik Dónal'ın testlerini geçer - ortak bir kök yoksa hedef yolu döndürür (zaten göreceli olabilir)

import static java.util.Arrays.asList;
import static java.util.Collections.nCopies;
import static org.apache.commons.io.FilenameUtils.normalizeNoEndSeparator;
import static org.apache.commons.io.FilenameUtils.separatorsToUnix;
import static org.apache.commons.lang3.StringUtils.getCommonPrefix;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.apache.commons.lang3.StringUtils.join;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ResourceUtils {

    public static String getRelativePath(String targetPath, String basePath, String pathSeparator) {
        File baseFile = new File(basePath);
        if (baseFile.isFile() || !baseFile.exists() && !basePath.endsWith("/") && !basePath.endsWith("\\"))
            basePath = baseFile.getParent();

        String target = separatorsToUnix(normalizeNoEndSeparator(targetPath));
        String base = separatorsToUnix(normalizeNoEndSeparator(basePath));

        String commonPrefix = getCommonPrefix(target, base);
        if (isBlank(commonPrefix))
            return targetPath.replaceAll("/", pathSeparator);

        target = target.replaceFirst(commonPrefix, "");
        base = base.replaceFirst(commonPrefix, "");

        List<String> result = new ArrayList<>();
        if (isNotEmpty(base))
            result.addAll(nCopies(base.split("/").length, ".."));
        result.addAll(asList(target.replaceFirst("^/", "").split("/")));

        return join(result, pathSeparator);
    }
}

0

Bir Maven eklentisi yazıyorsanız, kullanabileceğiniz PlexusPathTool :

import org.codehaus.plexus.util.PathTool;

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

0

JRE 1.5 çalışma zamanı veya maven eklentisi için Yollar yoksa

package org.afc.util;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

public class FileUtil {

    public static String getRelativePath(String basePath, String filePath)  {
        return getRelativePath(new File(basePath), new File(filePath));
    }

    public static String getRelativePath(File base, File file)  {

        List<String> bases = new LinkedList<String>();
        bases.add(0, base.getName());
        for (File parent = base.getParentFile(); parent != null; parent = parent.getParentFile()) {
            bases.add(0, parent.getName());
        }

        List<String> files = new LinkedList<String>();
        files.add(0, file.getName());
        for (File parent = file.getParentFile(); parent != null; parent = parent.getParentFile()) {
            files.add(0, parent.getName());
        }

        int overlapIndex = 0;
        while (overlapIndex < bases.size() && overlapIndex < files.size() && bases.get(overlapIndex).equals(files.get(overlapIndex))) {
            overlapIndex++;
        }

        StringBuilder relativePath = new StringBuilder();
        for (int i = overlapIndex; i < bases.size(); i++) {
            relativePath.append("..").append(File.separatorChar);
        }

        for (int i = overlapIndex; i < files.size(); i++) {
            relativePath.append(files.get(i)).append(File.separatorChar);
        }

        relativePath.deleteCharAt(relativePath.length() - 1);
        return relativePath.toString();
    }

}


-1
private String relative(String left, String right){
    String[] lefts = left.split("/");
    String[] rights = right.split("/");
    int min = Math.min(lefts.length, rights.length);
    int commonIdx = -1;
    for(int i = 0; i < min; i++){
        if(commonIdx < 0 && !lefts[i].equals(rights[i])){
            commonIdx = i - 1;
            break;
        }
    }
    if(commonIdx < 0){
        return null;
    }
    StringBuilder sb = new StringBuilder(Math.max(left.length(), right.length()));
    sb.append(left).append("/");
    for(int i = commonIdx + 1; i < lefts.length;i++){
        sb.append("../");
    }
    for(int i = commonIdx + 1; i < rights.length;i++){
        sb.append(rights[i]).append("/");
    }

    return sb.deleteCharAt(sb.length() -1).toString();
}

-2

Psuedo kodu:

  1. Dizeleri yol ayırıcıyla ("/") ayırın
  2. Bölünmüş dizenin sonucunu yineleyerek en yaygın yolu bulun (böylece iki örnekte "/ var / data" veya "/ a" ile sonuçlanırsınız)
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);

2
Bu cevap en iyi ihtimalle bir hack'tir. Pencereler ne olacak?
Qix - MONICA
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.