วิธีสร้างเส้นทางสัมพันธ์ใน Java จากสองเส้นทางสัมบูรณ์ (หรือ URL)?


275

รับสองเส้นทางสัมบูรณ์เช่น

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

เราจะสร้างเส้นทางสัมพัทธ์ที่ใช้เส้นทางที่สองเป็นฐานได้อย่างไร ในตัวอย่างด้านบนผลลัพธ์ควรเป็น:./stuff/xyz.dat


3
สำหรับ Java 7 และใหม่กว่าโปรดดูคำตอบของ @ VitaliiFedorenko
Andy Thomas

1
tl; dr answer: Paths.get (startPath). relativize (Paths.get (endPath)) toString () (ซึ่งโดยวิธีดูเหมือนว่าจะทำงานได้ดีกับเช่น "../" สำหรับฉันใน Java 8 ดังนั้น ... )
Andrew

คำตอบ:


298

มันเป็นวงเวียนเล็ก ๆ แต่ทำไมไม่ใช้ URI มันมีวิธีการ relativize ซึ่งจะทำการตรวจสอบที่จำเป็นทั้งหมดสำหรับคุณ

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"

โปรดทราบว่าเส้นทางของไฟล์มีjava.nio.file.Path#relativizeตั้งแต่ Java 1.7 เป็นออกแหลมโดย@Jirka Meluzinในคำตอบอื่น


17
ดูคำตอบของ Peter Mueller relativize () จะค่อนข้างเสียหายสำหรับทุกคน แต่เป็นกรณีที่ง่ายที่สุด
เดฟเรย์

11
ใช่มันใช้งานได้หากเส้นทางหลักเป็นพาเรนต์ของเส้นทางแรก หากคุณต้องการลำดับชั้นแบบย้อนหลังเช่น "../../relativepath" มันจะไม่ทำงาน ฉันพบวิธีแก้ปัญหา: mrpmorris.blogspot.com/2007/05/…
Aurelien Ribon

4
ในฐานะ @VitaliiFedorenko wrote: ใช้java.nio.file.Path#relativize(Path)มันใช้ได้กับผู้ปกครองจุดคู่และทั้งหมด
Campa

พิจารณาใช้แทนtoPath() toURI()มันสามารถสร้างสิ่งต่าง ๆ ได้อย่าง"..\.."สมบูรณ์แบบ แต่ต้องตระหนักถึงjava.lang.IllegalArgumentException: 'other' has different rootข้อยกเว้นเมื่อขอเส้นทางญาติจากไป"C:\temp" "D:\temp"
อิกอร์

สิ่งนี้ไม่ทำงานอย่างที่คาดไว้มันจะส่งคืนข้อมูล / stuff / xyz.dat ในกรณีทดสอบของฉัน
ลง

238

ตั้งแต่ Java 7 คุณสามารถใช้วิธีrelativize :

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

}

เอาท์พุท:

stuff/xyz.dat

3
ดีสั้นไม่มี lib +1 พิเศษ โซลูชันของ Adam Crume (กด 1) ไม่ผ่านการทดสอบของฉันและคำตอบถัดไป (hit2) "โซลูชัน 'การทำงาน' เท่านั้น 'เพิ่ม jar ใหม่และเป็นรหัสมากกว่าการใช้งานของฉันฉันพบที่นี่หลังจากนั้น ... ดีกว่าไม่เคย. - )
hokr

1
แต่ระวังปัญหานี้
ben3000

1
ตรวจสอบว่าสิ่งนี้จัดการเพิ่ม..เมื่อจำเป็น (ไม่)
โอเว่น

น่าเสียดายที่ Android ไม่ได้รวมjava.nio.file:(
Nathan Osman

1
ฉันพบว่าคุณได้รับผลลัพธ์ที่แปลกถ้า "pathBase" ไม่ใช่ "normalized" ก่อน "relativize" แม้ว่าจะดีในตัวอย่างนี้ฉันจะทำpathBase.normalize().relativize(pathAbsolute);ตามกฎทั่วไป
pstanton

77

ในช่วงเวลาของการเขียน (มิถุนายน 2010) นี่เป็นทางออกเดียวที่ผ่านกรณีทดสอบของฉัน ฉันไม่สามารถรับประกันได้ว่าวิธีนี้จะปราศจากข้อผิดพลาด แต่มันผ่านการทดสอบรวมกรณี วิธีการและการทดสอบที่ฉันเขียนขึ้นอยู่กับFilenameUtilsคลาสจากApacheทั่วไป

โซลูชันได้รับการทดสอบด้วย Java 1.4 หากคุณใช้ Java 1.5 (หรือสูงกว่า) คุณควรพิจารณาแทนที่StringBufferด้วยStringBuilder(หากคุณยังคงใช้ Java 1.4 คุณควรพิจารณาเปลี่ยนนายจ้างแทน)

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

กรณีทดสอบที่ผ่านนี้คือ

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
ดี! สิ่งหนึ่งแม้ว่ามันจะแตกถ้าฐานและเป้าหมายเหมือนกัน - สตริงทั่วไปถูกสร้างขึ้นเพื่อสิ้นสุดในตัวแยกซึ่งเส้นทางเป้าหมายปกติไม่ได้ดังนั้นการเรียกสตริงย่อยจะขอหนึ่งหลักมากเกินไป คิดว่าฉันแก้ไขโดยเพิ่มต่อไปนี้ก่อนสองบรรทัดสุดท้ายของฟังก์ชัน: if (common.length ()> = normalizedTargetPath.length ()) {return "."; }
Erhannis

4
การพูดแบบนี้เป็นวิธีการแก้ปัญหาเดียวที่ทำให้เข้าใจผิด คำตอบอื่น ๆ ทำงานได้ดีขึ้น (คำตอบนี้ล้มเหลวเมื่อฐานและเป้าหมายเหมือนกัน) ง่ายกว่าและไม่ต้องพึ่งพาคอมมอนส์ - io
NateS

26

เมื่อใช้ java.net.URI.relativize คุณควรระวัง Java bug: JDK-6226081 (URI ควรจะสัมพันธ์กับ path ด้วย root บางส่วน)

ในขณะนี้relativize()วิธีการURIจะให้ความสำคัญกับ URI เมื่อหนึ่งเป็นคำนำหน้าของอีก

ซึ่งหมายความว่าjava.net.URI.relativizeจะไม่สร้าง ".. " สำหรับคุณ


6
น่ารังเกียจ มีวิธีแก้ไขปัญหานี้ชัดเจน: stackoverflow.com/questions/204784/…
skaffman

Paths.get (startPath). relatize (Paths.get (endPath)) toString‌ () ดูเหมือนว่าจะทำงานได้ดีกับเช่น "../" สำหรับฉันใน Java 8
Andrew

@skaffman คุณแน่ใจเหรอ? คำตอบนี้อ้างอิงข้อผิดพลาด JDK-6226081 แต่URIUtils.resolve()กล่าวถึง JDK-4708535 และจากซอร์สโค้ดฉันไม่เห็นอะไรที่เกี่ยวข้องกับการย้อนรอย (เช่น..ส่วน) คุณสับสนข้อผิดพลาดสองข้อหรือไม่?
Garret Wilson

JDK-6920138 ถูกทำเครื่องหมายว่าซ้ำกับ JDK-4708535
Christian K.

17

ข้อผิดพลาดที่อ้างถึงในคำตอบอื่นได้รับการแก้ไขโดยURIUtilsในApache HttpComponents

public static URI resolve(URI baseURI,
                          String reference)

แก้ไขการอ้างอิง URI กับ URI ฐาน แก้ไขข้อผิดพลาดใน java.net.URI ()


วิธีแก้ไขไม่สร้าง URI แบบสัมบูรณ์จากฐานและพา ธ สัมพัทธ์หรือไม่ วิธีนี้จะช่วยได้อย่างไร?
Chase

17

ในJava 7และใหม่กว่าคุณสามารถใช้ (และในทางตรงกันข้ามURIมันไม่มีข้อบกพร่อง):

Path#relativize(Path)

10

หากคุณรู้ว่าสตริงที่สองเป็นส่วนหนึ่งของสตริงแรก:

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

หรือถ้าคุณต้องการให้ช่วงเวลาเป็นจุดเริ่มต้นตามตัวอย่าง:

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

3
String s3 = "." + s1.substring (s2.length ()); เป็นเพียงเล็กน้อยอ่านได้มากขึ้น IMO
Dónal

10

การเรียกซ้ำผลิตโซลูชันที่เล็กกว่า สิ่งนี้จะส่งข้อยกเว้นหากผลลัพธ์เป็นไปไม่ได้ (เช่นดิสก์ Windows อื่น) หรือทำไม่ได้ (รูทเป็นไดเรกทอรีทั่วไปเท่านั้น)

/**
 * 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 อาจมีน้ำหนักมากดังนั้นจึงไม่สามารถแนะนำวิธีแก้ปัญหานี้เมื่อคุณต้องการประมวลผลบันทึกนับแสนรายการ ตัวอย่างเช่นฉันมีไฟล์รายชื่อบางไฟล์ที่มีมากถึงล้านเรคคอร์ดและตอนนี้ฉันต้องการย้ายไฟล์เหล่านั้นเพื่อใช้พา ธ สัมพัทธ์สำหรับการพกพา
2305886

8

นี่เป็นวิธีแก้ปัญหาห้องสมุดอื่น ๆ ฟรี:

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

เอาท์พุท

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

[แก้ไข] จริงแล้วมันส่งออกมากกว่า .. \ เพราะแหล่งที่มาไม่ใช่ไฟล์ ทางออกที่ถูกต้องสำหรับเคสของฉันคือ:

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

เวอร์ชันของฉันหลวมตามรุ่นของMattและSteve :

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

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

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

2
+1 ใช้ได้สำหรับฉัน การแก้ไขเล็กน้อยเท่านั้น: แทนที่จะ"/".length()ใช้ตัวคั่นคุณควรใช้ตัวคั่นความยาว
leonbloy

5

วิธีการแก้ปัญหาของ Matt B ทำให้จำนวนไดเรกทอรีย้อนรอยผิด - ควรเป็นความยาวของเส้นทางพื้นฐานลบด้วยจำนวนองค์ประกอบทางเดินทั่วไปลบหนึ่ง (สำหรับองค์ประกอบเส้นทางสุดท้ายซึ่งเป็นชื่อไฟล์หรือตามรอยที่""สร้างขึ้นsplit) . มันเกิดขึ้นกับการทำงานกับ/a/b/c/และ/a/x/y/แต่แทนที่ข้อโต้แย้งด้วย/m/n/o/a/b/c/และ/m/n/o/a/x/y/และคุณจะเห็นปัญหา

นอกจากนี้ยังต้องการelse breakภายในวงแรกสำหรับการวนซ้ำหรือมันจะผิดพลาดเส้นทางที่เกิดขึ้นจะมีชื่อไดเรกทอรีที่ตรงกันเช่น/a/b/c/d/และ/x/y/c/z- ที่cอยู่ในช่องเดียวกันในทั้งสองอาร์เรย์ แต่ไม่ตรงกับที่เกิดขึ้นจริง

การแก้ปัญหาทั้งหมดเหล่านี้ขาดความสามารถในการจัดการเส้นทางที่ไม่สามารถ relativized กับอีกคนหนึ่งเพราะพวกเขามีรากเข้ากันไม่ได้เช่นและC:\foo\bar D:\baz\quuxอาจเป็นเพียงปัญหาใน Windows แต่น่าสังเกต

ฉันใช้เวลานานกว่านี้กับที่ตั้งใจไว้ แต่ก็ไม่เป็นไร ฉันต้องการสิ่งนี้จริงๆสำหรับการทำงานดังนั้นขอขอบคุณทุกคนที่ chimed และฉันแน่ใจว่าจะมีการแก้ไขในเวอร์ชันนี้ด้วย!

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

และนี่คือการทดสอบเพื่อครอบคลุมหลายกรณี:

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

ที่จริงแล้วคำตอบอื่น ๆ ของฉันไม่ได้ผลถ้าเส้นทางเป้าหมายไม่ใช่ลูกของเส้นทางพื้นฐาน

สิ่งนี้น่าจะใช้ได้

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 ควรเป็น File.separator pathSeparator ควรใช้สำหรับ split (regex) เท่านั้นสำหรับ "////" regex (win path regex) พา ธ ผลลัพธ์จะไม่ถูกต้อง
Alex Ivasyuv

3

เย็น!! ฉันต้องการรหัสเล็กน้อยเช่นนี้ แต่เพื่อเปรียบเทียบเส้นทางไดเรกทอรีบนเครื่อง Linux ฉันพบว่าสิ่งนี้ไม่ทำงานในสถานการณ์ที่ไดเรกทอรีหลักเป็นเป้าหมาย

นี่เป็นวิธีที่เป็นมิตรกับไดเรกทอรีรุ่น:

 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

ฉันสมมติว่าคุณมีfromPath (พา ธ สัมบูรณ์สำหรับโฟลเดอร์) และtoPath (พา ธ สัมบูรณ์สำหรับโฟลเดอร์ / ไฟล์) และคุณกำลังมองหาพา ธ ที่มีไฟล์ / โฟลเดอร์ในtoPathเป็นพา ธ สัมพัทธ์ from fromPath (ไดเรกทอรีการทำงานปัจจุบันของคุณคือfromPath ) ดังนั้นสิ่งนี้ควรใช้งานได้:

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

มีคำตอบมากมายที่นี่ แต่ฉันพบว่าพวกเขาไม่ได้จัดการทุกกรณีเช่นฐานและเป้าหมายเหมือนกัน ฟังก์ชั่นนี้ใช้เวลาไดเรกทอรีฐานและเส้นทางเป้าหมายและส่งกลับเส้นทางญาติ หากไม่มีเส้นทางสัมพัทธ์อยู่เส้นทางเป้าหมายจะถูกส่งคืน File.separator ไม่จำเป็น

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

ต่อไปนี้เป็นวิธีการแก้ไขพา ธ สัมพัทธ์จากพา ธ ฐานโดยไม่คำนึงถึงว่าอยู่ในรูทเดียวกันหรือต่างรูท:

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

ผ่านการทดสอบของDónalการเปลี่ยนแปลงเพียงอย่างเดียว - หากไม่มีรูททั่วไปที่จะส่งคืนพา ธ เป้าหมาย (มันอาจจะสัมพันธ์กันอยู่แล้ว)

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

หาก Paths ไม่พร้อมใช้งานสำหรับ JRE 1.5 runtime หรือปลั๊กอิน maven

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

หลอกรหัส:

  1. แยกสตริงด้วยตัวคั่นพา ธ ("/")
  2. ค้นหาเส้นทางที่ยิ่งใหญ่ที่สุดโดยวนซ้ำผ่านผลลัพธ์ของสตริงการแยก (ดังนั้นคุณจะได้ "/ var / data" หรือ "/ a" ในสองตัวอย่างของคุณ)
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);

2
คำตอบนี้เป็นแฮ็คที่ดีที่สุด แล้ว windows ล่ะ?
Qix - MONICA ถูกยกเลิกเมื่อ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.