CodeGym /Kursy /JAVA 25 SELF /Podstawy java.io i java.nio: różnice, ewolucja API

Podstawy java.io i java.nio: różnice, ewolucja API

JAVA 25 SELF
Poziom 35, Lekcja 0
Dostępny

1. Jak Java nauczyła się pracować z plikami

Java i pliki: od czego się zaczęło

Dawno temu, w odległym 1996 roku, twórcy języka zaproponowali pierwszy sposób pracy z plikami — pakiet java.io. Pojawiły się w nim takie klasy jak File, FileInputStream, FileOutputStream, Reader, Writer i inne. Klasy te pozwalały wykonywać różne operacje na plikach: sprawdzać, czy plik istnieje, jego rozmiar, datę modyfikacji oraz pracować z katalogami.

Jednak, jak to często bywa z pierwszymi wersjami, nie było idealnie — wiele rzeczy można było zrobić wygodniej i bezpieczniej.

Ograniczenia starego API (java.io)

  • File — to nie plik, a „skrót/uchwyt”. Klasa File nie potrafi czytać ani zapisywać zawartości. Opisuje ścieżkę i metadane. Do odczytu/zapisu potrzebne są osobne strumienie: FileInputStream/FileOutputStream lub Reader/Writer.
  • Praca ze ścieżkami — udręka. Ręczne sklejanie ścieżek w stylu "C:\\Users\\" + user + "\\Documents" często psuło się przy przenoszeniu między Windows i Linux.
  • Brak obsługi dowiązań symbolicznych. Stare API nie rozumiało symlinków i mogło pracować z nimi niepoprawnie.
  • Słabe wsparcie atrybutów. Trudno było uzyskać uprawnienia, właściciela, flagi „ukryty” itp.
  • Niewydajne dla dużych plików. Klasyczne strumienie nie oferowały wygodnych i szybkich scenariuszy dla dużych ilości danych, brakowało asynchronicznego wejścia/wyjścia.

Pojawienie się java.nio.file (NIO.2, Java 7)

W Javie 7 pojawił się nowy pakiet: java.nio.file (często — NIO.2). Wniósł:

  • Path — nowoczesną abstrakcję ścieżki (w tym ścieżki wewnątrz ZIP, chmurowych i sieciowych systemów plików).
  • Files — statyczne narzędzia do odczytu, zapisu, kopiowania, usuwania.
  • FileSystem — pracę nie tylko z dyskiem lokalnym, lecz także z innymi systemami plików.
  • Obsługę dowiązań symbolicznych, rozszerzonych atrybutów, uprawnień.
  • Asynchroniczne I/O i lepszą wydajność dla dużych danych.
  • Wygodną pracę z katalogami i API strumieniowymi.

NIO to skrót od „New Input/Output” i choć ma już ponad dziesięć lat, dla Javy to wciąż współczesny standard.

2. Porównanie podejść: java.io vs java.nio.file

Klasa File (java.io)

  • Reprezentuje ścieżkę do pliku lub katalogu i ich metadane.
  • Nie potrafi czytać/zapisywać zawartości pliku.
  • Umożliwia sprawdzenie istnienia, rozmiaru, typu (plik/katalog), ścieżki bezwzględnej itp.
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("Plik istnieje!");
            System.out.println("Rozmiar: " + file.length() + " bajtów");
        }
    }
}

Klasa Path (java.nio.file)

  • Nowoczesna abstrakcja ścieżki, potrafi naturalnie się łączyć i normalizować.
  • Może reprezentować ścieżki lokalne, archiwalne i sieciowe.
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("Plik znaleziony!");
            System.out.println("Rozmiar: " + Files.size(path) + " bajtów");
        }
    }
}

Klasa Files (java.nio.file)

  • Zestaw statycznych metod do odczytu/zapisu w całości i częściami.
  • Kopiowanie, usuwanie, przenoszenie plików.
  • Tworzenie/usuwanie katalogów.
  • Dostęp do rozszerzonych atrybutów.
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);
        }
    }
}

Tabela porównawcza

Funkcja
java.io.File
java.nio.file.Path + Files
Reprezentacja ścieżki Tak Tak
Odczyt/zapis zawartości Nie Tak (przez Files)
Praca ze ścieżkami Niewygodnie Wygodnie (resolve, normalize)
Dowiązania symboliczne Nie Tak
Atrybuty plików Ograniczone Rozszerzone
Asynchroniczny IO Nie Tak (NIO.2)
Wieloplatformowość Częściowa Doskonała

3. Kiedy czego używać?

Stare API (java.io): kiedy jest potrzebne?

  • Do wsparcia kodu legacy. Jeśli projekt rozpoczął się przed Javą 7 i wszędzie używa File, FileInputStream, FileOutputStream, trzeba zachować kompatybilność.
  • W nowych projektach — niezalecane. Korzystanie ze „staruszka” File dziś to jak oglądanie wideo z kaset VHS.

Nowe API (java.nio.file): współczesny standard

  • Zawsze preferuj Path i Files w nowych projektach.
  • Prostsze, bezpieczniejsze, potężniejsze i lepiej integruje się ze strumieniami, lambdami i try-with-resources.

Krótka ściągawka

  • Odczyt, zapis, kopiowanie, usuwanie? — przez Files.
  • Praca ze ścieżkami (łączenie, normalizacja)? — przez Path.
  • Rozmiar, data, uprawnienia? — Files.getAttribute() i metody pokrewne.
  • Przechodzenie po katalogach, rekurencyjnie? — Files.walk, Files.list.

4. Przykłady: jak wyglądałby kod w starym i nowym API

Przykład 1: Sprawdzenie istnienia pliku

Stary sposób:

import java.io.File;

File file = new File("data.txt");
if (file.exists()) {
    System.out.println("Plik znaleziony!");
}

Nowy sposób:

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("Plik znaleziony!");
}

Przykład 2: Odczyt wszystkich linii pliku

Stary sposób:

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();

Nowy sposób:

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);
}

Komentarz: Nowy sposób jest krótszy i bezpieczniejszy; z try-with-resources zarządzanie zasobami jest jeszcze prostsze.

Przykład 3: Zapis ciągu do pliku

Stary sposób:

import java.io.FileWriter;

FileWriter writer = new FileWriter("output.txt");
writer.write("Cześć, pliku!");
writer.close();

Nowy sposób:

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

Path path = Paths.get("output.txt");
Files.write(path, "Cześć, pliku!".getBytes());

5. Przydatne niuanse

Dlaczego Java postanowiła wszystko przebudować?

  • Bezpieczeństwo i niezawodność. Mniej błędów z zamykaniem zasobów i ścieżkami.
  • Wieloplatformowość. Te same klasy dla Windows, Linux, a nawet archiwów ZIP.
  • Łatwość rozszerzania. Prościej dodawać wsparcie chmurowych i sieciowych systemów plików.
  • Wydajność. Lepsze zachowanie na dużych plikach i operacje asynchroniczne.
  • Zgodność ze współczesnym stosem Javy. Lambdy, strumienie, try-with-resources.

Jak przejść ze starego API na nowe?

Prawie zawsze można przekształcić File w Path i odwrotnie:

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

Co zrobić, gdy trafisz na stary kod?

  • Bez paniki: wszystko da się zrobić prościej i nowocześniej.
  • Jeśli jest możliwość — przepisuj na java.nio.file.
  • Jeśli nie — używaj mostów (toPath(), toFile()) i migruj stopniowo.

6. Końcowy przykład: miniaplikacja

Przykład: Sprawdzamy, czy plik istnieje, i wypisujemy jego rozmiar.

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("Plik znaleziony. Rozmiar: " + size + " bajtów");
            } catch (Exception e) {
                System.out.println("Błąd podczas pobierania rozmiaru pliku: " + e.getMessage());
            }
        } else {
            System.out.println("Nie znaleziono pliku!");
        }
    }
}

Czego tutaj użyliśmy:

  • Path — reprezentacja ścieżki.
  • Files.exists() — sprawdzenie istnienia.
  • Files.size() — pobranie rozmiaru.

7. Typowe błędy podczas pracy z API plików

Błąd nr 1: Oczekiwanie, że File potrafi czytać lub zapisywać zawartość. W rzeczywistości File to tylko „skrót/uchwyt”. Do odczytu/zapisu używaj FileInputStream/FileOutputStream (stare API) lub narzędzi Files (nowe API).

Błąd nr 2: Ręczne sklejanie ścieżek przez + lub ukośnik. Prowadzi to do błędów na różnych systemach operacyjnych. Używaj Path.resolve() albo Paths.get(...) z wieloma argumentami.

Błąd nr 3: Zapomnienie o zamknięciu strumienia. W starym API to częsta przyczyna wycieków zasobów. W nowym API wiele operacji przez Files nie tworzy strumieni, a tam, gdzie są potrzebne — stosuj try-with-resources.

Błąd nr 4: Używanie starego API w nowych projektach. Jeśli startujesz nowy projekt — wybieraj java.nio.file. Do java.io warto sięgać jedynie dla kompatybilności z kodem legacy.

1
Zadanie
JAVA 25 SELF, poziom 35, lekcja 0
Niedostępne
Gdzie jest mój tajny dziennik? 🕵️‍♀️
Gdzie jest mój tajny dziennik? 🕵️‍♀️
1
Zadanie
JAVA 25 SELF, poziom 35, lekcja 0
Niedostępne
Ile waży zapis gry? 💾
Ile waży zapis gry? 💾
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION