Untuk Java 7 dan yang lebih baru, lihat jawaban @ VitaliiFedorenko.
Andy Thomas
1
tl; dr jawab: Paths.get (startPath) .relativize (Paths.get (endPath)). toString () (yang, omong-omong, tampaknya berfungsi dengan baik dengan misalnya "../" untuk saya di Jawa 8 , jadi ...)
Andrew
Jawaban:
298
Ini sedikit bundaran, tetapi mengapa tidak menggunakan URI? Ini memiliki metode relativize yang melakukan semua pemeriksaan yang diperlukan untuk Anda.
String path ="/var/data/stuff/xyz.dat";String base ="/var/data";String relative =newFile(base).toURI().relativize(newFile(path).toURI()).getPath();// relative == "stuff/xyz.dat"
Harap dicatat bahwa untuk jalur file sudah ada java.nio.file.Path#relativizesejak Java 1.7, seperti yang ditunjukkan oleh @Jirka Meluzin di jawaban lain .
Lihat jawaban Peter Mueller. relativize () tampak sangat rusak untuk semua kecuali kasus yang paling sederhana.
Dave Ray
11
Yap, itu hanya berfungsi jika jalur dasar adalah induk dari jalur pertama. Jika Anda memerlukan beberapa hierarki mundur seperti "../../relativepath", itu tidak akan berfungsi. Saya menemukan solusi: mrpmorris.blogspot.com/2007/05/...
Aurelien Ribon
4
Seperti @VitaliiFedorenko menulis: gunakan java.nio.file.Path#relativize(Path), itu hanya berfungsi dengan titik ganda induk dan semuanya.
Campa
Pertimbangkan untuk menggunakan toPath()bukan toURI(). Ini sangat bisa membuat barang-barang seperti "..\..". Tetapi waspadai java.lang.IllegalArgumentException: 'other' has different rootpengecualian ketika meminta jalur relatif dari "C:\temp"ke "D:\temp".
Igor
Ini tidak berfungsi seperti yang diharapkan, ini mengembalikan data / stuff / xyz.dat dalam test case saya.
izin
238
Karena Java 7 Anda dapat menggunakan metode relativize :
Bagus, pendek, tanpa lib ekstra +1. Solusi Adam Crume (hit 1) tidak lulus tes saya dan jawaban berikutnya (hit2) "Satu-satunya 'Bekerja' Solusi" menambahkan jar baru DAN lebih banyak kode daripada implementasi saya, saya menemukan ini di sini setelah itu ... lebih baik daripada tidak pernah .- )
Diperiksa bahwa ini menangani penambahan ..jika perlu (benar).
Owen
Sayangnya, Android tidak termasuk java.nio.file:(
Nathan Osman
1
Saya menemukan Anda mendapatkan hasil yang aneh jika "pathBase" tidak "dinormalisasi" sebelum "relativize". Meskipun baik dalam contoh ini, saya akan melakukan pathBase.normalize().relativize(pathAbsolute);sebagai aturan umum.
pstanton
77
Pada saat penulisan (Juni 2010), ini adalah satu-satunya solusi yang lulus ujian saya. Saya tidak dapat menjamin bahwa solusi ini bebas bug, tetapi tidak lulus uji kasus yang disertakan. Metode dan tes yang saya tulis tergantung pada FilenameUtilskelas dari Apache commons IO .
Solusinya diuji dengan Java 1.4. Jika Anda menggunakan Java 1.5 (atau lebih tinggi) Anda harus mempertimbangkan untuk mengganti StringBufferdengan StringBuilder(jika Anda masih menggunakan Java 1.4, Anda harus mempertimbangkan penggantian majikan).
import java.io.File;import java.util.regex.Pattern;import org.apache.commons.io.FilenameUtils;publicclassResourceUtils{/**
* Get the relative path from one file to another, specifying the directory separator.
* If one of the provided resources does not exist, it is assumed to be a file unless it ends with '/' or
* '\'.
*
* @param targetPath targetPath is calculated to this file
* @param basePath basePath is calculated from this file
* @param pathSeparator directory separator. The platform default is not assumed so that we can test Unix behaviour when running on Windows (for example)
* @return
*/publicstaticString getRelativePath(String targetPath,String basePath,String pathSeparator){// Normalize the pathsString normalizedTargetPath =FilenameUtils.normalizeNoEndSeparator(targetPath);String normalizedBasePath =FilenameUtils.normalizeNoEndSeparator(basePath);// Undo the changes to the separators made by normalizationif(pathSeparator.equals("/")){
normalizedTargetPath =FilenameUtils.separatorsToUnix(normalizedTargetPath);
normalizedBasePath =FilenameUtils.separatorsToUnix(normalizedBasePath);}elseif(pathSeparator.equals("\\")){
normalizedTargetPath =FilenameUtils.separatorsToWindows(normalizedTargetPath);
normalizedBasePath =FilenameUtils.separatorsToWindows(normalizedBasePath);}else{thrownewIllegalArgumentException("Unrecognised dir separator '"+ pathSeparator +"'");}String[] base = normalizedBasePath.split(Pattern.quote(pathSeparator));String[] target = normalizedTargetPath.split(Pattern.quote(pathSeparator));// First get all the common elements. Store them as a string,// and also count how many of them there are.StringBuffer common =newStringBuffer();int commonIndex =0;while(commonIndex < target.length && commonIndex < base.length
&& target[commonIndex].equals(base[commonIndex])){
common.append(target[commonIndex]+ pathSeparator);
commonIndex++;}if(commonIndex ==0){// No single common path element. This most// likely indicates differing drive letters, like C: and D:.// These paths cannot be relativized.thrownewPathResolutionException("No common path element found for '"+ normalizedTargetPath +"' and '"+ normalizedBasePath
+"'");}// The number of directories we have to backtrack depends on whether the base is a file or a dir// For example, the relative path from//// /foo/bar/baz/gg/ff to /foo/bar/baz// // ".." if ff is a file// "../.." if ff is a directory//// The following is a heuristic to figure out if the base refers to a file or dir. It's not perfect, because// the resource referred to by this path may not actually exist, but it's the best I can doboolean baseIsFile =true;File baseResource =newFile(normalizedBasePath);if(baseResource.exists()){
baseIsFile = baseResource.isFile();}elseif(basePath.endsWith(pathSeparator)){
baseIsFile =false;}StringBuffer relative =newStringBuffer();if(base.length != commonIndex){int numDirsUp = baseIsFile ? base.length - commonIndex -1: base.length - commonIndex;for(int i =0; i < numDirsUp; i++){
relative.append(".."+ pathSeparator);}}
relative.append(normalizedTargetPath.substring(common.length()));return relative.toString();}staticclassPathResolutionExceptionextendsRuntimeException{PathResolutionException(String msg){super(msg);}}}
Kasus uji yang lolos adalah
publicvoid testGetRelativePathsUnix(){
assertEquals("stuff/xyz.dat",ResourceUtils.getRelativePath("/var/data/stuff/xyz.dat","/var/data/","/"));
assertEquals("../../b/c",ResourceUtils.getRelativePath("/a/b/c","/a/x/y/","/"));
assertEquals("../../b/c",ResourceUtils.getRelativePath("/m/n/o/a/b/c","/m/n/o/a/x/y/","/"));}publicvoid testGetRelativePathFileToFile(){String target ="C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";String base ="C:\\Windows\\Speech\\Common\\sapisvr.exe";String relPath =ResourceUtils.getRelativePath(target, base,"\\");
assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);}publicvoid testGetRelativePathDirectoryToFile(){String target ="C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";String base ="C:\\Windows\\Speech\\Common\\";String relPath =ResourceUtils.getRelativePath(target, base,"\\");
assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);}publicvoid testGetRelativePathFileToDirectory(){String target ="C:\\Windows\\Boot\\Fonts";String base ="C:\\Windows\\Speech\\Common\\foo.txt";String relPath =ResourceUtils.getRelativePath(target, base,"\\");
assertEquals("..\\..\\Boot\\Fonts", relPath);}publicvoid testGetRelativePathDirectoryToDirectory(){String target ="C:\\Windows\\Boot\\";String base ="C:\\Windows\\Speech\\Common\\";String expected ="..\\..\\Boot";String relPath =ResourceUtils.getRelativePath(target, base,"\\");
assertEquals(expected, relPath);}publicvoid testGetRelativePathDifferentDriveLetters(){String target ="D:\\sources\\recovery\\RecEnv.exe";String base ="C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";try{ResourceUtils.getRelativePath(target, base,"\\");
fail();}catch(PathResolutionException ex){// expected exception}}
Bagus! Satu hal, meskipun, itu rusak jika basis dan target adalah sama - string umum dibuat untuk berakhir di pemisah, yang tidak dimiliki jalur target yang dinormalisasi, sehingga panggilan substring meminta satu digit terlalu banyak. Pikirkan saya memperbaikinya dengan menambahkan berikut sebelum dua baris terakhir dari fungsi: if (common.length ()> = normalizedTargetPath.length ()) {return "."; }
Erhannis
4
Mengatakan ini adalah satu-satunya solusi yang berfungsi adalah menyesatkan. Jawaban lain berfungsi lebih baik (jawaban ini macet ketika basis dan targetnya sama), lebih sederhana dan tidak bergantung pada commons-io.
Paths.get (startPath) .relativize (Paths.get (endPath)). ToString () tampaknya berfungsi dengan baik dengan misalnya "../" untuk saya di Jawa 8.
Andrew
@ skaffman Anda yakin? Jawaban ini merujuk bug JDK-6226081, tetapi URIUtils.resolve()menyebutkan JDK-4708535. Dan dari kode sumber, saya tidak melihat apa pun yang terkait dengan penelusuran ulang (yaitu ..segmen). Apakah Anda membingungkan kedua bug?
Garret Wilson
JDK-6920138 ditandai sebagai duplikat JDK-4708535.
Christian K.
18
Di Java 7 dan yang lebih baru, Anda cukup menggunakan (dan berbeda dengan URIitu, bebas bug):
String s3 = "." + s1.substring (s2.length ()); IMO sedikit lebih mudah dibaca
Dónal
10
Rekursi menghasilkan solusi yang lebih kecil. Ini melempar pengecualian jika hasilnya tidak mungkin (misalnya disk Windows berbeda) atau tidak praktis (root hanya direktori umum.)
/**
* Computes the path for a file relative to a given base, or fails if the only shared
* directory is the root and the absolute form is better.
*
* @param base File that is the base for the result
* @param name File to be "relativized"
* @return the relative name
* @throws IOException if files have no common sub-directories, i.e. at best share the
* root prefix "/" or "C:\"
*/publicstaticString getRelativePath(File base,File name)throwsIOException{File parent = base.getParentFile();if(parent ==null){thrownewIOException("No common directory");}String bpath = base.getCanonicalPath();String fpath = name.getCanonicalPath();if(fpath.startsWith(bpath)){return fpath.substring(bpath.length()+1);}else{return(".."+File.separator + getRelativePath(parent, name));}}
getCanonicalPath bisa jadi sangat berat, jadi solusi ini tidak bisa direkomendasikan ketika Anda perlu memproses ratusan ribu catatan. Misalnya saya memiliki beberapa file daftar yang memiliki hingga jutaan catatan dan sekarang saya ingin memindahkannya untuk menggunakan jalur relatif untuk portabilitas.
Versi saya secara longgar didasarkan pada versi Matt dan Steve :
/**
* Returns the path of one File relative to another.
*
* @param target the target directory
* @param base the base directory
* @return target's path relative to the base directory
* @throws IOException if an error occurs while resolving the files' canonical names
*/publicstaticFile getRelativeFile(File target,File base)throwsIOException{String[] baseComponents = base.getCanonicalPath().split(Pattern.quote(File.separator));String[] targetComponents = target.getCanonicalPath().split(Pattern.quote(File.separator));// skip common componentsint index =0;for(; index < targetComponents.length && index < baseComponents.length;++index){if(!targetComponents[index].equals(baseComponents[index]))break;}StringBuilder result =newStringBuilder();if(index != baseComponents.length){// backtrack to base directoryfor(int i = index; i < baseComponents.length;++i)
result.append(".."+File.separator);}for(; index < targetComponents.length;++index)
result.append(targetComponents[index]+File.separator);if(!target.getPath().endsWith("/")&&!target.getPath().endsWith("\\")){// remove final path separator
result.delete(result.length()-File.separator.length(), result.length());}returnnewFile(result.toString());}
+1 berfungsi untuk saya. Hanya koreksi kecil: alih-alih "/".length()Anda harus menggunakan separator.length
leonbloy
5
Solusi Matt B membuat jumlah direktori untuk mundur dengan salah - itu harus menjadi panjang jalur dasar dikurangi jumlah elemen jalur umum, minus satu (untuk elemen jalur terakhir, yang dapat berupa nama file atau trailing yang ""dihasilkan oleh split) . Itu terjadi untuk bekerja dengan /a/b/c/dan /a/x/y/, tetapi mengganti argumen dengan /m/n/o/a/b/c/dan/m/n/o/a/x/y/ dan Anda akan melihat masalahnya.
Juga, ini membutuhkan bagian else breakdalam pertama untuk loop, atau itu akan salah jalan yang kebetulan memiliki nama direktori yang cocok, seperti /a/b/c/d/dan /x/y/c/z- yang cada di slot yang sama di kedua array, tetapi bukan pertandingan yang sebenarnya.
Semua solusi ini tidak memiliki kemampuan untuk menangani jalur yang tidak dapat direlatifikasi satu sama lain karena mereka memiliki akar yang tidak kompatibel, seperti C:\foo\bardan D:\baz\quux. Mungkin hanya masalah pada Windows, tetapi patut dicatat.
Saya menghabiskan jauh lebih lama untuk ini daripada yang saya maksudkan, tapi tidak apa-apa. Saya benar-benar membutuhkan ini untuk bekerja, jadi terima kasih kepada semua orang yang telah ikut serta, dan saya yakin akan ada koreksi pada versi ini juga!
publicstaticString getRelativePath(String targetPath,String basePath,String pathSeparator){// We need the -1 argument to split to make sure we get a trailing // "" token if the base ends in the path separator and is therefore// a directory. We require directory paths to end in the path// separator -- otherwise they are indistinguishable from files.String[] base = basePath.split(Pattern.quote(pathSeparator),-1);String[] target = targetPath.split(Pattern.quote(pathSeparator),0);// First get all the common elements. Store them as a string,// and also count how many of them there are. String common ="";int commonIndex =0;for(int i =0; i < target.length && i < base.length; i++){if(target[i].equals(base[i])){
common += target[i]+ pathSeparator;
commonIndex++;}elsebreak;}if(commonIndex ==0){// Whoops -- not even a single common path element. This most// likely indicates differing drive letters, like C: and D:. // These paths cannot be relativized. Return the target path.return targetPath;// This should never happen when all absolute paths// begin with / as in *nix. }String relative ="";if(base.length == commonIndex){// Comment this out if you prefer that a relative path not start with .///relative = "." + pathSeparator;}else{int numDirsUp = base.length - commonIndex -1;// The number of directories we have to backtrack is the length of // the base path MINUS the number of common path elements, minus// one because the last element in the path isn't a directory.for(int i =1; i <=(numDirsUp); i++){
relative +=".."+ pathSeparator;}}
relative += targetPath.substring(common.length());return relative;}
Dan berikut ini adalah tes untuk mencakup beberapa kasus:
publicvoid testGetRelativePathsUnixy(){
assertEquals("stuff/xyz.dat",FileUtils.getRelativePath("/var/data/stuff/xyz.dat","/var/data/","/"));
assertEquals("../../b/c",FileUtils.getRelativePath("/a/b/c","/a/x/y/","/"));
assertEquals("../../b/c",FileUtils.getRelativePath("/m/n/o/a/b/c","/m/n/o/a/x/y/","/"));}publicvoid testGetRelativePathFileToFile(){String target ="C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";String base ="C:\\Windows\\Speech\\Common\\sapisvr.exe";String relPath =FileUtils.getRelativePath(target, base,"\\");
assertEquals("..\\..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);}publicvoid testGetRelativePathDirectoryToFile(){String target ="C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";String base ="C:\\Windows\\Speech\\Common";String relPath =FileUtils.getRelativePath(target, base,"\\");
assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);}publicvoid testGetRelativePathDifferentDriveLetters(){String target ="D:\\sources\\recovery\\RecEnv.exe";String base ="C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";// Should just return the target path because of the incompatible roots.String relPath =FileUtils.getRelativePath(target, base,"\\");
assertEquals(target, relPath);}
Sebenarnya jawaban saya yang lain tidak berfungsi jika jalur target bukan anak dari jalur dasar.
Ini seharusnya bekerja.
publicclassRelativePathFinder{publicstaticString getRelativePath(String targetPath,String basePath,String pathSeparator){// find common pathString[] target = targetPath.split(pathSeparator);String[] base = basePath.split(pathSeparator);String common ="";int commonIndex =0;for(int i =0; i < target.length && i < base.length; i++){if(target[i].equals(base[i])){
common += target[i]+ pathSeparator;
commonIndex++;}}String relative ="";// is the target a child directory of the base directory?// i.e., target = /a/b/c/d, base = /a/b/if(commonIndex == base.length){
relative ="."+ pathSeparator + targetPath.substring(common.length());}else{// determine how many directories we have to backtrackfor(int i =1; i <= commonIndex; i++){
relative +=".."+ pathSeparator;}
relative += targetPath.substring(common.length());}return relative;}publicstaticString getRelativePath(String targetPath,String basePath){return getRelativePath(targetPath, basePath,File.pathSeparator);}}
Alih-alih File.pathSeparator harus File.separator. pathSeparator harus digunakan hanya untuk split (regex), seperti untuk regex "////" (win path regex), jalur hasil akan salah.
Alex Ivasyuv
3
Keren!! Saya perlu sedikit kode seperti ini tetapi untuk membandingkan jalur direktori pada mesin Linux. Saya menemukan bahwa ini tidak berfungsi dalam situasi di mana direktori induk adalah targetnya.
Berikut adalah versi metode yang ramah direktori:
publicstaticString getRelativePath(String targetPath,String basePath,String pathSeparator){boolean isDir =false;{File f =newFile(targetPath);
isDir = f.isDirectory();}// We need the -1 argument to split to make sure we get a trailing // "" token if the base ends in the path separator and is therefore// a directory. We require directory paths to end in the path// separator -- otherwise they are indistinguishable from files.String[] base = basePath.split(Pattern.quote(pathSeparator),-1);String[] target = targetPath.split(Pattern.quote(pathSeparator),0);// First get all the common elements. Store them as a string,// and also count how many of them there are. String common ="";int commonIndex =0;for(int i =0; i < target.length && i < base.length; i++){if(target[i].equals(base[i])){
common += target[i]+ pathSeparator;
commonIndex++;}elsebreak;}if(commonIndex ==0){// Whoops -- not even a single common path element. This most// likely indicates differing drive letters, like C: and D:. // These paths cannot be relativized. Return the target path.return targetPath;// This should never happen when all absolute paths// begin with / as in *nix. }String relative ="";if(base.length == commonIndex){// Comment this out if you prefer that a relative path not start with ./
relative ="."+ pathSeparator;}else{int numDirsUp = base.length - commonIndex -(isDir?0:1);/* only subtract 1 if it is a file. */// The number of directories we have to backtrack is the length of // the base path MINUS the number of common path elements, minus// one because the last element in the path isn't a directory.for(int i =1; i <=(numDirsUp); i++){
relative +=".."+ pathSeparator;}}//if we are comparing directories then we if(targetPath.length()> common.length()){//it's OK, it isn't a directory
relative += targetPath.substring(common.length());}return relative;}
Saya berasumsi Anda memiliki fromPath (jalur absolut untuk folder), dan toPath (jalur absolut untuk folder / file), dan Anda sedang mencari jalur yang dengan mewakili file / folder di toPath sebagai jalur relatif from fromath (direktori kerja Anda saat ini adalah fromPath ) maka sesuatu seperti ini seharusnya berfungsi:
publicstaticString getRelativePath(String fromPath,String toPath){// This weirdness is because a separator of '/' messes with String.split()String regexCharacter =File.separator;if(File.separatorChar =='\\'){
regexCharacter ="\\\\";}String[] fromSplit = fromPath.split(regexCharacter);String[] toSplit = toPath.split(regexCharacter);// Find the common pathint common =0;while(fromSplit[common].equals(toSplit[common])){
common++;}StringBuffer result =newStringBuffer(".");// Work your way up the FROM path to common groundfor(int i = common; i < fromSplit.length; i++){
result.append(File.separatorChar).append("..");}// Work your way down the TO pathfor(int i = common; i < toSplit.length; i++){
result.append(File.separatorChar).append(toSplit[i]);}return result.toString();}
Sudah banyak jawaban di sini, tetapi saya menemukan mereka tidak menangani semua kasus, seperti basis dan target sama. Fungsi ini mengambil direktori basis dan jalur target dan mengembalikan jalur relatif. Jika tidak ada jalur relatif, jalur target dikembalikan. File.separator tidak perlu.
publicstaticString getRelativePath (String baseDir,String targetPath){String[] base = baseDir.replace('\\','/').split("\\/");
targetPath = targetPath.replace('\\','/');String[] target = targetPath.split("\\/");// Count common elements and their length.int commonCount =0, commonLength =0, maxCount =Math.min(target.length, base.length);while(commonCount < maxCount){String targetElement = target[commonCount];if(!targetElement.equals(base[commonCount]))break;
commonCount++;
commonLength += targetElement.length()+1;// Directory name length plus slash.}if(commonCount ==0)return targetPath;// No common path element.int targetLength = targetPath.length();int dirsUp = base.length - commonCount;StringBuffer relative =newStringBuffer(dirsUp *3+ targetLength - commonLength +1);for(int i =0; i < dirsUp; i++)
relative.append("../");if(commonLength < targetLength) relative.append(targetPath.substring(commonLength));return relative.toString();}
Di sini metode yang menyelesaikan jalur relatif dari jalur dasar tanpa memperhatikan mereka berada di root yang sama atau berbeda:
publicstaticStringGetRelativePath(String path,String base){finalString SEP ="/";// if base is not a directory -> return emptyif(!base.endsWith(SEP)){return"";}// check if path is a file -> remove last "/" at the end of the methodboolean isfile =!path.endsWith(SEP);// get URIs and split them by using the separatorString a ="";String b ="";try{
a =newFile(base).getCanonicalFile().toURI().getPath();
b =newFile(path).getCanonicalFile().toURI().getPath();}catch(IOException e){
e.printStackTrace();}String[] basePaths = a.split(SEP);String[] otherPaths = b.split(SEP);// check common partint n =0;for(; n < basePaths.length && n < otherPaths.length; n ++){if( basePaths[n].equals(otherPaths[n])==false)break;}// compose the new pathStringBuffer tmp =newStringBuffer("");for(int m = n; m < basePaths.length; m ++)
tmp.append(".."+SEP);for(int m = n; m < otherPaths.length; m ++){
tmp.append(otherPaths[m]);
tmp.append(SEP);}// get path stringString result = tmp.toString();// remove last "/" if path is a fileif(isfile && result.endsWith(SEP)){
result = result.substring(0,result.length()-1);}return result;}
Temukan jalur umum terbesar dengan mengulangi melalui hasil string split (sehingga Anda akan berakhir dengan "/ var / data" atau "/ a" dalam dua contoh Anda)
Jawaban:
Ini sedikit bundaran, tetapi mengapa tidak menggunakan URI? Ini memiliki metode relativize yang melakukan semua pemeriksaan yang diperlukan untuk Anda.
Harap dicatat bahwa untuk jalur file sudah ada
java.nio.file.Path#relativize
sejak Java 1.7, seperti yang ditunjukkan oleh @Jirka Meluzin di jawaban lain .sumber
java.nio.file.Path#relativize(Path)
, itu hanya berfungsi dengan titik ganda induk dan semuanya.toPath()
bukantoURI()
. Ini sangat bisa membuat barang-barang seperti"..\.."
. Tetapi waspadaijava.lang.IllegalArgumentException: 'other' has different root
pengecualian ketika meminta jalur relatif dari"C:\temp"
ke"D:\temp"
.Karena Java 7 Anda dapat menggunakan metode relativize :
Keluaran:
sumber
..
jika perlu (benar).java.nio.file
:(pathBase.normalize().relativize(pathAbsolute);
sebagai aturan umum.Pada saat penulisan (Juni 2010), ini adalah satu-satunya solusi yang lulus ujian saya. Saya tidak dapat menjamin bahwa solusi ini bebas bug, tetapi tidak lulus uji kasus yang disertakan. Metode dan tes yang saya tulis tergantung pada
FilenameUtils
kelas dari Apache commons IO .Solusinya diuji dengan Java 1.4. Jika Anda menggunakan Java 1.5 (atau lebih tinggi) Anda harus mempertimbangkan untuk mengganti
StringBuffer
denganStringBuilder
(jika Anda masih menggunakan Java 1.4, Anda harus mempertimbangkan penggantian majikan).Kasus uji yang lolos adalah
sumber
Saat menggunakan java.net.URI.relativize Anda harus mewaspadai bug Java: JDK-6226081 (URI harus dapat merelatifkan path dengan root parsial)
Yang pada dasarnya berarti
java.net.URI.relativize
tidak akan membuat ".." untuk Anda.sumber
URIUtils.resolve()
menyebutkan JDK-4708535. Dan dari kode sumber, saya tidak melihat apa pun yang terkait dengan penelusuran ulang (yaitu..
segmen). Apakah Anda membingungkan kedua bug?Di Java 7 dan yang lebih baru, Anda cukup menggunakan (dan berbeda dengan
URI
itu, bebas bug):sumber
Bug yang dimaksud dalam jawaban lain diatasi oleh URIUtils di Apache HttpComponents
sumber
Jika Anda tahu string kedua adalah bagian dari yang pertama:
atau jika Anda benar-benar menginginkan periode di awal seperti pada contoh Anda:
sumber
Rekursi menghasilkan solusi yang lebih kecil. Ini melempar pengecualian jika hasilnya tidak mungkin (misalnya disk Windows berbeda) atau tidak praktis (root hanya direktori umum.)
sumber
Berikut ini solusi gratis perpustakaan lainnya:
Keluaran
[EDIT] sebenarnya ini menghasilkan lebih banyak .. \ karena sumbernya adalah file bukan direktori. Solusi yang tepat untuk kasus saya adalah:
sumber
Versi saya secara longgar didasarkan pada versi Matt dan Steve :
sumber
"/".length()
Anda harus menggunakan separator.lengthSolusi Matt B membuat jumlah direktori untuk mundur dengan salah - itu harus menjadi panjang jalur dasar dikurangi jumlah elemen jalur umum, minus satu (untuk elemen jalur terakhir, yang dapat berupa nama file atau trailing yang
""
dihasilkan olehsplit
) . Itu terjadi untuk bekerja dengan/a/b/c/
dan/a/x/y/
, tetapi mengganti argumen dengan/m/n/o/a/b/c/
dan/m/n/o/a/x/y/
dan Anda akan melihat masalahnya.Juga, ini membutuhkan bagian
else break
dalam pertama untuk loop, atau itu akan salah jalan yang kebetulan memiliki nama direktori yang cocok, seperti/a/b/c/d/
dan/x/y/c/z
- yangc
ada di slot yang sama di kedua array, tetapi bukan pertandingan yang sebenarnya.Semua solusi ini tidak memiliki kemampuan untuk menangani jalur yang tidak dapat direlatifikasi satu sama lain karena mereka memiliki akar yang tidak kompatibel, seperti
C:\foo\bar
danD:\baz\quux
. Mungkin hanya masalah pada Windows, tetapi patut dicatat.Saya menghabiskan jauh lebih lama untuk ini daripada yang saya maksudkan, tapi tidak apa-apa. Saya benar-benar membutuhkan ini untuk bekerja, jadi terima kasih kepada semua orang yang telah ikut serta, dan saya yakin akan ada koreksi pada versi ini juga!
Dan berikut ini adalah tes untuk mencakup beberapa kasus:
sumber
Sebenarnya jawaban saya yang lain tidak berfungsi jika jalur target bukan anak dari jalur dasar.
Ini seharusnya bekerja.
sumber
Keren!! Saya perlu sedikit kode seperti ini tetapi untuk membandingkan jalur direktori pada mesin Linux. Saya menemukan bahwa ini tidak berfungsi dalam situasi di mana direktori induk adalah targetnya.
Berikut adalah versi metode yang ramah direktori:
sumber
Saya berasumsi Anda memiliki fromPath (jalur absolut untuk folder), dan toPath (jalur absolut untuk folder / file), dan Anda sedang mencari jalur yang dengan mewakili file / folder di toPath sebagai jalur relatif from fromath (direktori kerja Anda saat ini adalah fromPath ) maka sesuatu seperti ini seharusnya berfungsi:
sumber
Sudah banyak jawaban di sini, tetapi saya menemukan mereka tidak menangani semua kasus, seperti basis dan target sama. Fungsi ini mengambil direktori basis dan jalur target dan mengembalikan jalur relatif. Jika tidak ada jalur relatif, jalur target dikembalikan. File.separator tidak perlu.
sumber
Di sini metode yang menyelesaikan jalur relatif dari jalur dasar tanpa memperhatikan mereka berada di root yang sama atau berbeda:
sumber
Lulus tes Dónal, satu-satunya perubahan - jika tidak ada root yang sama, ia mengembalikan jalur target (mungkin sudah relatif)
sumber
Jika Anda menulis plugin Maven, Anda dapat menggunakan Plexus '
PathTool
:sumber
Jika Paths tidak tersedia untuk JRE 1.5 runtime atau plugin pakar
sumber
org.apache.ant memiliki kelas FileUtils dengan metode getRelativePath. Belum mencobanya sendiri, tetapi bisa bermanfaat untuk memeriksanya.
http://javadoc.haefelinger.it/org.apache.ant/1.7.1/org/apache/tools/ant/util/FileUtils.html#getRelativePath(java.io.File , java.io.File)
sumber
sumber
Kode psuedo:
return "." + whicheverPathIsLonger.substring(commonPath.length);
sumber