Jak łączyć ścieżki w Javie?

350

Czy System.IO.Path.Combine()w C # / .NET istnieje odpowiednik Java ? Lub jakiś kod, aby to osiągnąć?

Ta metoda statyczna łączy jeden lub więcej ciągów w ścieżkę.

Rodrigue
źródło
1
To SO pytanie może pomóc.
Jim Blizard

Odpowiedzi:

407

Zamiast utrzymywać wszystko oparte na łańcuchach, powinieneś użyć klasy zaprojektowanej do reprezentowania ścieżki systemu plików.

Jeśli używasz Java 7 lub Java 8, powinieneś zdecydowanie rozważyć użycie java.nio.file.Path; Path.resolvemożna użyć do połączenia jednej ścieżki z drugą lub z łańcuchem. PathsKlasa pomocnika jest zbyt przydatny. Na przykład:

Path path = Paths.get("foo", "bar", "baz.txt");

Jeśli chcesz obsługiwać środowiska starsze niż Java-7, możesz użyć java.io.File:

File baseDirectory = new File("foo");
File subDirectory = new File(baseDirectory, "bar");
File fileInDirectory = new File(subDirectory, "baz.txt");

Jeśli chcesz później wrócić jako ciąg znaków, możesz zadzwonić getPath(). Rzeczywiście, jeśli naprawdę chcesz naśladować Path.Combine, możesz po prostu napisać coś takiego:

public static String combine(String path1, String path2)
{
    File file1 = new File(path1);
    File file2 = new File(file1, path2);
    return file2.getPath();
}
Jon Skeet
źródło
10
Uważaj na absolutne ścieżki. Wersja .NET zwróci path2(ignoruje path1), jeśli path2jest to ścieżka bezwzględna. Wersja Java upuści wiodący /lub \ potraktuje go jako ścieżkę względną.
finnw
23
@Matthew - ponieważ katalog jest plikiem. Jest to plik, którego zawartość definiuje dzieci tego katalogu, ich lokalizację na dysku, uprawnienia itp.
Dónal
7
@ Hugo: Więc marnuje całe dwa przedmioty ? Wstrząsający! Dla mnie wygląda całkiem czysto, szczerze mówiąc ... zachowuje logikę względnych nazw plików tam, gdzie należy, w klasie File.
Jon Skeet,
1
@modosansreves: Spójrz na File.getCanonicalPath.
Jon Skeet,
1
@SargeBorsch: C # to tylko język. Możesz łatwo stworzyć swój własny odpowiednik Filew C #, jeśli chcesz. (Zakładam, że masz na myśli istnienie Filekorzyści, z którą się zgodziłbym.)
Jon Skeet
118

W Javie 7 powinieneś użyć resolve:

Path newPath = path.resolve(childPath);

Chociaż klasa Ścieżka NIO2 może wydawać się nieco redundantna dla File z niepotrzebnie innym API, jest w rzeczywistości subtelniej bardziej elegancka i solidna.

Zauważ, że Paths.get()(jak sugeruje ktoś inny) nie ma przeciążenia biorąc Path, a robienie Paths.get(path.toString(), childPath)NIE jest tym samym resolve(). Z Paths.get()dokumentów :

Zauważ, że chociaż ta metoda jest bardzo wygodna, jej użycie będzie oznaczało domniemane odniesienie do domyślnego systemu plików i ograniczy użyteczność kodu wywołującego. Dlatego nie należy go stosować w kodzie biblioteki przeznaczonym do elastycznego ponownego wykorzystania. Bardziej elastyczną alternatywą jest użycie istniejącej instancji Path jako kotwicy, na przykład:

Path dir = ...
Path path = dir.resolve("file");

Funkcja siostrzana resolveto doskonała relativize:

Path childPath = path.relativize(newPath);
Aleksandr Dubinsky
źródło
17

Wiem, że minęło dużo czasu od oryginalnej odpowiedzi Jona, ale miałem podobny wymóg co OP.

Rozszerzając rozwiązanie Jona, wymyśliłem następujące, które zajmą jeden lub więcej segmentów ścieżki, zajmując tyle segmentów ścieżki, ile możesz na nią rzucić.

Stosowanie

Path.combine("/Users/beardtwizzle/");
Path.combine("/", "Users", "beardtwizzle");
Path.combine(new String[] { "/", "Users", "beardtwizzle", "arrayUsage" });

Kod tutaj dla innych z podobnym problemem

public class Path {
    public static String combine(String... paths)
    {
        File file = new File(paths[0]);

        for (int i = 1; i < paths.length ; i++) {
            file = new File(file, paths[i]);
        }

        return file.getPath();
    }
}
isNaN1247
źródło
12

niezależne od platformy podejście (wykorzystuje File.separator, tzn. będzie działać, zależy od systemu operacyjnego, w którym działa kod:

java.nio.file.Paths.get(".", "path", "to", "file.txt")
// relative unix path: ./path/to/file.txt
// relative windows path: .\path\to\filee.txt

java.nio.file.Paths.get("/", "path", "to", "file.txt")
// absolute unix path: /path/to/filee.txt
// windows network drive path: \\path\to\file.txt

java.nio.file.Paths.get("C:", "path", "to", "file.txt")
// absolute windows path: C:\path\to\file.txt
Maksim Kostromin
źródło
11

Aby ulepszyć odpowiedź JodaStephen, Apache Commons IO ma FilenameUtils, który to robi. Przykład (w systemie Linux):

assert org.apache.commons.io.FilenameUtils.concat("/home/bob", "work\\stuff.log") == "/home/bob/work/stuff.log"

Jest niezależny od platformy i produkuje wszelkie separatory, których potrzebuje Twój system.

alpejski
źródło
2

Oto rozwiązanie, które obsługuje wiele części ścieżki i warunki krawędzi:

public static String combinePaths(String ... paths)
{
  if ( paths.length == 0)
  {
    return "";
  }

  File combined = new File(paths[0]);

  int i = 1;
  while ( i < paths.length)
  {
    combined = new File(combined, paths[i]);
    ++i;
  }

  return combined.getPath();
}
Rachunek
źródło
2

Jeśli nie potrzebujesz więcej niż ciągów, możesz użyć com.google.common.io.Files

Files.simplifyPath("some/prefix/with//extra///slashes" + "file//name")

dostać

"some/prefix/with/extra/slashes/file/name"
Gismo Ranas
źródło
1

Może późno na imprezę, ale chciałem się z tym zgodzić. Używam wzorca konstruktora i zezwalam na wygodne appendpołączenia łańcuchowe . Można go łatwo rozszerzyć, aby obsługiwał także pracę z Pathobiektami.

public class Files  {
    public static class PathBuilder {
        private File file;

        private PathBuilder ( File root ) {
            file = root;
        }

        private PathBuilder ( String root ) {
            file = new File(root);
        }

        public PathBuilder append ( File more ) {
            file = new File(file, more.getPath()) );
            return this;
        }

        public PathBuilder append ( String more ) {
            file = new File(file, more);
            return this;
        }

        public File buildFile () {
            return file;
        }
    }

    public static PathBuilder buildPath ( File root ) {
        return new PathBuilder(root);
    }

    public static PathBuilder buildPath ( String root ) {
        return new PathBuilder(root);
    }
}

Przykład użycia:

File root = File.listRoots()[0];
String hello = "hello";
String world = "world";
String filename = "warez.lha"; 

File file = Files.buildPath(root).append(hello).append(world)
              .append(filename).buildFile();
String absolute = file.getAbsolutePath();

Wynikowy absolutebędzie zawierać coś takiego:

/hello/world/warez.lha

a może nawet:

A:\hello\world\warez.lha
Stephan Henningsen
źródło
1

Działa to również w Javie 8:

Path file = Paths.get("Some path");
file = Paths.get(file + "Some other path");
rootExplorr
źródło
0

To rozwiązanie oferuje interfejs do łączenia fragmentów ścieżek z tablicy String []. Używa java.io.File.File (String string, child String) :

    public static joinPaths(String[] fragments) {
        String emptyPath = "";
        return buildPath(emptyPath, fragments);
    }

    private static buildPath(String path, String[] fragments) {
        if (path == null || path.isEmpty()) {
            path = "";
        }

        if (fragments == null || fragments.length == 0) {
            return "";
        }

        int pathCurrentSize = path.split("/").length;
        int fragmentsLen = fragments.length;

        if (pathCurrentSize <= fragmentsLen) {
            String newPath = new File(path, fragments[pathCurrentSize - 1]).toString();
            path = buildPath(newPath, fragments);
        }

        return path;
    }

Następnie możesz po prostu zrobić:

String[] fragments = {"dir", "anotherDir/", "/filename.txt"};
String path = joinPaths(fragments);

Zwroty:

"/dir/anotherDir/filename.txt"
JackCid
źródło