जावा में दो निरपेक्ष पथ (या URL) से एक सापेक्ष पथ का निर्माण कैसे करें?


275

दो पूर्ण पथ दिए गए, उदा

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

कोई ऐसा रिश्तेदार पथ कैसे बना सकता है जो अपने आधार के रूप में दूसरे मार्ग का उपयोग करता है? उपरोक्त उदाहरण में, परिणाम निम्न होना चाहिए:./stuff/xyz.dat


3
जावा 7 और बाद के लिए, @ VitaliiFedorenko का जवाब देखें।
एंडी थॉमस

1
tl; dr answer: Paths.get (startPath) .relativize (Paths.get (endPath))। toString () (जो, वैसे, उदाहरण के लिए "../" जावा 8 में मेरे साथ ठीक काम कर रहा है। , इसलिए ...)
एंड्रयू

जवाबों:


297

यह थोड़ा गोल चक्कर है, लेकिन यूआरआई का उपयोग क्यों नहीं किया जाता है? इसकी एक सापेक्ष विधि है जो आपके लिए सभी आवश्यक जांच करती है।

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जावा 1.7 के बाद से फ़ाइल पथ के लिए , जैसा कि अन्य उत्तर में @ जिरका मेलुज़िन द्वारा बताया गया है ।


17
देखिए पीटर मुलर का जवाब। relativize () सभी लेकिन सरलतम मामलों के लिए बहुत टूटा हुआ प्रतीत होता है।
डेव रे

11
हां, यह केवल तभी काम करता है जब आधार पथ पहले पथ का जनक हो। यदि आपको "../../relativepath" जैसे कुछ पदानुक्रमित पिछड़े की आवश्यकता है, तो यह काम नहीं करेगा। मुझे एक हल मिला: mrpmorris.blogspot.com/2007/05/…
Aurelien Ribon

4
जैसा @VitaliiFedorenko ने लिखा है: उपयोग java.nio.file.Path#relativize(Path), यह सिर्फ माता-पिता के डबल डॉट्स और सभी के साथ काम करता है।
कैम्पा

के toPath()बजाय का उपयोग करने पर विचार करें toURI()। यह पूरी तरह से सामान बनाने में सक्षम है "..\.."। लेकिन बारे में पता होना java.lang.IllegalArgumentException: 'other' has different rootअपवाद है जब से रिश्तेदार पथ के लिए पूछ "C:\temp"करने के लिए "D:\temp"
इगोर

यह अपेक्षा के अनुरूप काम नहीं करता है, यह मेरे परीक्षण मामले में डेटा / सामान / xyz.dat लौटाता है।
20

238

जावा 7 के बाद से आप रीलेटिव विधि का उपयोग कर सकते हैं :

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
अच्छा, छोटा, कोई अतिरिक्त देय नहीं +1। एडम क्रूम का समाधान (हिट 1) मेरे परीक्षण और अगले उत्तर (हिट 2) पास नहीं करता है "केवल 'वर्किंग' समाधान" एक नया जार जोड़ता है और मेरे कार्यान्वयन की तुलना में अधिक कोड है, मुझे यह बाद में लगता है ... पहले से कहीं बेहतर ।- )
होक्र

1
लेकिन इस समस्या के लिए बाहर देखो ।
बेंज़ 3000

1
जाँच की गई कि यह ..जहां आवश्यक है उसे जोड़ता है (यह करता है)।
ओवेन

दुर्भाग्य से, Android में शामिल नहीं है java.nio.file:(
नाथन उस्मान

1
मैंने पाया है कि आप अजीब परिणाम प्राप्त करते हैं यदि "relativize" से पहले "pathBase" को "सामान्यीकृत" नहीं किया जाता है। हालांकि इस उदाहरण में ठीक है, मैं pathBase.normalize().relativize(pathAbsolute);एक सामान्य नियम के रूप में करूंगा ।
पेस्टनन

77

लेखन के समय (जून 2010), यह एकमात्र समाधान था जिसने मेरे परीक्षण मामलों को पारित किया। मैं इस बात की गारंटी नहीं दे सकता कि यह समाधान बग-मुक्त है, लेकिन इसमें शामिल परीक्षण मामले पास नहीं हैं। मैंने जो विधि और परीक्षण लिखा है वह अपाचे कॉमन्स आईओFilenameUtils से वर्ग पर निर्भर करता है ।

समाधान का जावा 1.4 के साथ परीक्षण किया गया था। यदि आप जावा 1.5 (या उच्चतर) का उपयोग कर रहे हैं, तो आपको इसके StringBufferसाथ बदलने पर विचार करना चाहिए StringBuilder(यदि आप अभी भी जावा 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
अच्छा! एक बात, हालांकि, यह टूट जाता है यदि आधार और लक्ष्य समान हैं - स्ट्रिंग आम को एक विभाजक में समाप्त करने के लिए बनाया गया है, जो सामान्यीकृत लक्ष्य पथ में नहीं है, इसलिए प्रतिस्थापन कॉल एक से कई अंकों के लिए पूछता है। मुझे लगता है कि मैंने इसे फ़ंक्शन की अंतिम दो पंक्तियों से पहले निम्नलिखित जोड़कर तय किया: यदि (common.length ()> = normalizedTargetPath.length ()) {वापसी ""। }
एरहनीस

4
यह कहना कि केवल कार्य समाधान भ्रामक है। अन्य उत्तर बेहतर काम करते हैं (यह उत्तर क्रैश तब होता है जब आधार और लक्ष्य समान होते हैं), सरल होते हैं और कॉमन्स-आईओ पर भरोसा नहीं करते हैं।
नैट्स

26

Java.net.URI.relativize का उपयोग करते समय आपको जावा बग के बारे में पता होना चाहिए: JDK-6226081 (URI आंशिक जड़ों के साथ पथों को फिर से सक्रिय करने में सक्षम होना चाहिए)

फिलहाल, द relativize()URI वसीयत केवल यूआरआई से संबंधित होगी जब एक दूसरे का उपसर्ग होगा।

जो अनिवार्य रूप से मतलब है java.net.URI.relativizeकि आपके लिए ".." नहीं बनेगा।


6
बुरा। इसके लिए वर्कअराउंड है, जाहिरा तौर पर: stackoverflow.com/questions/204784/…
skaffman

Paths.get (startPath) .relativize (Paths.get (endPath))। ToStringh () उदाहरण के लिए मेरे साथ ठीक काम कर रहा है जैसे "../" मेरे लिए जावा 8 में।
एंड्रयू

@skaffman क्या आप सुनिश्चित हैं? यह उत्तर बग JDK-6226081 का संदर्भ देता है, लेकिन URIUtils.resolve()JDK-4708535 का उल्लेख करता है। और स्रोत कोड से, मुझे बैकट्रैकिंग (यानी ..सेगमेंट) से संबंधित कुछ भी दिखाई नहीं देता है । क्या आपने दो बगों को भ्रमित किया?
गैरेट विल्सन

JDK-6920138 को JDK-4708535 के डुप्लिकेट के रूप में चिह्नित किया गया है।
क्रिश्चियन के।

17

एक अन्य उत्तर में संदर्भित बग को अपाचे एचटीटीपी.कॉम में URIUtils द्वारा संबोधित किया गया है

public static URI resolve(URI baseURI,
                          String reference)

आधार URI के विरुद्ध URI संदर्भ का निराकरण करता है। काम के लिए चारों ओर java.net.URI () में बग


क्या रिज़ॉल्यूशन विधि एक आधार और एक रिश्तेदार पथ से एक पूर्ण यूआरआई उत्पन्न नहीं करता है? यह तरीका कैसे मदद करेगा?
चेस

17

में जावा 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
स्ट्रिंग s3 = "।" + s1.substring (s2.length ()); थोड़ा अधिक पठनीय IMO है
डोरनल

10

पुनरावृत्ति एक छोटे से समाधान का उत्पादन करती है। यदि परिणाम असंभव है (उदाहरण के लिए अलग-अलग विंडोज़ डिस्क) या अव्यवहारिक (मूल केवल सामान्य निर्देशिका है) तो यह अपवाद को फेंकता है।

/**
 * 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 भारी वजन हो सकता है, इसलिए इस समाधान की अनुशंसा नहीं की जा सकती है जब आपको सौ हजार रिकॉर्ड संसाधित करने की आवश्यकता होती है। उदाहरण के लिए, मेरे पास कुछ लिस्टिंग फाइलें हैं, जो मिलियन रिकॉर्ड तक की हैं और अब मैं उन्हें पोर्टेबिलिटी के लिए सापेक्ष पथ का उपयोग करने के लिए स्थानांतरित करना चाहता हूं।
user2305886

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

मेरा संस्करण शिथिल रूप से मैट और स्टीव के संस्करणों पर आधारित है :

/**
 * 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()आप separator.length का उपयोग करना चाहिए
leonbloy

5

मैट बी के समाधान को गलत करने के लिए निर्देशिकाओं की संख्या गलत हो जाती है - यह आधार पथ की लंबाई शून्य होना चाहिए सामान्य पथ तत्वों की संख्या, शून्य से एक (अंतिम पथ तत्व के लिए, जो या तो एक फ़ाइल नाम या एक अनुगामी ""द्वारा उत्पन्न होता है 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। शायद केवल विंडोज पर एक मुद्दा है, लेकिन ध्यान देने योग्य है।

मैंने जितना इरादा किया था, उससे कहीं अधिक समय मैंने बिताया, लेकिन यह ठीक है। मुझे वास्तव में काम के लिए इसकी आवश्यकता थी, इसलिए हर किसी के लिए धन्यवाद, जिसने अंदर झांका है, और मुझे यकीन है कि इस संस्करण में सुधार भी होंगे!

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 को केवल विभाजन (regex) के लिए उपयोग करना चाहिए, जैसे कि "////" regex (जीत पथ regex), परिणाम पथ गलत होगा।
एलेक्स Ivasyuv

3

ठंडा!! मुझे इस तरह के कोड की आवश्यकता है लेकिन लिनक्स मशीनों पर निर्देशिका पथ की तुलना करने के लिए। मैंने पाया कि यह उन परिस्थितियों में काम नहीं कर रहा था जहां एक मूल निर्देशिका लक्ष्य थी।

यहाँ विधि का एक निर्देशिका अनुकूल संस्करण है:

 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 (एक फ़ोल्डर / फ़ाइल के लिए एक निरपेक्ष पथ), और your're साथ फ़ाइल / फ़ोल्डर में प्रतिनिधित्व करते हैं कि एक रास्ता तलाश में toPath एक रिश्तेदार पथ के रूप में से 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

डोनल के परीक्षणों को पारित करता है, एकमात्र परिवर्तन - यदि कोई सामान्य जड़ नहीं है तो यह लक्षित पथ देता है (यह पहले से ही सापेक्ष हो सकता है)

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

यदि पथ JRE 1.5 रनटाइम या मावेन प्लगइन के लिए उपलब्ध नहीं है

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

org.apache.ant में एक GetRelativePath विधि के साथ एक FileUtils वर्ग है। अभी तक इसे खुद करने की कोशिश नहीं की है, लेकिन इसे बाहर की जाँच करने के लिए सार्थक हो सकता है।

http://javadoc.haefelinger.it/org.apache.ant/1.7.1/org/apache/tools/ant/util/FileUtils.html#getRelativePath(java.io.File , java.io.File)


-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
यह उत्तर एक हैक सबसे अच्छा है। खिड़कियों के बारे में क्या?
Qix - MONICA
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.