JavaRush /Blog Java /Random-PL /Pliki Java, ścieżka

Pliki Java, ścieżka

Opublikowano w grupie Random-PL
Cześć! Dzisiaj porozmawiamy o pracy z plikami i katalogami. Już wiesz jak zarządzać zawartością plików: mieliśmy sporo zajęć temu poświęconych :) Myślę, że bez problemu możesz zapamiętać kilka klas, które są potrzebne do tych celów. W dzisiejszym wykładzie porozmawiamy konkretnie o zarządzaniu plikami - tworzeniu, zmianie nazwy itp. Przed wersją Java 7 wszystkie takie operacje były wykonywane przy użyciu pliku File. O jego twórczości można przeczytać tutaj . Jednak w Javie 7 twórcy języka postanowili zmienić sposób pracy z plikami i katalogami. Wynikało to z faktu, że klasa Filemiała wiele wad. Na przykład nie posiadał metody copy()umożliwiającej kopiowanie pliku z jednej lokalizacji do drugiej (pozornie wyraźnie potrzebna funkcja). Dodatkowo klasa Fileposiadała całkiem sporo metod zwracających booleanwartości -. W przypadku wystąpienia błędu taka metoda zwraca wartość false zamiast rzucać wyjątek, co bardzo utrudnia diagnozowanie błędów i ustalanie ich przyczyn. Zamiast jednej klasy Filepojawiły się aż 3 klasy: Paths, Pathi Files. A ściślej mówiąc, Pathjest to interfejs, a nie klasa. Zastanówmy się, czym się od siebie różnią i dlaczego każdy z nich jest potrzebny. Zacznijmy od najprostszej rzeczy – Paths.

Ścieżki

Pathsjest bardzo prostą klasą z pojedynczą metodą statyczną get(). Został stworzony wyłącznie w celu uzyskania obiektu typu z przekazanego ciągu znaków lub identyfikatora URI Path. Nie ma innej funkcjonalności. Oto przykład jego 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 najtrudniejsze zajęcia, prawda? :) Cóż, skoro mamy obiekt typu Path, zastanówmy się, co to jest Pathi dlaczego jest potrzebny :)

Ścieżka

Path, ogólnie rzecz biorąc, jest przeprojektowanym odpowiednikiem File. Dużo łatwiej jest pracować z niż z File. Po pierwsze , usunięto z niego wiele metod użytkowych (statycznych) i przeniesiono do klasy Files. Po drugiePath uporządkowano wartości zwracane metod. Na zajęciach Filemetody zwracały this String, tamto boolean, tamto File- nie było łatwo to rozgryźć. Na przykład istniała metoda getParent(), która zwracała ścieżkę nadrzędną bieżącego pliku w postaci ciągu znaków. Ale jednocześnie istniała metoda getParentFile(), która zwracała to samo, ale w postaci obiektu File! Jest to wyraźnie zbędne. Dlatego w interfejsie Pathmetoda getParent()i inne metody pracy z plikami po prostu zwracają obiekt Path. Żadnych mnóstwa opcji – wszystko jest łatwe i proste. Jakie ma przydatne metody Path? Oto niektórzy z nich i przykłady ich twórczości:
  • getFileName()— zwraca nazwę pliku ze ścieżki;

  • getParent()— zwraca katalog „nadrzędny” w odniesieniu do bieżącej ścieżki (czyli katalogu znajdującego się wyżej w drzewie katalogów);

  • getRoot()— zwraca katalog „root”; to znaczy ten, który znajduje się na górze drzewa katalogów;

  • startsWith(), endsWith()— sprawdź, czy ścieżka zaczyna/kończy się na podanej ścieżce:

    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:\Użytkownicy\Nazwa użytkownika\Pulpit
    C:\
    prawda
    Fałsz

    Zwróć uwagę na działanie metody endsWith(). Sprawdza, czy bieżąca ścieżka kończy się ścieżką przekazaną . Znajduje się na ścieżce , a nie na zestawie znaków .

    Porównaj wyniki tych dwóch rozmów:

    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:

    fałsz
    PRAWDA

    Do metody należy przekazać pełną ścieżkę endsWith(), a nie tylko zestaw znaków: w przeciwnym razie wynik zawsze będzie fałszywy , nawet jeśli bieżąca ścieżka faktycznie kończy się takim ciągiem znaków (jak w przypadku „estFile.txt ” w powyższym przykładzie).

    Dodatkowo istnieje Pathgrupa metod ułatwiających 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:

    PRAWDA

  • Path normalize()— „normalizuje” bieżącą ścieżkę, usuwając z niej niepotrzebne elementy. Być może wiesz, że popularne systemy operacyjne często używają znaków „.” do oznaczenia ścieżek. („katalog bieżący”) i „..” (katalog nadrzędny). Przykładowo: „ ./Pictures/dog.jpg ” oznacza, że ​​w katalogu w którym się aktualnie znajdujemy znajduje się folder Obrazy, a w nim plik „dog.jpg”

    Więc oto jest. Jeśli twój program ma ścieżkę używającą „.” lub „..”, metoda normalize()je usunie i otrzyma ścieżkę, która ich nie będzie 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:\Użytkownicy\Java\przykłady
    C:\Użytkownicy\przykłady

  • Path relativize()— oblicza względną ścieżkę pomiędzy bieżącą i przebytą ś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:

    Nazwa użytkownika\Pulpit\testFile.txt

Pełna lista metod Pathjest dość duża. Można je wszystkie znaleźć w dokumentacji Oracle . Przejdźmy do recenzji Files.

Akta

Files- jest to klasa narzędziowa, do której przeniesiono metody statyczne z tej klasy File. Files- to jest mniej więcej to samo co Arrayslub Collections, tyle że działa z plikami, a nie z tablicami i kolekcjami :) Skupia się na zarządzaniu plikami i katalogami. Za pomocą metod statycznych Filesmożemy tworzyć, usuwać i przenosić pliki i katalogi. Do tych operacji wykorzystywane są metody createFile()(dla katalogów - createDirectory()), move()oraz 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 {

       //tworzenie pliku
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println(„Czy plik został pomyślnie utworzony?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // Utworzyć katalog
       Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
       System.out.println(„Czy katalog został pomyślnie utworzony?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));

       //przenieś plik z pulpitu do katalogu testDirectory. Musisz przenieść z nazwą pliku w folderze!
       testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println(„Czy nasz plik pozostał na pulpicie?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println(„Czy nasz plik został przeniesiony do katalogu testDirectory?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));

       //Usuń plik
       Files.delete(testFile1);
       System.out.println(„Czy plik nadal istnieje?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
   }
}
Tutaj najpierw tworzymy plik (metoda Files.createFile()) na pulpicie, a następnie tworzymy tam folder (metoda Files.createDirectory()). Następnie przenosimy plik (metoda Files.move()) z pulpitu do tego nowego folderu, a na koniec usuwamy plik (metoda Files.delete()). Dane wyjściowe konsoli: czy plik został pomyślnie utworzony? true Czy katalog został pomyślnie utworzony? true Czy nasz plik nadal znajduje się na pulpicie? false Czy nasz plik został przeniesiony do katalogu testowego? true Czy plik nadal istnieje? FAŁSZ Zwróć uwagę:Podobnie jak metody interfejsu Path, wiele metod Fileszwraca obiektPath . Większość metod klasowych Filesakceptuje również Path. Tutaj metoda stanie się Twoim wiernym pomocnikiem Paths.get()– korzystaj z niej aktywnie. Co jeszcze jest ciekawego Files? Tym, czego naprawdę brakowało starej klasie, była metoda ! File. copy()Rozmawialiśmy o nim na początku wykładu, teraz czas 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 {

       //tworzenie pliku
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println(„Czy plik został pomyślnie utworzony?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // Utworzyć katalog
       Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
       System.out.println(„Czy katalog został pomyślnie utworzony?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));

       //skopiuj plik z pulpitu do katalogu testDirectory2.
       testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println(„Czy nasz plik pozostał na pulpicie?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println(„Czy nasz plik został skopiowany do katalogu testDirectory?”);
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
   }
}
Dane wyjściowe konsoli: czy plik został pomyślnie utworzony? true Czy katalog został pomyślnie utworzony? true Czy nasz plik nadal znajduje się na pulpicie? true Czy nasz plik został skopiowany do katalogu testowego? true Teraz możesz programowo kopiować pliki! :) Ale klasa Filespozwala nie tylko zarządzać samymi plikami, ale także pracować z ich zawartością. Aby zapisać dane do pliku, ma metodę write(), a do odczytu - aż 3:, read()i readAllBytes()omówimy readAllLines() szczegółowo ten ostatni. Dlaczego na tym? Ponieważ ma bardzo interesujący typ zwrotu - List<String>! Oznacza to, że zwraca nam listę linii w pliku. Oczywiście sprawia to, że praca z zawartością jest bardzo wygodna, ponieważ cały plik, linia po linii, można na przykład wyprowadzić do konsoli w regularnej 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 z konsoli: Pamiętam cudowną chwilę: Pojawiłeś się przede mną, Jak przelotna wizja, Jak geniusz czystego piękna. Bardzo wygodnie! :) Ta funkcja pojawiła się w Javie 7. W Javie 8 pojawił się Stream API , który dodał pewne elementy programowania funkcjonalnego do Javy. Zawiera bogatsze możliwości zarządzania plikami. Wyobraź sobie, że mamy zadanie: znaleźć wszystkie linie w pliku rozpoczynające się od słowa „Jak”, przekonwertować je na WIELKIE LITERY i wypisać na konsolę. Jak wyglądałoby rozwiązanie wykorzystujące klasę Filesw 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("Jak")) {
               String upper = s.toUpperCase();
               result.add(upper);
           }
       }

       for (String s: result) {
           System.out.println(s);
       }
   }
}
Wyjście z konsoli: JAK WIZJA NA POST, JAK GENIUSZ CZYSTEGO PIĘKNA. Wydaje się, że nam się to udało, ale czy nie sądzicie, że przy tak prostym zadaniu nasz kod okazał się trochę… rozwlekły? Dzięki Java 8 Stream API 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("Jak"))
               .map(String::toUpperCase)
               .collect(Collectors.toList());
       result.forEach(System.out::println);
   }
}
Osiągnęliśmy ten sam wynik, ale przy znacznie mniejszym kodzie! Co więcej, nie można powiedzieć, że straciliśmy na „czytelności”. Myślę, że możesz łatwo skomentować działanie tego kodu, nawet jeśli nie znasz interfejsu Stream API. Krótko mówiąc, strumień to sekwencja elementów, na których można wykonywać różne funkcje. Z metody pobieramy obiekt Stream Files.lines()i następnie stosujemy do niego 3 funkcje:
  1. Za pomocą tej metody filter()wybieramy tylko te linie z pliku, które zaczynają się od „Jak”.

  2. Przechodzimy przez wszystkie wybrane linie metodą map()i przenosimy każdą z nich na WIELKIE LITERY.

  3. Łączymy wszystkie powstałe linie, Listużywając collect().

Na wyjściu otrzymujemy ten sam rezultat: JAK WIZJA NA POST, JAK GENIUSZ CZYSTEGO PIĘKNA. Jeśli chcesz dowiedzieć się więcej o możliwościach tej biblioteki, polecamy przeczytanie tego artykułu . Wrócimy do naszych owiec, czyli plików :) Ostatnią możliwością, którą dzisiaj rozważymy, jest przejście przez drzewo plików . Struktura plików we współczesnych systemach operacyjnych najczęściej przybiera formę drzewa: ma korzeń i istnieją gałęzie, z których można oddzielić inne gałęzie itp. Katalogi pełnią rolę korzenia i gałęzi. Na przykład katalog „ C:// ” może pełnić rolę katalogu głównego . Odchodzą od niego dwie gałęzie: „ C://Downloads ” i „ C://Users ”. Z każdej z tych gałęzi istnieją jeszcze 2 gałęzie: „ C://Downloads/Pictures ”, „ C://Downloads/Video ”, „ C://Users/JohnSmith ”, „ C://Users/Pudge2005 ” . Inne gałęzie odgałęziają się od tych gałęzi itp. - tak okazuje się drzewo. W Linuksie wygląda to mniej więcej tak samo, tylko tam katalog pełni rolę katalogu głównego / Pliki, ścieżka - 2 Teraz wyobraźmy sobie, że mamy zadanie: znając katalog główny, musimy przez niego przejść, zajrzeć do folderów wszystkich poziomów i znaleźć w nich pliki z zawartością potrzebujemy. Będziemy szukać plików zawierających wiersz „To jest plik, którego potrzebujemy!” Naszym katalogiem głównym będzie folder „testFolder”, który znajduje się na pulpicie. Wewnątrz znajduje się następująca zawartość: Pliki, ścieżka - 3Wewnątrz folderów poziom 1-a i poziom 1-b znajdują się także foldery: Pliki, ścieżka - 4Pliki, ścieżka - 5Wewnątrz tych „folderów drugiego poziomu” nie ma już folderów, są tylko pojedyncze pliki: Pliki, ścieżka - 6Pliki, ścieżka - 7Specjalnie wyznaczymy 3 pliki z zawartością, którą potrzebujemy o wyraźnych nazwach - FileWeNeed1.txt , FileWeNeed2.txt, FileWeNeed3.txt To są te, które musimy znaleźć według treści za pomocą Java. Jak możemy to zrobić? Na ratunek przychodzi bardzo wydajna metoda przechodzenia przez drzewo plików - Files.walkFileTree(). Oto, co musimy zrobić. Po pierwsze, potrzebujemy FileVisitor. FileVisitorto specjalny interfejs opisujący wszystkie metody poruszania się po drzewie plików. Konkretnie umieścimy tam logikę, która odczyta zawartość pliku i sprawdzi, czy zawiera on potrzebny nam tekst. Tak będzie wyglądać nasza FileVisitor:
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(„Znaleziono wymagany plik!”);
               System.out.println(file.toAbsolutePath());
               break;
           }
       }

       return FileVisitResult.CONTINUE;
   }
}
W tym przypadku nasza klasa dziedziczy z SimpleFileVisitor. Jest to klasa implementująca FileVisitor, w której należy zastąpić tylko jedną metodę: visitFile(). Tutaj opisujemy, co należy zrobić z każdym plikiem w każdym katalogu. Jeśli potrzebujesz bardziej złożonej logiki przechodzenia, powinieneś napisać własną implementację FileVisitor. Tam będziesz musiał wdrożyć jeszcze 3 metody:
  • preVisitDirectory()— logika, którą należy wykonać przed wejściem do folderu;

  • visitFileFailed()— co zrobić, jeśli wejście do pliku jest niemożliwe (brak dostępu lub inne przyczyny);

  • postVisitDirectory()— logika, którą należy wykonać po wejściu do folderu.

Nie mamy takiej logiki, więc nam to wystarczy SimpleFileVisitor. Logika tej metody visitFile()jest dość prosta: przeczytaj wszystkie linie z pliku, sprawdź, czy zawierają one potrzebną nam treść i jeśli tak, wypisz bezwzględną ścieżkę do konsoli. Jedyną linijką, która może sprawić ci kłopoty, jest ta:
return FileVisitResult.CONTINUE;
W rzeczywistości wszystko jest proste. Tutaj po prostu opisujemy, co program powinien zrobić po wprowadzeniu pliku i wykonaniu wszystkich niezbędnych operacji. W naszym przypadku musimy kontynuować przemierzanie drzewa, dlatego wybieramy opcję CONTINUE. Ale my na przykład moglibyśmy mieć inne zadanie: znaleźć nie wszystkie pliki zawierające „To jest plik, którego potrzebujemy”, ale tylko jeden taki plik . Następnie program należy zakończyć. W tym przypadku nasz kod wyglądałby dokładnie tak samo, tyle że zamiast break; zrobiłbym:
return FileVisitResult.TERMINATE;
Cóż, uruchommy nasz kod i zobaczmy, 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());
   }
}
Dane wyjściowe konsoli: Znaleziono wymagany plik! C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt Znaleziono wymagany plik! C:\Users\Username\Desktop\testFolder\level1-a\level2-aa\FileWeNeed2.txt Znaleziono wymagany plik! C:\Users\Nazwa użytkownika\Desktop\testFolder\level1-b\level2-bb\FileWeNeed3.txt Świetnie, udało nam się! :) Jeśli chcesz dowiedzieć się więcej na temat walkFileTree(), polecam Ci ten artykuł . Możesz także wykonać małe zadanie - zastąpić je SimpleFileVisitorzwykłym FileVisitor, wdrożyć wszystkie 4 metody i wymyślić cel dla tego programu. Możesz na przykład napisać program, który będzie rejestrował wszystkie Twoje działania: wyświetlał nazwę pliku lub folderu w konsoli przed/po ich wprowadzeniu. To wszystko – do zobaczenia później! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION