CodeGym /Kursy /JAVA 25 SELF /Odczyt i zapis plików: podstawowe operacje

Odczyt i zapis plików: podstawowe operacje

JAVA 25 SELF
Poziom 35 , Lekcja 2
Dostępny

1. Odczyt pliku w całości

Odczyt wszystkich bajtów

Czasem trzeba wyciągnąć z pliku całą zawartość naraz — jak jest, bez analizy, czy to obrazek, archiwum, czy jakiś format binarny. Do tego w Javie jest metoda Files.readAllBytes(path). Zwraca ona tablicę bajtów (byte[]), czyli surowe dane pliku, które dalej możesz wykorzystać wedle uznania.

import java.nio.file.*;

public class ReadBytesExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("example.txt");
        byte[] allBytes = Files.readAllBytes(path);
        System.out.println("Długość pliku w bajtach: " + allBytes.length);
    }
}

Uwaga: Jeśli plik jest duży (gigabajty), takie podejście może prowadzić do problemów z pamięcią — w końcu cała zawartość trafia od razu do RAM-u.

Odczyt wszystkich linii

Dla plików tekstowych jest bardziej przyjazny sposób: Files.readAllLines(path). Zwraca on listę linii (List<String>), gdzie każda pozycja to jeden wiersz z pliku (separatory linii są uwzględniane automatycznie).

import java.nio.file.*;
import java.util.List;

public class ReadLinesExample {
    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);
        }
    }
}

Ważne: Domyślnie używane jest kodowanie systemowe. Jeśli chcesz jawnie wskazać kodowanie (np. UTF-8), użyj przeciążonej wersji metody:

List<String> lines = Files.readAllLines(path, java.nio.charset.StandardCharsets.UTF_8);

Przykład: czytamy plik i wypisujemy na ekran

Dodajmy to do naszej miniaplikacji (np. „notatnik” albo „lista zadań”):

import java.nio.file.*;
import java.util.List;

public class TodoReader {
    public static void main(String[] args) throws Exception {
        Path todoPath = Paths.get("todo.txt");
        if (Files.exists(todoPath)) {
            List<String> tasks = Files.readAllLines(todoPath);
            System.out.println("Twoje zadania na dziś:");
            for (String task : tasks) {
                System.out.println("- " + task);
            }
        } else {
            System.out.println("Plik todo.txt nie został znaleziony. Utwórz listę zadań!");
        }
    }
}

2. Zapis do pliku

Zapis tablicy bajtów

Jeśli masz tablicę bajtów (np. wynik serializacji, obraz, audio), użyj Files.write(path, bytes). Ta metoda tworzy plik, jeśli go nie ma, lub nadpisuje istniejący.

import java.nio.file.*;

public class WriteBytesExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("output.bin");
        byte[] data = {1, 2, 3, 4, 5};
        Files.write(path, data);
        System.out.println("Bajty zapisano do pliku output.bin");
    }
}

Zapis listy linii

Dla plików tekstowych wygodniej użyć listy linii — każda linia zostanie zapisana osobno, z znakiem nowej linii.

import java.nio.file.*;
import java.util.Arrays;
import java.util.List;

public class WriteLinesExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("todo.txt");
        List<String> tasks = Arrays.asList(
            "Kupić mleko",
            "Zadzwonić do babci",
            "Zrobić pracę domową z Java"
        );
        Files.write(path, tasks);
        System.out.println("Listę zadań zapisano w todo.txt");
    }
}

Uwaga: Domyślnie plik zostanie nadpisany! Jeśli już istniał, stare zadania znikną. (Jak nie stracić poprzedniej zawartości — opowiemy nieco później.)

Zapis pojedynczego wiersza do pliku

Jeśli chcesz zapisać jeden wiersz — po prostu utwórz listę z jedną pozycją lub użyj Files.write(path, string.getBytes()):

import java.nio.file.*;

public class WriteStringExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("hello.txt");
        String message = "Cześć, Java!";
        Files.write(path, message.getBytes());
        System.out.println("Wiersz zapisano do hello.txt");
    }
}

Jawne określenie kodowania

Aby uniknąć „krzaczków” przy pracy z tekstem po rosyjsku (i nie tylko), zawsze podawaj kodowanie:

import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.List;

public class WriteLinesUtf8Example {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("hello.txt");
        List<String> lines = List.of("Cześć, świecie!", "To jest Java.");
        Files.write(path, lines, StandardCharsets.UTF_8);
    }
}

3. Obsługa błędów

Podczas pracy z plikami Java może zgłosić wyjątek IOException. To ogólny sygnał, że „coś poszło nie tak” na poziomie wejścia/wyjścia. Przyczyn może być wiele: pliku po prostu nie ma, brakuje uprawnień, dysk jest pełny lub zajęty, a czasem nawet wyjęto pendrive w najmniej odpowiednim momencie.

Dlatego metody odczytu i zapisu plików zawsze wymagają od nas gotowości na obsługę takich sytuacji. Robi się to przez try-catch:

import java.nio.file.*;
import java.util.List;
import java.io.IOException;

public class SafeReadExample {
    public static void main(String[] args) {
        Path path = Paths.get("todo.txt");
        try {
            List<String> lines = Files.readAllLines(path);
            System.out.println("Zawartość pliku:");
            for (String line : lines) {
                System.out.println(line);
            }
        } catch (IOException ex) {
            System.out.println("Błąd podczas odczytu pliku: " + ex.getMessage());
        }
    }
}

Po co to wszystko?

try-catch zamienia potencjalne wywrócenie się programu w kontrolowaną sytuację. Jeśli pliku nie ma — spokojnie poinformujemy użytkownika. Jeśli system nie daje dostępu — wskażemy problem. A jeśli dysk zniknie w środku operacji, program nie runie z okropnym stack trace'em, tylko ostrzeże normalnym komunikatem. Innymi słowy: IOException to nie wróg, a po prostu sposób, w jaki Java mówi nam: „hej, na zewnątrz coś poszło nie tak, wymyśl, co robić dalej”.

4. Praktyczne przykłady

Odczytać plik i wyświetlić zawartość

import java.nio.file.*;
import java.util.List;
import java.io.IOException;

public class PrintFileExample {
    public static void main(String[] args) {
        Path path = Paths.get("notes.txt");
        try {
            if (!Files.exists(path)) {
                System.out.println("Plik notes.txt nie został znaleziony.");
                return;
            }
            List<String> lines = Files.readAllLines(path, java.nio.charset.StandardCharsets.UTF_8);
            System.out.println("Zawartość pliku notes.txt:");
            for (String line : lines) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Błąd podczas odczytu pliku: " + e.getMessage());
        }
    }
}

Zapisać wiersz do nowego pliku

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

public class WriteFileExample {
    public static void main(String[] args) {
        Path path = Paths.get("greeting.txt");
        String content = "Witaj w świecie Java IO!";
        try {
            Files.write(path, content.getBytes(StandardCharsets.UTF_8));
            System.out.println("Wiersz zapisano do pliku greeting.txt");
        } catch (IOException e) {
            System.out.println("Błąd podczas zapisu pliku: " + e.getMessage());
        }
    }
}

Zapisać listę linii do pliku

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.io.IOException;

public class WriteListExample {
    public static void main(String[] args) {
        Path path = Paths.get("shopping.txt");
        List<String> items = List.of("Chleb", "Mleko", "Ser");
        try {
            Files.write(path, items, StandardCharsets.UTF_8);
            System.out.println("Listę zakupów zapisano w shopping.txt");
        } catch (IOException e) {
            System.out.println("Błąd podczas zapisu pliku: " + e.getMessage());
        }
    }
}

5. Krótko o strumieniach

Metody Files.readAllBytes, Files.readAllLines, Files.write — to „wszystko naraz”: albo cały plik do pamięci, albo cała zawartość od razu na dysk. Dla małych plików to wygodne, a dla dużych — nie bardzo (można otrzymać OutOfMemoryError albo po prostu „zawiesić się” na czytaniu gigabajtowych logów).

Do pracy z dużymi plikami lub odczytu linia po linii używa się strumieni:

  • Dla tekstu: BufferedReader, BufferedWriter
  • Dla bajtów: InputStream, OutputStream

Więcej o strumieniach powiemy w kolejnych wykładach, a na razie mała zajawka:

import java.nio.file.*;
import java.io.*;

public class BufferedReaderExample {
    public static void main(String[] args) {
        Path path = Paths.get("bigfile.txt");
        try (BufferedReader reader = Files.newBufferedReader(path)) {
            String line;
            while ((line = reader.readLine()) != null) {
                // Przetwarzamy wiersz
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Błąd podczas odczytu pliku: " + e.getMessage());
        }
    }
}

6. Przydatne wskazówki

  • Nadpisywanie pliku: Wszystkie metody Files.write domyślnie nadpisują plik. Jeśli chcesz dopisać na końcu, użyj dodatkowych opcji (o tym — w następnym wykładzie).
  • Kodowanie: Zawsze podawaj kodowanie jawnie, jeśli pracujesz z tekstem spoza ASCII. To uchroni przed „krzaczkami” na wyjściu. Dobry wybór — StandardCharsets.UTF_8.
  • Ścieżki: Używaj Paths.get(...) dla przenośności międzyplatformowej. Nigdy nie hardkoduj ukośników ( lub \) ręcznie.
  • Nazwy plików: Nie używaj w nazwach plików niedozwolonych znaków (?, *, :, <, >, | itp.), zwłaszcza jeśli Twój kod ma działać w systemie Windows.

7. Typowe błędy przy odczycie i zapisie plików

Błąd nr 1: Brak obsługi IOException. Bardzo często początkujący piszą Files.readAllLines(path) bez try-catch, i przy pierwszym problemie (plik nie znaleziony, brak uprawnień, uszkodzony dysk) program się wywraca. Zawsze obsługuj wyjątki!

Błąd nr 2: Praca z dużymi plikami przez readAllBytes/readAllLines. Jeśli plik waży setki megabajtów lub gigabajty, próba załadowania go w całości może „zabić” Twój program. W takich przypadkach używaj strumieni (BufferedReader).

Błąd nr 3: Brak wskazania kodowania przy zapisie/odczycie tekstu. Jeśli nie wskażesz kodowania, na różnych komputerach i systemach operacyjnych wynik może się różnić. Problem często ujawnia się przy cyrylicy. Używaj StandardCharsets.UTF_8 lub innego, którego potrzebujesz, jawnie.

Błąd nr 4: Oczekiwanie, że File/Path to sam plik. Klasa File lub Path to tylko „skrót” do pliku, a nie sam plik. Tworząc obiekt, nie tworzysz pliku na dysku. Aby utworzyć plik — użyj metod Files.createFile, Files.write i tak dalej.

Błąd nr 5: Niezamknięty strumień (gdy używasz strumieni). Jeśli używasz strumieni ręcznie (np. BufferedReader), koniecznie je zamykaj (najlepiej przez try-with-resources). W przeciwnym razie plik może pozostać zablokowany i niedostępny dla innych programów.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION