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 File
miał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 File
posiadała całkiem sporo metod zwracających boolean
wartoś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 File
pojawiły się aż 3 klasy: Paths
, Path
i Files
. A ściślej mówiąc, Path
jest 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
Paths
jest 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 Path
i 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 File
metody 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 Path
metoda 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łszZwróć 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
PRAWDADo 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
Path
grupa metod ułatwiających pracę ze ścieżkami bezwzględnymi (pełnymi) i względnymi .
-
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
Path
jest 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 Arrays
lub 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 Files
moż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 Files
zwraca obiektPath
. Większość metod klasowych Files
akceptuje 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 Files
pozwala 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ę Files
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("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:
-
Za pomocą tej metody
filter()
wybieramy tylko te linie z pliku, które zaczynają się od „Jak”. -
Przechodzimy przez wszystkie wybrane linie metodą
map()
i przenosimy każdą z nich na WIELKIE LITERY. -
Łączymy wszystkie powstałe linie,
List
używająccollect()
.
Files.walkFileTree()
. Oto, co musimy zrobić. Po pierwsze, potrzebujemy FileVisitor
. FileVisitor
to 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.
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 SimpleFileVisitor
zwykł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! :)
GO TO FULL VERSION