JavaRush /Курси /JAVA 25 SELF /Основи java.io та java.nio: відмінності, еволюція API

Основи java.io та java.nio: відмінності, еволюція API

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

1. Як Java навчилася працювати з файлами

Java та файли: із чого все починалося

Колись, у далекому 1996 році, розробники мови запропонували перший спосіб роботи з файлами — пакет java.io. У ньому з’явилися такі класи, як File, FileInputStream, FileOutputStream, Reader, Writer та інші. Ці класи дозволяли виконувати різні операції з файлами: дізнаватися, чи існує файл, його розмір і дату зміни, а також працювати з каталогами.

Проте як часто буває з першими версіями, не все було ідеально — багато що можна було реалізувати зручніше й безпечніше.

Обмеження старого API (java.io)

  • File — не файл, а «ярлик». Клас File не вміє читати або записувати вміст. Він описує шлях і метадані. Для читання/запису потрібні окремі потоки: FileInputStream/FileOutputStream або Reader/Writer.
  • Робота зі шляхами — це біль. Ручне «склеювання» шляхів на кшталт "C:\\Users\\" + user + "\\Documents" часто ламалося під час перенесення між Windows і Linux.
  • Немає підтримки символічних посилань. Старий API не розумів символічних посилань і працював із ними некоректно.
  • Слабка підтримка атрибутів. Складно отримати права доступу, власника або атрибут «прихований» тощо.
  • Неефективність для великих файлів. Класичні потоки не пропонували зручних і швидких сценаріїв для великих обсягів даних, бракувало асинхронного введення-виведення.

Поява java.nio.file (NIO.2, Java 7)

У Java 7 з’явився новий пакет: java.nio.file (часто — NIO.2). Він приніс:

  • Path — сучасна абстракція шляху (включно зі шляхами всередині ZIP, хмарних і мережевих файлових систем).
  • Files — статичні утиліти для читання, запису, копіювання, видалення.
  • FileSystem — робота не лише з локальним диском, а й з іншими файловими системами.
  • Підтримка символічних посилань, розширених атрибутів, прав доступу.
  • Асинхронне введення-виведення та поліпшена продуктивність для великих обсягів даних.
  • Зручна робота з каталогами та потоковими API.

Абревіатуру NIO розшифровують як «New Input/Output». Попри те, що цьому API вже понад десять років, для Java це й досі сучасний стандарт.

2. Порівняння підходів: java.io vs java.nio.file

Клас File (java.io)

  • Представляє шлях до файлу або каталогу та пов’язані метадані.
  • Не вміє читати/писати вміст файлу.
  • Дозволяє перевірити існування, дізнатися розмір, тип (файл/каталог), абсолютний шлях тощо.
import java.io.File;

public class ExampleIO {
    public static void main(String[] args) {
        File file = new File("example.txt");
        if (file.exists()) {
            System.out.println("Файл існує!");
            System.out.println("Розмір: " + file.length() + " байт");
        }
    }
}

Клас Path (java.nio.file)

  • Сучасна абстракція шляху, природно поєднується та нормалізується.
  • Може представляти локальні, архівні та мережеві шляхи.
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ExampleNIOPath {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("example.txt");
        if (Files.exists(path)) {
            System.out.println("Файл знайдено!");
            System.out.println("Розмір: " + Files.size(path) + " байт");
        }
    }
}

Клас Files (java.nio.file)

  • Набір статичних методів для читання/запису файлів повністю й частинами.
  • Копіювання, видалення, переміщення файлів.
  • Створення/видалення каталогів.
  • Доступ до розширених атрибутів.
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class ExampleNIOFiles {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("example.txt");
        List<String> lines = Files.readAllLines(path);
        for (String line : lines) {
            System.out.println(line);
        }
    }
}

Таблиця порівняння

Функція
java.io.File
java.nio.file.Path + Files
Представлення шляху Так Так
Читання/запис вмісту Ні Так (через Files)
Робота зі шляхами Незручно Зручно (resolve, normalize)
Символічні посилання Ні Так
Атрибути файлів Обмежено Розширено
Асинхронне введення-виведення Ні Так (NIO.2)
Кросплатформність Часткова Відмінна

3. Коли використовувати що?

Старий API (java.io): коли він потрібен?

  • Для підтримки легасі-коду. Якщо проєкт розпочали до Java 7 і всюди використовують File, FileInputStream, FileOutputStream, можливо, доведеться дотримуватися сумісності.
  • У нових проєктах — не рекомендується. Використовувати «дідуся» File сьогодні — усе одно що дивитися відео з VHS-касет.

Новий API (java.nio.file): сучасний стандарт

  • Завжди надавайте перевагу Path і Files у нових проєктах.
  • Простіше, безпечніше, потужніше та краще інтегрується зі стрімами, лямбдами й try-with-resources.

Коротка памʼятка

  • Читання, запис, копіювання, видалення? — через Files.
  • Робота зі шляхами (обʼєднання, нормалізація)? — через Path.
  • Розмір, дата, права доступу?Files.getAttribute() та супутні методи.
  • Обхід каталогів, рекурсивно?Files.walk, Files.list.

4. Приклади: як виглядав би код на старому й новому API

Приклад 1: Перевірка існування файлу

Старий спосіб:

import java.io.File;

File file = new File("data.txt");
if (file.exists()) {
    System.out.println("Файл знайдено!");
}

Новий спосіб:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

Path path = Paths.get("data.txt");
if (Files.exists(path)) {
    System.out.println("Файл знайдено!");
}

Приклад 2: Читання всіх рядків файлу

Старий спосіб:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

File file = new File("data.txt");
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
    System.out.println(line);
}
reader.close();

Новий спосіб:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

Path path = Paths.get("data.txt");
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
    System.out.println(line);
}

Коментар: Новий спосіб коротший і безпечніший; з try-with-resources керування ресурсами ще простіше.

Приклад 3: Запис рядка у файл

Старий спосіб:

import java.io.FileWriter;

FileWriter writer = new FileWriter("output.txt");
writer.write("Привіт, файл!");
writer.close();

Новий спосіб:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

Path path = Paths.get("output.txt");
Files.write(path, "Привіт, файл!".getBytes());

5. Корисні нюанси

Чому Java вирішила усе переробити?

  • Безпека та надійність. Менше помилок із закриттям ресурсів та під час роботи зі шляхами.
  • Кросплатформність. Єдині класи для Windows, Linux і навіть для ZIP-архівів.
  • Легкість розширення. Простіше додавати підтримку хмарних і мережевих файлових систем.
  • Продуктивність. Вища ефективність під час роботи з великими файлами та підтримка асинхронних операцій.
  • Сумісність із сучасним Java-стеком. Лямбди, стріми, try-with-resources.

Як перейти зі старого API на новий?

Майже завжди можна перетворити File на Path і навпаки:

File file = new File("data.txt");
Path path = file.toPath();
File file2 = path.toFile();

Що робити, якщо натрапили на старий код?

  • Не лякайтеся: багато що можна зробити простіше й сучасніше.
  • Є можливість — переписуйте на java.nio.file.
  • Немає можливості — використовуйте мости (toPath(), toFile()) та мігруйте поступово.

6. Підсумковий приклад: мінізастосунок

Приклад: Перевіряємо, чи існує файл, і виводимо його розмір.

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileInfo {
    public static void main(String[] args) {
        Path path = Paths.get("test.txt");
        if (Files.exists(path)) {
            try {
                long size = Files.size(path);
                System.out.println("Файл знайдено. Розмір: " + size + " байт");
            } catch (Exception e) {
                System.out.println("Помилка під час отримання розміру файлу: " + e.getMessage());
            }
        } else {
            System.out.println("Файл не знайдено!");
        }
    }
}

Що ми тут використали:

  • Path — представлення шляху.
  • Files.exists() — перевірка існування.
  • Files.size() — отримання розміру.

7. Типові помилки під час роботи з файловими API

Помилка № 1: Очікування, що File може читати або писати вміст. Насправді File — це лише «ярлик». Для читання/запису використовуйте FileInputStream/FileOutputStream (старий API) або утиліти Files (новий API).

Помилка № 2: Ручне «склеювання» шляхів через + або косу риску. Це призводить до помилок на різних ОС. Використовуйте Path.resolve() або Paths.get(...) з кількома аргументами.

Помилка № 3: Забули закрити потік. У старому API це часта причина витоку ресурсів. У новому API багато операцій через Files потоків не створюють, а де вони потрібні — застосовуйте try-with-resources.

Помилка № 4: Використання старого API в нових проєктах. Якщо запускаєте новий проєкт — обирайте java.nio.file. До java.io має сенс звертатися лише для сумісності з легасі-кодом.

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