CodeGym /Blog Java /Random-PL /Pliki Java, ścieżka
Autor
Andrey Gorkovenko
Frontend Engineer at NFON AG

Pliki Java, ścieżka

Opublikowano w grupie Random-PL
Cześć! Dzisiaj porozmawiamy o pracy z plikami i katalogami. Wiesz już, jak zarządzać zawartością plików: poświęciliśmy temu wiele lekcji :) Myślę, że z łatwością pamiętasz kilka klas używanych do tych celów. W dzisiejszej lekcji omówimy konkretnie zarządzanie plikami: tworzenie, zmianę nazwy itp. Przed Javą 7 wszystkie tego typu operacje były wykonywane przy użyciu klasy File . Możesz o tym przeczytać tutaj . Ale w Javie 7 twórcy języka postanowili zmienić sposób, w jaki pracujemy z plikami i katalogami. Stało się tak, ponieważ klasa File miała kilka wad. Na przykład nie miał metody copy() , która pozwalałaby skopiować plik z jednej lokalizacji do drugiej (pozornie niezbędna umiejętność). PonadtoKlasa File miała sporo metod, które zwracały wartości logiczne . Gdy wystąpi błąd, taka metoda zwraca wartość false. Nie rzuca wyjątku, przez co bardzo trudno jest zidentyfikować błędy i zdiagnozować ich przyczyny. W miejsce pojedynczej klasy File pojawiły się 3 klasy: Paths , Path i Files . Cóż, aby być precyzyjnym, Path jest interfejsem, a nie klasą. Zastanówmy się, czym się od siebie różnią i dlaczego potrzebujemy każdego z nich. Zacznijmy od najprostszego: Paths .

Ścieżki

Paths to bardzo prosta klasa z jedną metodą statyczną: get() . Został stworzony wyłącznie w celu pobrania obiektu Path z przekazanego ciągu znaków lub identyfikatora URI. Nie posiada żadnej innej funkcjonalności. Oto przykład tego w pracy:

import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {

   public static void main(String[] args) {

       Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
   }
}
Nie jest to najbardziej złożona klasa, prawda? :) Cóż, mamy też ten typ ścieżki . Dowiedzmy się, czym jest Path i dlaczego jest potrzebny :)

Ścieżka

Path , ogólnie rzecz biorąc, jest przeprojektowanym odpowiednikiem klasy File . Jest o wiele łatwiejszy w obsłudze niż File . Po pierwsze , wiele metod użytkowych (statycznych) zostało usuniętych i przeniesionych do klasy Files . Po drugie , narzucono kolejność zwracanych wartości metod interfejsu Path . W klasie File metody zwracały String , boolean lub File . Nie było łatwo to rozgryźć. Na przykład istniała metoda getParent() , która zwracała ciąg reprezentujący ścieżkę nadrzędną bieżącego pliku. Ale był też AgetParentFile() , która zwróciła to samo, ale w postaci obiektu File ! Jest to wyraźnie zbędne. W związku z tym winterfejsie Path metoda getParent() i inne metody pracy z plikami po prostu zwracają obiekt Path . Brak stosu opcji — wszystko jest łatwe i proste. Jakie przydatne metody ma Path ? Oto niektóre z nich i przykłady ich działania:
  • getFileName() : zwraca nazwę pliku ze ścieżki;

  • getParent() : zwraca katalog „nadrzędny” bieżącej ścieżki (innymi słowy katalog znajdujący się bezpośrednio powyżej w drzewie katalogów);

  • getRoot() : zwraca katalog „główny”, tj. katalog na górze drzewa katalogów;

  • zaczyna się od() , kończy się z() : sprawdza, czy ścieżka zaczyna się/kończy przekazaną ścieżką:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           Path fileName = testFilePath.getFileName();
           System.out.println(fileName);
    
           Path parent = testFilePath.getParent();
           System.out.println(parent);
    
           Path root = testFilePath.getRoot();
           System.out.println(root);
    
           boolean endWithTxt = testFilePath.endsWith("Desktop\\testFile.txt");
           System.out.println(endWithTxt);
    
           boolean startsWithLalala = testFilePath.startsWith("lalalala");
           System.out.println(startsWithLalala);
       }
    }
    

    Wyjście konsoli:

    
    testFile.txt
    C:\Users\Username\Desktop
    C:\
    true
    false
    

    Zwróć uwagę, jak działa metoda endWith() . Sprawdza, czy bieżąca ścieżka kończy się przekazaną ścieżką . W szczególności, czy znajduje się w ścieżce , a nie w przekazanym łańcuchu .

    Porównaj wyniki tych dwóch wezwań:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath.endsWith("estFile.txt"));
           System.out.println(testFilePath.endsWith("Desktop\\testFile.txt"));
       }
    }
    

    Wyjście konsoli:

    
    false
    true
    

    Do metody endWith() należy przekazać prawdziwą ścieżkę, a nie tylko zestaw znaków: w przeciwnym razie wynik zawsze będzie fałszywy, nawet jeśli bieżąca ścieżka naprawdę kończy się na tej sekwencji znaków (jak w przypadku „estFile.txt " w powyższym przykładzie).

    Ponadto Path posiada grupę metod, które upraszczają pracę ze ścieżkami bezwzględnymi (pełnymi) i względnymi .

Przyjrzyjmy się tym metodom:
  • boolean isAbsolute() zwraca wartość true, jeśli bieżąca ścieżka jest bezwzględna:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath.isAbsolute());
       }
    }
    

    Wyjście konsoli:

    
    true
    
  • Path normalize() : „normalizuje” bieżącą ścieżkę, usuwając z niej niepotrzebne elementy. Być może wiesz, że w popularnych systemach operacyjnych symbole „.” (bieżący katalog) i „..” (katalog nadrzędny) są często używane do wyznaczania ścieżek. Na przykład „ ./Pictures/dog.jpg ” oznacza, że ​​bieżący katalog zawiera folder „Pictures”, który z kolei zawiera plik „dog.jpg”.

    Popatrz tutaj. Jeśli ścieżka używająca „.” lub „..” pojawi się w twoim programie, metoda normalize() usunie je i utworzy ścieżkę, która ich nie zawiera:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
          
           Path path5 = Paths.get("C:\\Users\\Java\\.\\examples");
          
           System.out.println(path5.normalize());
          
           Path path6 = Paths.get("C:\\Users\\Java\\..\\examples");
           System.out.println(path6.normalize());
       }
    }
    

    Wyjście konsoli:

    
    C:\Users\Java\examples
    C:\Users\examples
    
  • Path relativize() : oblicza względną ścieżkę między bieżącą a przekazaną ścieżką.

    Na przykład:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath1 = Paths.get("C:\\Users\\Users\\Users\\Users");
           Path testFilePath2 = Paths.get("C:\\Users\\Users\\Users\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath1.relativize(testFilePath2));
       }
    }
    

    Wyjście konsoli:

    
    Username\Desktop\testFile.txt
    

Pełna lista metod Path jest dość długa. Wszystkie można znaleźć w dokumentacji Oracle . Teraz przejdziemy do rozważenia Files .

Akta

Files to klasa narzędziowa, która zawiera metody statyczne wyjęte z klasy File . Pliki są porównywalne z tablicami lub kolekcjami . Różnica polega na tym, że działa z plikami, a nie z tablicami czy kolekcjami :) Skupia się na zarządzaniu plikami i katalogami. Korzystając ze statycznych metod klasy Files , możemy tworzyć, usuwać i przenosić pliki i katalogi. Te operacje są wykonywane przy użyciu metod createFile() (dla katalogów, createDirectory() ), move() i delete() . Oto jak z nich korzystać:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create a file
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("Was the file created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // Create a directory
       Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));

       // Move the file from the desktop to the testDirectory directory. When you move a folder, you need to indicate its name in the folder!
       testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Has our file been moved to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));

       // Delete a file
       Files.delete(testFile1);
       System.out.println("Does the file still exist?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
   }
}
Tutaj najpierw tworzymy plik ( metoda Files.createFile() ) na pulpicie. Następnie tworzymy folder w tej samej lokalizacji ( metoda Files.createDirectory() ). Następnie przenosimy plik ( metoda Files.move() ) z pulpitu do tego nowego folderu, a na koniec usuwamy plik ( metoda Files.delete() ). Wyjście konsoli:

Was the file created successfully? 
true 
Was the directory created successfully? 
true
Did our file remain on the desktop? 
false 
Has our file been moved to testDirectory? 
true 
Does the file still exist? 
false
Notatka:podobnie jak metody interfejsu Path, wiele metod klasy FileszwracaPath obiekt. Większość metod klasy Filesprzyjmuje również Pathobiekty jako dane wejściowe. Tutaj Paths.get()metoda będzie twoim wiernym pomocnikiem — zrób z niej dobry użytek. Co jeszcze ciekawego Files? FileTo, czego naprawdę brakowało starej klasie, to copy()metoda! Mówiliśmy o tym na początku tej lekcji. Teraz nadszedł czas, aby go poznać!

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create a file
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("Was the file created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // Create a directory
       Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));

       // Copy the file from the desktop to the testDirectory2 directory.
       testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Was our file copied to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
   }
}
Wyjście konsoli:

Was the file created successfully? 
true 
Was the directory created successfully? 
true 
Did our file remain on the desktop? 
true 
Was our file copied to testDirectory? 
true
Teraz wiesz, jak programowo kopiować pliki! :) Oczywiście Filesklasa pozwala nie tylko zarządzać samym plikiem, ale także pracować z jego zawartością. Ma write()metodę zapisywania danych do pliku i wszystkie 3 metody odczytu danych: read(), readAllBytes(), i readAllLines() Zajmiemy się szczegółowo ostatnią. Dlaczego ten? Ponieważ ma bardzo interesujący typ zwrotu: List<String>! Oznacza to, że zwraca nam listę wszystkich linii w pliku. Oczywiście sprawia to, że praca z zawartością pliku jest bardzo wygodna, ponieważ cały plik, linia po linii, można np. wyświetlić na konsoli za pomocą zwykłej pętli for:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

   public static void main(String[] args) throws IOException {

       List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);

       for (String s: lines) {
           System.out.println(s);
       }
   }
}
Wyjście konsoli:

I still recall the wondrous moment: 
When you appeared before my sight, 
As though a brief and fleeting omen, 
Pure phantom in enchanting light.
Bardzo wygodne! :) Ta umiejętność pojawiła się w Javie 7. Stream API pojawił się w Javie 8. Dodaje do Javy pewne elementy programowania funkcyjnego. W tym bogatsze możliwości obsługi plików. Wyobraź sobie, że mamy następujące zadanie: znaleźć wszystkie wiersze zaczynające się od słowa „As”, zamienić je na WIELKIE LITERY i wyświetlić na konsoli. Jak wyglądałoby rozwiązanie wykorzystujące Filesklasę w Javie 7? Coś takiego:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

   public static void main(String[] args) throws IOException {

       List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);

       List<String> result = new ArrayList<>();

       for (String s: lines) {
           if (s.startsWith("As")) {
               String upper = s.toUpperCase();
               result.add(upper);
           }
       }

       for (String s: result) {
           System.out.println(s);
       }
   }
}
Wyjście konsoli:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
Misja wykonana, ale nie sądzisz, że jak na tak proste zadanie nasz kod okazał się trochę... rozwlekły? Korzystając z Stream API Java 8, rozwiązanie wygląda znacznie bardziej elegancko:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

   public static void main(String[] args) throws IOException {

       Stream<String> stream = Files.lines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"));

       List<String> result  = stream
               .filter(line -> line.startsWith("As"))
               .map(String::toUpperCase)
               .collect(Collectors.toList());
       result.forEach(System.out::println);
   }
}
Osiągnęliśmy ten sam wynik, ale przy użyciu znacznie mniejszej ilości kodu! Co więcej, nikt nie może powiedzieć, że straciliśmy „czytelność”. Myślę, że możesz łatwo komentować, co robi ten kod, nawet bez znajomości Stream API. W skrócie Strumień to sekwencja elementów, na których można wykonywać różne operacje. Otrzymujemy obiekt Stream z Files.lines()metody, a następnie stosujemy do niego 3 funkcje:
  1. Używamy filter()metody, aby wybrać tylko te linie z pliku, które zaczynają się od „As”.

  2. Przechodzimy przez wszystkie wybrane linie za pomocą map()metody i zamieniamy każdą z nich na WIELKIE LITERY.

  3. Używamy tej collect()metody, aby zebrać wszystkie otrzymane linie w pliku List.

Otrzymujemy ten sam wynik:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
Wróćmy teraz do naszego chleba powszedniego, czyli plików :) Ostatnią możliwością, którą dziś omówimy, jest przechodzenie przez drzewo plików . W nowoczesnych systemach operacyjnych struktura plików najczęściej wygląda jak drzewo: ma korzeń i są gałęzie, które mogą mieć inne gałęzie itp. Korzeń i gałęzie to katalogi. Na przykład katalog „ С:// ” może być katalogiem głównym. Zawiera dwie gałęzie: „ C://Downloads ” i „ C://Users ”. Każda z tych gałęzi ma dwie gałęzie: „ C://Downloads/Pictures ”, „ C://Downloads/Video ”, „ C://Users/JohnSmith ”, „ C://Users/Pudge2005". A te gałęzie z kolei mają inne gałęzie itp. i dlatego nazywamy to drzewem. W systemie Linux struktura jest podobna, ale katalog / jest katalogiem głównym. Teraz wyobraźPliki, ścieżka - 2 sobie, że musimy zacząć od katalogu głównego , przejrzyj wszystkie jego foldery i podfoldery i znajdź pliki, które mają określoną zawartość. Wyszukamy pliki zawierające wiersz „To jest ten plik, którego potrzebujemy!” Weźmiemy folder „testFolder”, który znajduje się na na pulpicie, jako katalog główny. Oto jego zawartość: Pliki, ścieżka - 3Foldery level1-a i level1-b zawierają również foldery: Pliki, ścieżka - 4Pliki, ścieżka - 5W tych „folderach drugiego poziomu” nie ma folderów, tylko pojedyncze pliki: Pliki, ścieżka - 6Pliki, ścieżka - 7Trzem plikom z potrzebną nam zawartością celowo nadano objaśniające nazwy: FileWeNeed1.txt, FileWeNeed2.txt, FileWeNeed3.txt. Są to dokładnie pliki, które musimy znaleźć za pomocą Javy. Jak to zrobić? Z pomocą przychodzi nam bardzo wydajna metoda przechodzenia przez drzewo plików: Files.walkFileTree (). Oto, co musimy zrobić. Po pierwsze potrzebujemy ok FileVisitor. FileVisitorto specjalny interfejs, w którym opisane są metody przechodzenia przez drzewo plików. W szczególności tam umieścimy logikę odczytywania zawartości pliku i sprawdzania, czy zawiera on potrzebny nam tekst. Oto jak FileVisitorwygląda nasz:

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;

public class MyFileVisitor extends SimpleFileVisitor<Path> {

   @Override
   public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

       List<String> lines = Files.readAllLines(file);
       for (String s: lines) {
           if (s.contains("This is the file we need")) {
               System.out.println("We found a file we need!");
               System.out.println(file.toAbsolutePath());
               break;
           }
       }

       return FileVisitResult.CONTINUE;
   }
}
W tym przypadku nasza klasa dziedziczy SimpleFileVisitor. Jest to klasa implementująca FileVisitor, w której musimy przesłonić tylko jedną metodę: visitFile(). Tutaj określamy, co należy zrobić z każdym plikiem w każdym katalogu. Jeśli potrzebujesz bardziej złożonej logiki do przechodzenia przez strukturę plików, powinieneś napisać własną implementację FileVisitor. Będziesz musiał zaimplementować 3 dodatkowe metody w tej klasie:
  • preVisitDirectory(): logika do wykonania przed wejściem do folderu;

  • visitFileFailed(): logika do wykonania, jeśli nie można odwiedzić pliku (brak dostępu lub z innych powodów);

  • postVisitDirectory(): logika do wykonania po wejściu do folderu.

Nie potrzebujemy żadnej takiej logiki, więc nie mamy nic przeciwko SimpleFileVisitor. Logika wewnątrz visitFile()metody jest dość prosta: przeczytaj wszystkie linie w pliku, sprawdź, czy zawierają one potrzebną nam treść, a jeśli tak, wypisz ścieżkę bezwzględną na konsoli. Jedyną linią, która może sprawiać ci trudności, jest ta:

return FileVisitResult.CONTINUE;
Właściwie to jest bardzo proste. Tutaj po prostu opisujemy, co program powinien zrobić po odwiedzeniu pliku i wykonaniu wszystkich niezbędnych operacji. W naszym przypadku chcemy kontynuować przemierzanie drzewa, więc wybieramy opcję CONTINUE. Ale alternatywnie możemy mieć inny cel: zamiast znajdować wszystkie pliki zawierające komunikat „To jest plik, którego potrzebujemy”, znajdź tylko jeden taki plik . Po tym program powinien się zakończyć. W tym przypadku nasz kod wyglądałby dokładnie tak samo, ale zamiast break byłoby:

return FileVisitResult.TERMINATE;
Cóż, uruchommy nasz kod i sprawdźmy, czy działa.

import java.io.IOException;
import java.nio.file.*;

public class Main {

   public static void main(String[] args) throws IOException {

       Files.walkFileTree(Paths.get("C:\\Users\\Username\\Desktop\\testFolder"), new MyFileVisitor());
   }
}
Wyjście konsoli:

We found a file we need! 
C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-a\level2-a-a\FileWeNeed2.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-b\level2-b-b\FileWeNeed3.txt
Doskonały! Zadziałało! :) Możesz także przyjąć to małe wyzwanie: zastąpić SimpleFileVisitorzwykłym FileVisitor, zastąpić wszystkie 4 metody i wymyślić własny cel programu. Na przykład możesz napisać program, który rejestruje wszystkie swoje działania: wyświetla nazwę pliku lub folderu przed lub po ich wprowadzeniu. To wszystko na teraz. Do zobaczenia wkrótce! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION