File
. Про його роботу ти можеш прочитати тут . Але в Java 7 творці мови вирішабо змінити роботу з файлуми та каталогами. Це сталося через те, що клас File
мав низку недоліків. Наприклад, у ньому не було методу copy()
, який дозволив би скопіювати файл з одного місця в інше (начебто, явно необхідна функція). Крім того, у класіFile
було досить багато методів, які повертали boolean
значення. При помилці такий метод повертає false , а чи не викидає виняток, що робить діагностику помилок і встановлення причин дуже непростим справою. Замість єдиного класу File
з'явабося цілих 3 класи: 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("Чи наш файл був скопійований у testDirectory?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
}
}
Висновок у консоль: Чи був файл успішно створено? true Чи була директорія успішно створена? true Чи залишився файл на робочому столі? true Чи був файл скопійований у testDirectory? true Тепер ти вмієш копіювати файли програмно! :) Але клас Files
дозволяє не лише керувати самими файлуми, а й працювати з його вмістом. Для запису даних у файл він має метод write()
, а читання — цілих 3: 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 деякі елементи функціонального програмування. У тому числі багатші можливості роботи з файлуми. Уяви, що ми маємо завдання: знайти у файлі всі рядки, які починаються зі слова «Як», привести їх до UPPER CASE і вивести в консоль. Як виглядало б рішення з використанням класу 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);
}
}
}
Висновок в консоль: ЯК МИМОЛІТНЕ БАЧЕННЯ, ЯК ГЕНІЙ ЧИСТОЇ КРАСИ. Ми начебто впоралися, але чи не здається тобі, що для такого простого завдання наш код вийшов трохи...багатослівним? З використанням Java 8 Stream API рішення виглядає набагато елегантнішим:
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()
, після чого застосовуємо до нього 3 функції:
-
За допомогою методу
filter()
відбираємо ті рядки з файлу, які починаються з «Як». -
Проходимося по всіх відібраних рядках за допомогою методу та наводимо кожен з них до UPPER CASE.
map()
-
Об'єднуємо всі рядки, що вийшли,
List
за допомогою методуcollect()
.
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
. Там знадобиться реалізувати ще 3 методи:
-
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-aa\FileWeNeed2.txt Потрібний файл виявлено! C:\Users\Username\Desktop\testFolder\level1-b\level2-bb\FileWeNeed3.txt Відмінно, у нас все вийшло! :) Якщо тобі хочеться дізнатися більше про walkFileTree()
, рекомендую тобі цю статтю . Також ти можеш виконати невелике завдання – замінити SimpleFileVisitor
на звичайний FileVisitor
, реалізувати всі 4 методи та придумати призначення для цієї програми. Наприклад, можна написати програму, яка логуватиме всі свої дії: виводити в консоль назву файлу або папки до/після входу до них. На цьому все – до зустрічі! :)
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ