1. Запис із перезаписом
Коли ви працюєте з файлами, важливо розуміти, що «записати у файл» — це не завжди одна й та сама операція. Іноді потрібно повністю перезаписати файл (наприклад, коли ви зберігаєте новий звіт), а іноді — додати нову інформацію у кінець уже наявного файлу (наприклад, вести лог подій застосунку). У деяких випадках потрібно просто прочитати вміст файлу, не змінюючи його.
У Java (особливо в сучасному пакеті java.nio.file) режим роботи з файлом визначається набором спеціальних опцій, які ви передаєте методам запису, наприклад, у Files.write(). Ці опції дозволяють явно вказати, чи хочете ви перезаписати файл, чи додати щось у кінець.
Як це працює
Коли ви викликаєте Files.write(path, data), Java за замовчуванням створює новий файл або перезаписує уже наявний. Увесь старий вміст файлу буде знищено, і на його місці опиняться лише нові дані.
Ця поведінка за замовчуванням — фактично «жорстке перезавантаження» файлу. Якщо у файлі раніше було 1000 рядків, а ви записали один рядок, усе, що було до того, зникне безслідно.
Приклад: запис рядка у файл
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class OverwriteFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> lines = List.of("Привіт, світ!", "Це новий запис у файлі.");
// Записуємо рядки у файл (старий вміст буде видалено)
Files.write(path, lines);
System.out.println("Файл успішно перезаписано!");
}
}
Після запуску цього коду у файлі myfile.txt залишаться лише два рядки зі списку lines. Усе, що було раніше, зникне — без жодних попереджень з боку Java.
2. Дозапис (append): додавання даних у кінець файлу
Для деяких задач (наприклад, ведення журналу подій, логування, накопичення даних) потрібно не знищувати старий вміст файлу, а додавати нові рядки у кінець. У старому API для цього часто використовували FileWriter із прапорцем append = true. У сучасному API усе робиться простіше та очевидніше.
Як це зробити
Усе, що потрібно, — передати опцію StandardOpenOption.APPEND у метод Files.write():
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class AppendFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> moreLines = List.of("Додали ще один рядок.", "І ще один!");
// Додаємо рядки у кінець файлу (старий вміст зберігається)
Files.write(path, moreLines, StandardOpenOption.APPEND);
System.out.println("Рядки додано у кінець файлу!");
}
}
Важливий момент: якщо файл не існує, під час спроби дозапису виникне помилка — Java не створюватиме файл автоматично в режимі APPEND. Щоб створити файл, якщо його немає, використовуйте одразу дві опції:
Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
Як це виглядає на практиці
- Ви запускаєте програму із записом (перезаписом) — у файлі два рядки.
- Запускаєте програму з дозаписом — до цих рядків додаються нові, старі залишаються на місці.
3. Комбінування опцій: CREATE, APPEND, TRUNCATE_EXISTING
У Java можна комбінувати кілька опцій для гнучкішого керування режимами відкриття файлу:
- StandardOpenOption.CREATE — створити файл, якщо його немає.
- StandardOpenOption.APPEND — додавати дані у кінець.
- StandardOpenOption.TRUNCATE_EXISTING — обрізати файл до нуля байтів (очистити), якщо він існує.
- StandardOpenOption.CREATE_NEW — створити новий файл; якщо вже існує — помилка.
Приклад: створити файл, якщо його немає, і додати дані у кінець
Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
Приклад: створити файл або повністю очистити й записати нові дані
Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
Таблиця: основні режими запису
| Опція (опції) | Поведінка |
|---|---|
| (за замовчуванням) | Створити файл або перезаписати наявний |
|
Додати у кінець, помилка, якщо файлу немає |
|
Додати у кінець, створити файл, якщо його немає |
|
Обрізати файл до нуля й записати нові дані |
|
Створити новий файл, помилка, якщо файл уже існує |
4. Читання та запис бінарних файлів
Досі ми працювали з рядками. Але інколи потрібно записувати або читати «сирі» байти (наприклад, зображення, архіви, PDF-файли). У цьому випадку використовуйте:
- Files.readAllBytes(path) — зчитує файл у масив байтів.
- Files.write(path, byteArray) — записує масив байтів у файл.
Приклад: копіювання файлу
import java.nio.file.*;
import java.io.IOException;
public class CopyBinaryFileExample {
public static void main(String[] args) throws IOException {
Path source = Paths.get("logo.png");
Path target = Paths.get("logo_copy.png");
byte[] data = Files.readAllBytes(source);
Files.write(target, data);
System.out.println("Файл скопійовано!");
}
}
Приклад: дозапис бінарних даних
byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
Увага: дозапис бінарних даних у файл, який уже містить структуровані дані (наприклад, зображення), зазвичай призведе до його псування. Використовуйте дозапис лише для текстових або спеціально підготовлених бінарних файлів (наприклад, логів).
5. Коли потрібні потоки (FileInputStream, BufferedReader тощо)
Методи Files.readAllBytes(), Files.write() зручні для невеликих і середніх файлів, коли можна спокійно завантажити увесь вміст у памʼять за один раз. Якщо файл великий (наприклад, на гігабайти) або якщо ви хочете читати його построково (наприклад, для аналізу логів), використовуйте потоки.
Приклад: читання файлу построково за допомогою BufferedReader
import java.nio.file.*;
import java.io.*;
public class ReadLinesExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedReader reader = Files.newBufferedReader(path)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Рядок: " + line);
}
}
}
}
Приклад: запис файлу построково за допомогою BufferedWriter і дозапису
import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class WriteAppendExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedWriter writer = Files.newBufferedWriter(
path,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND)) {
writer.write("Ще один рядок через BufferedWriter!");
writer.newLine();
}
}
}
6. Практика: створюємо файл, дозаписуємо в нього рядки
Обʼєднаймо все в один застосунок. Припустімо, у нас є програма, яка веде список завдань (todo-list) у текстовому файлі. Кожного разу, коли користувач додає нове завдання, ми дозаписуємо його у кінець файлу.
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
public class TodoList {
public static void main(String[] args) throws IOException {
Path path = Paths.get("todo.txt");
Scanner scanner = new Scanner(System.in);
System.out.print("Введіть нове завдання: ");
String task = scanner.nextLine();
// Дозаписуємо завдання у кінець файлу (створюємо файл, якщо його немає)
Files.write(path,
List.of(task),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
System.out.println("Завдання додано!");
}
}
Запустіть програму кілька разів — кожне завдання з’явиться у файлі в новому рядку. Ось і маємо наш перший «вічний» todo-list!
7. Обробка помилок: що може піти не так?
Робота з файлами завжди пов’язана з ризиком помилок. Ось що може трапитися:
- IOException — базовий виняток для всіх помилок введення-виведення. Може виникнути, якщо файл зайнятий іншою програмою, якщо немає прав на читання/запис, якщо диск переповнений тощо.
- NoSuchFileException — якщо ви намагаєтеся прочитати або дозаписати у файл, якого не існує (і не вказали опцію CREATE).
- FileAlreadyExistsException — якщо ви використовуєте опцію CREATE_NEW, а файл уже є.
Рекомендація: завжди обгорніть роботу з файлами у try-catch:
try {
// ваші операції з файлами
} catch (IOException e) {
System.out.println("Помилка під час роботи з файлом: " + e.getMessage());
}
8. Типові помилки під час роботи з режимами відкриття файлів
Помилка № 1: забули вказати опцію CREATE під час дозапису. Якщо ви намагаєтеся дозаписати (APPEND) у файл, якого ще немає, отримаєте NoSuchFileException. Завжди додавайте CREATE, якщо хочете, щоб файл створювався автоматично.
Помилка № 2: випадковий перезапис файлу. Виклик Files.write(path, data) без додаткових опцій знищує увесь старий вміст файлу. Якщо хочете додати дані, а не знищити старі, використовуйте APPEND.
Помилка № 3: спроба дозапису бінарних даних у файл із текстом (або навпаки). Якщо ви змішуєте текстові й бінарні дані в одному файлі, найімовірніше, отримаєте пошкоджений файл, який неможливо прочитати. Завжди дотримуйтеся одного формату для одного файлу.
Помилка № 4: забули закрити потік. Якщо використовуєте потоки (BufferedWriter, BufferedReader), не забувайте закривати їх (краще через try-with-resources), інакше файл може залишитися заблокованим.
Помилка № 5: не обробили винятки. Будь-яка операція з файлами може «викинути» IOException. Якщо не обробити виняток, програма завершиться аварійно.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ