1. Читання файлу построково: BufferedReader та інші
Раніше ми вже знайомилися з побайтовим читанням: це зручно для роботи з двійковими форматами, але для тексту такий підхід незручний. Текстовий файл складається із символів, які залежать від кодування. Тому Java пропонує зручні «надбудови» — FileReader, BufferedReader та інші класи, які перетворюють потік байтів на потік символів і рядків.
Уявіть текстовий файл — чи то журнал роботи програми, список користувачів або навіть величезний роман «Війна і мир». Іноді потрібно швидко прочитати весь файл цілком, іноді — пройтися по ньому построково, а іноді — дістати один конкретний рядок.
У Java є кілька способів це зробити, і вибір залежить від розміру файлу та завдання. Якщо потрібно обробити файл по одному рядку (наприклад, підрахувати кількість рядків або знайти запис), використовують построкове читання. А якщо файл невеликий — його можна завантажити в пам’ять цілком і працювати з ним як зі списком рядків.
Чому построково — це добре?
Для великих файлів построкове читання рятує від проблем із пам’яттю. Завантажувати в пам’ять гігабайтний лог — погана ідея: великий шанс натрапити на OutOfMemoryError. А от зчитувати файл рядок за рядком можна без особливих витрат, навіть якщо він важить сотні мегабайтів.
Як це робиться в Java?
Найкласичніший спосіб — використовувати BufferedReader (або його «родичів») і метод readLine().
import java.io.*;
public class ReadLinesDemo {
public static void main(String[] args) {
String fileName = "example.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
System.out.printf("%3d: %s%n", lineNumber, line);
lineNumber++;
}
} catch (IOException e) {
System.out.println("Помилка під час читання файлу: " + e.getMessage());
}
}
}
Тут ми відкриваємо файл для читання й за допомогою readLine() читаємо по одному рядку, доки не дійдемо до кінця (null). Після цього виводимо кожен рядок із номером.
Коротко про try-with-resources
Бачите конструкцію try (...) { ... }? Це try-with-resources. Вона гарантує, що файл закриється, навіть якщо посеред читання станеться помилка. Вам не потрібно вручну викликати close(): закриття відбудеться автоматично, навіть під час catch/finally.
Навіщо потрібен BufferedReader?
BufferedReader читає не по одному символу, а цілими блоками (зазвичай по 8192 байти), що пришвидшує роботу з файлами. Крім того, у нього є зручний метод readLine(), який повертає рядок відразу до символу нового рядка.
Який буфер обрати?
Зазвичай BufferedReader використовує буфер розміром 8192 байти (8 КБ) — цього достатньо для більшості завдань. Якщо ви читаєте дуже довгі рядки (наприклад, по 100_000 символів), буфер можна збільшити:
BufferedReader reader = new BufferedReader(new FileReader(fileName), 65536); // Буфер 64 КБ
Але для звичайних завдань стандартний розмір підходить ідеально.
2. Читання файлу цілком: Files.readAllLines і Files.readString
Якщо файл невеликий (наприклад, до 10–20 МБ), його зручно завантажити цілком. Наприклад, якщо потрібно швидко отримати список усіх рядків, проаналізувати текст або надіслати його мережею.
Сучасний спосіб: Files.readAllLines
Починаючи з Java 7, з’явився зручний клас Files із багатьма корисними методами.
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class ReadAllLinesDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try {
List<String> lines = Files.readAllLines(path);
for (int i = 0; i < lines.size(); i++) {
System.out.printf("%3d: %s%n", i + 1, lines.get(i));
}
} catch (IOException e) {
System.out.println("Помилка під час читання файлу: " + e.getMessage());
}
}
}
Тут метод Files.readAllLines(path) повертає список рядків (List<String>). З цим списком можна працювати як зі звичайною колекцією: шукати, сортувати, фільтрувати.
Ще сучасніше: Files.readString (Java 11+)
Якщо вам потрібно отримати весь файл одним рядком (наприклад, для пошуку підрядка або передавання в JSON), використовуйте Files.readString:
import java.nio.file.*;
import java.io.IOException;
public class ReadStringDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try {
String content = Files.readString(path);
System.out.println("Вміст файлу:");
System.out.println(content);
} catch (IOException e) {
System.out.println("Помилка під час читання файлу: " + e.getMessage());
}
}
}
А як щодо кодування?
Типово, методи Files.readAllLines і Files.readString використовують стандартне кодування вашої системи. Якщо файл записано в іншому кодуванні (наприклад, Windows-1251), вкажіть його явно:
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
String content = Files.readString(path, StandardCharsets.UTF_8);
3. Порівняння підходів: коли який використовувати
| Спосіб | Коли використовувати | Переваги | Недоліки |
|---|---|---|---|
|
Великі файли, построкова обробка | Заощаджує пам’ять, гнучко | Читання лише по рядках |
|
Малі та середні файли | Одразу список рядків, просто | Для великих файлів — OutOfMemoryError |
|
Малі файли, потрібен увесь текст | Увесь текст цілком | Немає розбиття на рядки |
Рекомендація:
— Якщо файл невеликий — використовуйте Files.readAllLines або Files.readString.
— Якщо файл великий або ви не знаєте його розміру — використовуйте BufferedReader.readLine().
4. Практичні завдання: приклади та розбір
Приклад 1. Підрахунок рядків у великому файлі
Припустімо, потрібно дізнатися, скільки рядків у величезному файлі (наприклад, у логу сервера).
import java.io.*;
public class LineCount {
public static void main(String[] args) {
String fileName = "biglog.txt";
int lineCount = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
while (reader.readLine() != null) {
lineCount++;
}
System.out.println("Усього рядків у файлі: " + lineCount);
} catch (IOException e) {
System.out.println("Помилка під час читання файлу: " + e.getMessage());
}
}
}
Приклад 2. Пошук рядка за вмістом
Шукаємо всі рядки, де трапляється слово «помилка» (без урахування регістру):
import java.io.*;
public class FindErrorLines {
public static void main(String[] args) {
String fileName = "biglog.txt";
String keyword = "помилка";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
if (line.toLowerCase().contains(keyword)) {
System.out.printf("%3d: %s%n", lineNumber, line);
}
lineNumber++;
}
} catch (IOException e) {
System.out.println("Помилка під час читання файлу: " + e.getMessage());
}
}
}
Приклад 3. Завантаження конфігурації з невеликого файлу
Файл config.txt:
host=localhost
port=8080
mode=dev
Читаємо його цілком і розбираємо на пари ключ–значення:
import java.nio.file.*;
import java.util.*;
public class ConfigLoader {
public static void main(String[] args) throws Exception {
Path path = Path.of("config.txt");
List<String> lines = Files.readAllLines(path);
Map<String, String> config = new HashMap<>();
for (String line : lines) {
if (line.trim().isEmpty() || line.startsWith("#")) continue; // пропускаємо порожні рядки та коментарі
String[] parts = line.split("=", 2);
if (parts.length == 2) {
config.put(parts[0].trim(), parts[1].trim());
}
}
System.out.println("Завантажена конфігурація: " + config);
}
}
5. Типові помилки під час читання текстових файлів
Помилка № 1: Спроба читати двійковий файл як текстовий. Якщо ви відкриєте зображення або архів через BufferedReader чи Files.readAllLines, отримаєте безглузді символи й ризик OutOfMemoryError. Для двійкових файлів використовуйте InputStream!
Помилка № 2: Не обробили виняток. Файли можуть видалити, перемістити або заблокувати. Завжди обгортайте читання у try-catch і інформуйте користувача про проблеми.
Помилка № 3: Ігнорування кодування. Якщо у вас текст російською, а ви читаєте файл без зазначення кодування, можна отримати «?????». Використовуйте StandardCharsets.UTF_8 або потрібне вам кодування.
Помилка № 4: Забули закрити потік. Якщо не закрити файл, він може залишитися заблокованим до завершення програми. Завжди використовуйте try-with-resources.
Помилка № 5: Використання read() замість readLine() для текстових файлів. Метод read() читає по символу — це повільно й незручно для рядків. Для тексту використовуйте readLine() або методи класу Files.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ