una rutas ruta relativas relativa leer guardar especifica crear archivos archivo java url file path

java - leer - rutas relativas html



¿Cómo construir una ruta relativa en Java a partir de dos rutas absolutas(o URL)? (22)

Dados dos caminos absolutos, por ejemplo

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

¿Cómo se puede crear una ruta relativa que use la segunda ruta como su base? En el ejemplo anterior, el resultado debe ser: ./stuff/xyz.dat


¡¡Guay!! Necesito un poco de código como este, pero para comparar rutas de directorio en máquinas Linux. Descubrí que esto no funcionaba en situaciones en las que el directorio principal era el objetivo.

Aquí hay una versión amigable de directorio del método:

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


Al momento de escribir (junio de 2010), esta era la única solución que pasaba mis casos de prueba. No puedo garantizar que esta solución esté libre de errores, pero pasa los casos de prueba incluidos. El método y las pruebas que he escrito dependen de la clase FilenameUtils de Apache commons IO .

La solución fue probada con Java 1.4. Si está utilizando Java 1.5 (o superior), debería considerar reemplazar StringBuffer con StringBuilder (si aún está usando Java 1.4, debería considerar un cambio de empleador).

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

Los casos de prueba que esto pasa son

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


Aquí hay una solución gratuita para otra biblioteca:

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

Salidas

../../../../d/e/f2.txt

[EDITAR] en realidad da como resultado más .. / debido a que la fuente es un archivo, no un directorio. La solución correcta para mi caso es:

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


Aquí un método que resuelve una ruta relativa desde una ruta base independientemente de que estén en la misma raíz o en una raíz diferente:

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



Desde Java 7 puedes usar el método de 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); } }

Salida:

stuff/xyz.dat


El error al que se hace referencia en la respuesta de @Peter Mueller es abordado por URIUtils en Apache HttpComponents

public static URI resolve(URI baseURI, String reference)

Resuelve una referencia de URI contra un URI base. Solución para el error en java.net.URI ()


En Java 8 puedes hacer simple (y en contraste con URI , es libre de errores):

Path#relativize(Path)


En realidad, mi otra respuesta no funcionó si la ruta de destino no era un elemento secundario de la ruta base.

Esto debería funcionar.

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/", "/")); } }


Es una pequeña rotonda, pero ¿por qué no usar URI? Tiene un método de relativización que hace todas las comprobaciones necesarias para usted.

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"

Tenga en cuenta que para la ruta del archivo hay java.nio.file.Path#relativize desde Java 1.7, como lo señaló @Jirka Meluzin en la otra respuesta .


La recursión produce una solución más pequeña. Esto genera una excepción si el resultado es imposible (por ejemplo, un disco de Windows diferente) o impráctico (la raíz es solo un directorio común).

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


La solución de Matt B obtiene un número incorrecto de directorios para retroceder, debe ser la longitud de la ruta base menos el número de elementos de ruta comunes, menos uno (para el último elemento de ruta, que es un nombre de archivo o un "" final generado) por split ). Resulta que funciona con /a/b/c/ y /a/x/y/ , pero reemplaza los argumentos con /m/n/o/a/b/c/ y /m/n/o/a/x/y/ y verás el problema.

Además, necesita una else break dentro del primer bucle for, o mal manejará las rutas que tengan nombres de directorio coincidentes, como /a/b/c/d/ y /x/y/c/z - the c está en la misma ranura en ambas matrices, pero no es una coincidencia real.

Todas estas soluciones carecen de la capacidad de manejar rutas que no pueden ser relativizadas entre sí porque tienen raíces incompatibles, como C:/foo/bar y D:/baz/quux . Probablemente solo sea un problema en Windows, pero vale la pena mencionar.

Gasté mucho más tiempo en esto de lo que pretendía, pero está bien. De hecho, necesitaba esto para trabajar, así que gracias a todos los que han colaborado, ¡y estoy seguro de que también habrá correcciones en esta versión!

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

Y aquí están las pruebas para cubrir varios casos:

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


Mi versión está basada libremente en las versiones de Matt y 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()); }


Muchas respuestas ya están aquí, pero descubrí que no manejaban todos los casos, como que la base y el objetivo son los mismos. Esta función toma un directorio base y una ruta de destino y devuelve la ruta relativa. Si no existe una ruta relativa, se devuelve la ruta de destino. File.separator es innecesario.

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


Pasa las pruebas de Dónal, el único cambio: si no hay una raíz común, devuelve la ruta de destino (ya podría ser relativa)

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


Si Paths no está disponible para JRE 1.5 runtime o maven plugin

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


Si está escribiendo un complemento de Maven, puede utilizar PathTool Plexus :

import org.codehaus.plexus.util.PathTool; String relativeFilePath = PathTool.getRelativeFilePath(file1, file2);


Si sabes que la segunda cadena es parte de la primera:

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

o si realmente quieres el período al principio como en tu ejemplo:

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


Supongo que tiene fromPath (una ruta absoluta para una carpeta), y toPath (una ruta absoluta para una carpeta / archivo), y está buscando una ruta que represente el archivo / carpeta en toPath como una ruta relativa from fromPath (su directorio de trabajo actual es fromPath ) entonces algo como esto debería funcionar:

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



Psuedo-code:

  1. Dividir las cadenas por el separador de ruta ("/")
  2. Encuentre la mejor ruta común iterando a través del resultado de la cadena dividida (por lo que terminaría con "/ var / data" o "/ a" en sus dos ejemplos)
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);

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