1. Zapis z nadpisaniem
Pracując z plikami, warto pamiętać, że „zapisać do pliku” nie zawsze znaczy to samo. Czasem trzeba całkowicie nadpisać plik (np. gdy zapisujesz nowy raport), a czasem — dodać nowe informacje na końcu już istniejącego pliku (np. prowadzić log zdarzeń aplikacji). Zdarza się też, że trzeba po prostu odczytać zawartość pliku, niczego w nim nie zmieniając.
W Javie (zwłaszcza w nowoczesnym pakiecie java.nio.file) tryb pracy z plikiem określa zestaw specjalnych opcji, które przekazujesz do metod zapisu, na przykład do Files.write(). Dzięki tym opcjom możesz jednoznacznie wskazać, czy chcesz plik nadpisać, czy dopisać coś na końcu.
Jak to działa
Gdy wywołujesz Files.write(path, data), Java domyślnie tworzy nowy plik albo nadpisuje już istniejący. Cała stara zawartość pliku zostanie usunięta i zastąpiona nowymi danymi.
To domyślne zachowanie — swoisty „twardy reset” pliku. Jeśli w pliku było wcześniej 1000 wierszy, a zapiszesz jeden — wszystko, co było wcześniej, zniknie bez śladu.
Przykład: zapis wierszy do pliku
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class OverwriteFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> lines = List.of("Witaj, świecie!", "To jest nowy wpis w pliku.");
// Zapisujemy wiersze do pliku (stara zawartość zostanie usunięta)
Files.write(path, lines);
System.out.println("Plik został pomyślnie nadpisany!");
}
}
Po uruchomieniu tego kodu w pliku myfile.txt pozostaną tylko dwa wiersze z listy lines. Wszystko, co było wcześniej, zniknie (bez żadnych „wyrzutów sumienia” ze strony Javy).
2. Dopisywanie (append): dodawanie danych na końcu pliku
W niektórych zadaniach (np. prowadzenie dziennika zdarzeń, logowanie, akumulowanie danych) nie należy usuwać starej zawartości pliku, lecz dodawać nowe wiersze na końcu. W starym API używano do tego często FileWriter z flagą append = true. W nowym API robi się to prościej i bardziej jawnie.
Jak to zrobić
Wystarczy przekazać opcję StandardOpenOption.APPEND do metody Files.write():
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class AppendFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> moreLines = List.of("Dodano kolejny wiersz.", "I jeszcze jeden!");
// Dodajemy wiersze na końcu pliku (stara zawartość pozostaje)
Files.write(path, moreLines, StandardOpenOption.APPEND);
System.out.println("Wiersze zostały dodane na końcu pliku!");
}
}
Ważne: jeśli plik nie istnieje, przy próbie dopisywania wystąpi błąd — Java nie utworzy pliku automatycznie w trybie APPEND. Aby utworzyć plik, jeśli go nie ma, użyj od razu dwóch opcji:
Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
Jak to wygląda w praktyce
- Uruchamiasz program z zapisem (nadpisaniem) — w pliku są dwa wiersze.
- Uruchamiasz program z dopisywaniem — do tych wierszy zostaną dodane nowe, stare pozostaną na miejscu.
3. Łączenie opcji: CREATE, APPEND, TRUNCATE_EXISTING
W Javie można łączyć kilka opcji, aby elastyczniej sterować trybami otwierania pliku:
- StandardOpenOption.CREATE — utworzyć plik, jeśli go nie ma.
- StandardOpenOption.APPEND — dopisywać dane na końcu.
- StandardOpenOption.TRUNCATE_EXISTING — przyciąć plik do zera bajtów (wyczyścić), jeśli istnieje.
- StandardOpenOption.CREATE_NEW — utworzyć nowy plik; jeśli już istnieje — błąd.
Przykład: utwórz plik, jeśli go nie ma, i dopisz dane na końcu
Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
Przykład: utwórz plik albo całkowicie wyczyść i zapisz nowe dane
Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
Tabela: podstawowe tryby zapisu
| Opcja(e) | Zachowanie |
|---|---|
| (domyślnie) | Utworzyć plik lub nadpisać istniejący |
|
Dopisać na końcu, błąd jeśli pliku nie ma |
|
Dopisać na końcu, utworzyć plik, jeśli go nie ma |
|
Wyzerować plik i zapisać nowe dane |
|
Utworzyć nowy plik, błąd jeśli plik już istnieje |
4. Odczyt i zapis plików binarnych
Do tej pory pracowaliśmy z wierszami. Czasem jednak trzeba zapisywać lub czytać „surowe” bajty (np. obrazy, archiwa, pliki PDF). W takim przypadku użyj:
- Files.readAllBytes(path) — odczytuje plik do tablicy bajtów.
- Files.write(path, byteArray) — zapisuje tablicę bajtów do pliku.
Przykład: kopiowanie pliku
import java.nio.file.*;
import java.io.IOException;
public class CopyBinaryFileExample {
public static void main(String[] args) throws IOException {
Path source = Paths.get("logo.png");
Path target = Paths.get("logo_copy.png");
byte[] data = Files.readAllBytes(source);
Files.write(target, data);
System.out.println("Plik został skopiowany!");
}
}
Przykład: dopisywanie danych binarnych
byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
Uwaga: dopisywanie danych binarnych do pliku, który już zawiera dane o określonej strukturze (np. obraz), zwykle prowadzi do jego uszkodzenia. Używaj dopisywania tylko dla plików tekstowych lub specjalnie przygotowanych plików binarnych (np. logów).
5. Kiedy potrzebne są strumienie (FileInputStream, BufferedReader itp.)
Metody Files.readAllBytes(), Files.write() są wygodne dla małych i średnich plików, gdy można bez problemu załadować całą zawartość do pamięci za jednym razem. Jeśli plik jest duży (np. gigabajty) lub chcesz czytać go wierszami (np. do analizy logów), użyj strumieni.
Przykład: odczyt pliku wiersz po wierszu za pomocą BufferedReader
import java.nio.file.*;
import java.io.*;
public class ReadLinesExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedReader reader = Files.newBufferedReader(path)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Wiersz: " + line);
}
}
}
}
Przykład: zapis pliku wierszami za pomocą BufferedWriter i dopisywaniem
import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class WriteAppendExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedWriter writer = Files.newBufferedWriter(
path,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND)) {
writer.write("Jeszcze jeden wiersz przez BufferedWriter!");
writer.newLine();
}
}
}
6. Praktyka: tworzymy plik i dopisujemy do niego wiersze
Połączmy wszystko w jedną aplikację. Załóżmy, że mamy program, który prowadzi listę zadań (todo-list) w pliku tekstowym. Za każdym razem, gdy użytkownik dodaje nowe zadanie, dopisujemy je na końcu pliku.
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
public class TodoList {
public static void main(String[] args) throws IOException {
Path path = Paths.get("todo.txt");
Scanner scanner = new Scanner(System.in);
System.out.print("Wprowadź nowe zadanie: ");
String task = scanner.nextLine();
// Dopisujemy zadanie na końcu pliku (tworzymy plik, jeśli go nie ma)
Files.write(path,
List.of(task),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
System.out.println("Zadanie dodane!");
}
}
Uruchom program kilka razy — każde zadanie pojawi się w pliku w nowym wierszu. I tak powstała nasza pierwsza „trwała” lista zadań!
7. Obsługa błędów: co może pójść nie tak?
Praca z plikami zawsze wiąże się z ryzykiem błędów. Oto, co może się zdarzyć:
- IOException — podstawowy wyjątek dla wszystkich błędów wejścia/wyjścia. Może wystąpić, gdy plik jest zajęty przez inny program, gdy brak uprawnień do zapisu/odczytu, gdy dysk jest pełny itd.
- NoSuchFileException — gdy próbujesz odczytać lub dopisać do pliku, który nie istnieje (i nie podałeś opcji CREATE).
- FileAlreadyExistsException — gdy używasz opcji CREATE_NEW, a plik już istnieje.
Rekomendacja: zawsze obejmuj pracę z plikami blokiem try-catch:
try {
// twoje operacje na plikach
} catch (IOException e) {
System.out.println("Błąd podczas pracy z plikiem: " + e.getMessage());
}
8. Typowe błędy przy pracy z trybami otwierania plików
Błąd nr 1: zapomniano podać opcję CREATE przy dopisywaniu. Jeśli próbujesz dopisać (APPEND) do pliku, którego jeszcze nie ma, dostaniesz NoSuchFileException. Zawsze dodawaj CREATE, jeśli chcesz, aby plik był tworzony automatycznie.
Błąd nr 2: przypadkowe nadpisanie pliku. Wywołanie Files.write(path, data) bez dodatkowych opcji usuwa całą starą zawartość pliku. Jeśli chcesz dodać dane, a nie usunąć stare, użyj APPEND.
Błąd nr 3: próba dopisywania danych binarnych do pliku z tekstem (lub odwrotnie). Jeśli mieszasz dane tekstowe i binarne w jednym pliku, najpewniej otrzymasz plik nieczytelny. Zawsze trzymaj się jednego formatu na plik.
Błąd nr 4: zapomniano zamknąć strumień. Jeśli używasz strumieni (BufferedWriter, BufferedReader), pamiętaj, aby je zamykać (najlepiej przez try-with-resources), w przeciwnym razie plik może pozostać zablokowany.
Błąd nr 5: brak obsługi wyjątków. Każda operacja na plikach może zgłosić IOException. Jeśli nie obsłużysz błędu, program zakończy się awaryjnie.
GO TO FULL VERSION