File. Про його роботу ти можеш прочитати тут. Однак у Java 7 розробники мови вирішили змінити підхід до роботи з файлами та каталогами. Причиною стало те, що клас File мав низку недоліків. Наприклад, у ньому не було методу copy(), який дозволяв би скопіювати файл з одного місця в інше (здавалося б, цілком необхідна функція).
Крім того, у класі File було чимало методів, які повертали значення типу boolean. У разі помилки такий метод повертав false, а не викидав виняток, що значно ускладнювало діагностику помилок і пошук їхніх причин. Замість одного класу File з'явилися одразу три: Paths, Path і Files. Якщо бути точнішими, Path – це інтерфейс, а не клас.
Давай розберемося, чим вони відрізняються один від одного і для чого потрібен кожен із них. Почнемо з найпростішого – Paths.Paths
Paths – це дуже простий клас, який містить лише один статичний метод – get(). Його створили виключно для того, щоб із переданого рядка або URI отримати об'єкт типу Path.
Іншої функціональності цей клас не має. Ось приклад його використання:
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, давай розберемося, що це за Path і для чого він потрібний.Path
Path – це, по суті, оновлений аналог класу File. Працювати з ним значно простіше, ніж із File.
По-перше, з нього прибрали багато службових (статичних) методів і перенесли їх до класу Files.
По-друге, в Path упорядкували типи значень, які повертають методи. У класі File методи могли повертати String, boolean або File, через що працювати з ними було не надто зручно.
Наприклад, існував метод getParent(), який повертав батьківський шлях для поточного файлу у вигляді рядка. Але водночас був метод getParentFile(), який повертав те саме, але вже у вигляді об'єкта File!
Очевидно, це було зайвим. Тому в інтерфейсі Path метод getParent() та інші методи для роботи з файлами повертають лише об'єкт Path. Жодної плутанини – усе просто й логічно.
Які ж корисні методи є в Path?
Ось деякі з них і приклади їх використання:
getFileName()– повертає ім'я файлу із шляху;getParent()– повертає батьківський каталог для поточного шляху (тобто каталог, який розташований на один рівень вище в дереві каталогів);getRoot()– повертає кореневий каталог, тобто каталог, що знаходиться на вершині дерева каталогів;startsWith(),endsWith()– перевіряють, чи починається або закінчується шлях переданим шляхом: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); } }Виведення в консоль:
testFile.txt
C:\Users\Username\Desktop
C:\
true
falseЗверни увагу на те, як працює метод
endsWith(). Він перевіряє, чи закінчується поточний шлях переданим шляхом. Саме шляхом, а не набором символів.Порівняй результати цих двох викликів:
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")); } }Виведення в консоль:
false
trueУ метод
endsWith()потрібно передавати саме повний шлях, а не просто набір символів. Інакше результатом завжди буде false, навіть якщо поточний шлях справді закінчується такою послідовністю символів (як у випадку з «estFile.txt» у прикладі вище).Крім того, у
Pathє група методів, яка спрощує роботу з абсолютними (повними) та відносними шляхами.
-
boolean isAbsolute()– повертає true, якщо поточний шлях є абсолютним: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()); } }Виведення в консоль:
true
-
Path normalize()– «нормалізує» поточний шлях, видаляючи з нього зайві елементи. Можливо, ти знаєш, що в популярних операційних системах під час запису шляхів часто використовуються символи.(поточний каталог) і..(батьківський каталог). Наприклад: ./Pictures/dog.jpg означає, що в каталозі, у якому ми зараз перебуваємо, є папкаPictures, а в ній – файлdog.jpg.Якщо у твоїй програмі з'явився шлях, який використовує
.або.., методnormalize()дозволить прибрати їх і отримати шлях без цих елементів: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()); } }Виведення в консоль:
C:\Users\Java\examples
C:\Users\examples -
Path relativize()– обчислює відносний шлях між поточним і переданим шляхами.Наприклад:
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)); } }Виведення в консоль:
Username\Desktop\testFile.txt
Path досить великий. Ознайомитися з ними можна в документації Oracle.
А ми перейдемо до розгляду Files.Files
Files – це службовий клас, до якого були перенесені статичні методи з класу File. Files можна порівняти з класами Arrays або Collections, тільки він працює не з масивами чи колекціями, а з файлами.
Основне призначення цього класу – керування файлами та каталогами. Використовуючи статичні методи Files, ми можемо створювати, видаляти й переміщувати файли та каталоги.
Для цього використовуються методи createFile() (для каталогів – createDirectory()), move() і delete().
Ось приклад їх використання:
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 {
// створення файлу
Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
System.out.println("Чи вдалося успішно створити файл?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));
// створення каталогу
Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
System.out.println("Чи вдалося успішно створити каталог?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));
// переміщуємо файл із робочого столу до каталогу testDirectory.
// Під час переміщення потрібно вказати повний шлях разом з ім'ям файлу!
testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);
System.out.println("Чи залишився файл на робочому столі?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));
System.out.println("Чи було файл переміщено до testDirectory?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
// видалення файлу
Files.delete(testFile1);
System.out.println("Чи файл усе ще існує?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
}
}
Тут ми спочатку створюємо файл (метод Files.createFile()) на робочому столі, потім створюємо там само каталог (метод Files.createDirectory()). Після цього переміщуємо файл (метод Files.move()) з робочого столу до нового каталогу, а наприкінці видаляємо його (метод Files.delete()).
Виведення в консоль:
Чи вдалося успішно створити файл? true Чи вдалося успішно створити каталог? true Чи залишився файл на робочому столі? false Чи було файл переміщено до testDirectory? true Чи файл усе ще існує? false
Зверни увагу: так само, як і методи інтерфейсуPath, багато методів класу Files повертають об'єкт Path.
Більшість методів класу Files також приймають як аргумент об'єкти Path. Тут твоїм надійним помічником стане метод Paths.get() – активно ним користуйся.
Що ще цікавого є в Files? Те, чого так не вистачало старому класу File, – метод copy()! Ми згадували про нього на початку лекції, тож саме час познайомитися з ним.
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 {
// створення файлу
Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
System.out.println("Чи вдалося успішно створити файл?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));
// створення каталогу
Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
System.out.println("Чи вдалося успішно створити каталог?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));
// копіюємо файл із робочого столу до каталогу testDirectory2
testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);
System.out.println("Чи залишився файл на робочому столі?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));
System.out.println("Чи було файл скопійовано до testDirectory2?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
}
}
Виведення в консоль:
Чи вдалося успішно створити файл? true Чи вдалося успішно створити каталог? true Чи залишився файл на робочому столі? true Чи було файл скопійовано до testDirectory2? true
Тепер ти вмієш програмно копіювати файли! :) Але класFiles дозволяє не лише керувати файлами, а й працювати з їхнім вмістом.
Для запису даних у файл клас Files має метод write(), а для читання – одразу три: read(), readAllBytes() і readAllLines().
Детальніше ми зупинимося на останньому. Чому саме на ньому?
Тому що він має дуже цікавий тип значення, яке повертає, – List<String>! Тобто цей метод повертає список рядків файлу. Звісно, це робить роботу з його вмістом дуже зручною, адже весь файл можна, рядок за рядком, вивести в консоль за допомогою звичайного циклу 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);
}
}
}
Виведення в консоль:
Я пам'ятаю дивовижну мить: Переді мною з'явилася ти, Немов скороминуще видіння, Немов геній чистої краси.
Дуже зручно! :) Така можливість з'явилася ще в Java 7. У Java 8 з'явився Stream API, який додав до Java елементи функціонального програмування, зокрема значно ширші можливості для роботи з файлами. Уяви, що перед нами стоїть завдання: знайти у файлі всі рядки, які починаються зі слова «Як», перетворити їх на ВЕЛИКІ ЛІТЕРИ та вивести в консоль. Як би виглядало таке рішення з використанням класуFiles у Java 7?
Приблизно так:
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("Как")) {
String upper = s.toUpperCase();
result.add(upper);
}
}
for (String s : result) {
System.out.println(s);
}
}
}
Виведення в консоль:
КАК МИМОЛЕТНОЕ ВИДЕНЬЕ, КАК ГЕНИЙ ЧИСТОЙ КРАСОТЫ.
Ми впоралися із завданням, але чи не здається тобі, що для такого простого завдання код вийшов трохи... багатослівним? З використанням Stream API, який з'явився в Java 8, це саме завдання можна розв'язати набагато елегантніше:
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("Как"))
.map(String::toUpperCase)
.collect(Collectors.toList());
result.forEach(System.out::println);
}
}
Ми отримали той самий результат, але з набагато меншим обсягом коду! При цьому не можна сказати, що код став менш зрозумілим. Думаю, навіть не будучи знайомим зі Stream API, ти легко зможеш пояснити, що саме він робить.
Якщо коротко, Stream – це послідовність елементів, над якими можна виконувати різні операції. Ми отримуємо об'єкт Stream за допомогою методу Files.lines(), після чого застосовуємо до нього три операції:
За допомогою методу
filter()відбираємо лише ті рядки файлу, які починаються зі слова «Как».За допомогою методу
map()проходимося по всіх відібраних рядках і перетворюємо кожен із них на верхній регістр (UPPER CASE).За допомогою методу
collect()збираємо всі отримані рядки вList.
КАК МИМОЛЕТНОЕ ВИДЕНЬЕ, КАК ГЕНИЙ ЧИСТОЙ КРАСОТЫ.
Якщо тобі буде цікаво дізнатися більше про можливості цієї бібліотеки, рекомендуємо прочитати ось цю статтю. А ми повернемося до наших баранів, тобто до файлів :) Остання можливість, яку ми сьогодні розглянемо, – це обхід дерева файлів. Файлова структура в сучасних операційних системах найчастіше має вигляд дерева: у нього є корінь і гілки, від яких можуть відходити інші гілки тощо. Роль кореня та гілок виконують каталоги. Наприклад, коренем може бути каталог “С://”. Від нього відходять дві гілки: “C://Downloads” і “C://Users”. Від кожної з цих гілок відходять ще по дві гілки: “C://Downloads/Pictures”, “C://Downloads/Video”, “C://Users/JohnSmith”, “C://Users/Pudge2005”. Від цих гілок відходять інші гілки тощо – так і утворюється дерево. У Linux це виглядає приблизно так само, тільки там роль кореня виконує каталог /
Тепер уяви, що в нас є завдання: знаючи кореневий каталог, ми маємо пройтися ним, зазирнути в папки всіх рівнів і знайти в них файли з потрібним нам вмістом. Ми шукатимемо файли, що містять рядок «This is the file we need!»
Нашим кореневим каталогом буде папка «testFolder», яка лежить на робочому столі.
Усередині вона має такий вміст:
Усередині папок level1-a і level1-b теж є папки:

Усередині цих «папок другого рівня» папок уже немає, лише окремі файли:

Три файли з потрібним нам вмістом ми спеціально позначимо зрозумілими назвами – FileWeNeed1.txt, FileWeNeed2.txt, FileWeNeed3.txt.
Саме їх нам і потрібно знайти за вмістом за допомогою Java.
Як же це зробити?
На допомогу приходить дуже потужний метод для обходу дерева файлів – Files.walkFileTree().
Ось що нам потрібно зробити. По-перше, нам знадобиться FileVisitor. FileVisitor – це спеціальний інтерфейс, у якому описані всі методи для обходу дерева файлів.
Зокрема, ми помістимо туди логіку зчитування вмісту файлу та перевірки, чи містить він потрібний нам текст. Ось як виглядатиме наш 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("Потрібний файл знайдено!");
System.out.println(file.toAbsolutePath());
break;
}
}
return FileVisitResult.CONTINUE;
}
}
У цьому випадку наш клас успадковується від SimpleFileVisitor. Це клас, який реалізує інтерфейс FileVisitor, і в ньому достатньо перевизначити лише один метод – visitFile(). Саме тут ми описуємо, що потрібно робити з кожним файлом у кожному каталозі.
Якщо тобі потрібна складніша логіка обходу, варто написати власну реалізацію FileVisitor. У такому разі доведеться реалізувати ще три методи:
preVisitDirectory()– логіка, яку потрібно виконати перед входом до каталогу;visitFileFailed()– що робити, якщо не вдалося отримати доступ до файлу (через відсутність прав доступу або з інших причин);postVisitDirectory()– логіка, яку потрібно виконати після завершення обробки каталогу.
SimpleFileVisitor.
Логіка всередині методу visitFile() досить проста: прочитати всі рядки файлу, перевірити, чи містять вони потрібний нам текст, і, якщо містять, вивести абсолютний шлях до файлу в консоль.
Єдиний рядок, який може викликати в тебе запитання, – ось цей:
return FileVisitResult.CONTINUE;
Насправді тут усе просто. Ми лише вказуємо, що має робити програма після того, як файл оброблено й усі необхідні операції виконано. У нашому випадку потрібно продовжити обхід дерева файлів, тому ми повертаємо значення CONTINUE.
Але в нас могло бути й інше завдання: знайти не всі файли, що містять рядок «This is the file we need», а лише один такий файл. Після цього роботу програми потрібно завершити. Тоді наш код виглядав би майже так само, але замість break; було б:
return FileVisitResult.TERMINATE;
Що ж, давай запустимо наш код і перевіримо, чи все працює.
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());
}
}
Виведення в консоль:
Потрібний файл знайдено! C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt Потрібний файл знайдено! C:\Users\Username\Desktop\testFolder\level1-a\level2-a-a\FileWeNeed2.txt Потрібний файл знайдено! C:\Users\Username\Desktop\testFolder\level1-b\level2-b-b\FileWeNeed3.txt
Чудово, у нас усе вийшло! :) Якщо тобі хочеться дізнатися більше проwalkFileTree(), рекомендую прочитати ось цю статтю.
Також можеш виконати невелике практичне завдання: заміни SimpleFileVisitor на звичайний FileVisitor, реалізуй усі чотири методи й придумай для цієї програми власне призначення. Наприклад, можна написати програму, яка журналюватиме всі свої дії: виводитиме в консоль назву файлу або каталогу до входу в нього та після завершення його обробки.
На цьому все. До зустрічі! :)
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ