JavaRush /Курси /JAVA 25 SELF /Читання текстових файлів: построково, цілком

Читання текстових файлів: построково, цілком

JAVA 25 SELF
Рівень 36 , Лекція 1
Відкрита

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

Якщо файл невеликий (наприклад, до 1020 МБ), його зручно завантажити цілком. Наприклад, якщо потрібно швидко отримати список усіх рядків, проаналізувати текст або надіслати його мережею.

Сучасний спосіб: 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. Порівняння підходів: коли який використовувати

Спосіб Коли використовувати Переваги Недоліки
BufferedReader.readLine()
Великі файли, построкова обробка Заощаджує пам’ять, гнучко Читання лише по рядках
Files.readAllLines()
Малі та середні файли Одразу список рядків, просто Для великих файлів — OutOfMemoryError
Files.readString()
Малі файли, потрібен увесь текст Увесь текст цілком Немає розбиття на рядки

Рекомендація:
— Якщо файл невеликий — використовуйте 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.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ