Kurs Java

Pliki w Javie

Popularnymi operacjami wykonywanymi na plikach są odczyt z pliku oraz zapis do pliku. Czynności te wiążą się z otwarciem połączenia pomiędzy naszym programem a plikiem. Połączenie to nazywamy strumieniem. To właśnie strumieniem będą płynąć dane z pliku do programu (podczas odczytu) oraz z programu do pliku (podczas zapisu).

W trakcie tych operacji może wystąpić kilka problemów. Na przykład możemy próbować odczytywać dane z pliku, który nie istnieje, bądź też nie mamy do niego praw odczytu. Możemy też próbować zapisywać dane do katalogu, do którego nie mamy praw zapisu. Poza tym może się zdarzyć, że w trakcie naszego zapisu, jakiś inny proces będzie blokował dostęp do pliku, ponieważ właśnie wtedy będzie zapisywał tam dane.

Wszystkie te sytuacje doprowadzą do wystąpienia wyjątku, na który musimy być odpowiednio przygotowani. Do tego, musimy pamiętać, że otwierając strumień, musimy go zawsze zamknąć. Zadania te, wraz z kolejnymi wersjami Javy, stawały się coraz prostsze do zaimplementowania. Obecnie jesteśmy w komfortowej sytuacji, ponieważ nie musimy pisać nadmiernie dużo kodu, aby wykonać poprawny zapis lub odczyt danych do/z pliku.

Odczyt danych z pliku przed Javą 7

Dawno temu, a więc jeszcze przed Javą 7, wykonanie odczytu danych z pliku, wiązało się z wyprodukowaniem sporej ilości kodu:
BufferedReader bufferedReader = null;
try {
    File file = new File("C:/WORK/testfile.txt"); // Tworzy nowy obiekt reprezentujący plik.
    bufferedReader = new BufferedReader(new FileReader(file)); // Tworzy nowy buforowany strumień. 
    StringBuffer textBuffer = new StringBuffer(); // Tworzy nowy obiekt bufora tekstowego, 
                                                  // do którego niżej zapisujemy kolejne linie pliku.
    String line;
    while ((line = bufferedReader.readLine()) != null) {
    	textBuffer.append(line); // Dodaje linie do bufora tekstowego.
    	textBuffer.append("\n"); // Dodaje znak końca linii do bufora tekstowego.
    }
    System.out.println("Content: ");
    System.out.println(textBuffer);		
} catch (FileNotFoundException exception) {
    exception.printStackTrace(); // Drukuje na konsoli wyjątek, wyrzucany, 
                                 // gdy plik nie istnieje (w naszym przypadku plik testfile.txt).
} catch (IOException exception) {
    exception.printStackTrace(); // Drukuje na konsoli wyjątek, wyrzucany, 
                                 // gdy występują inne problemy z plikiem (na przykład brak dostępu).
}
finally {
    if (bufferedReader != null) { 
        try {
            bufferedReader.close(); // Próbuje zamknąć strumień.
        }
        catch(IOException exception) {
            exception.printStackTrace(); // Drukuje na konsoli wyjątek, wyrzucany, 
                                         // gdy nie udało się zamknąć strumienia.
        }
    }
}
Appa Notka. Pamiętaj, aby przed uruchomieniem powyższego kodu stworzyć plik o nazwie testfile.txt w katalogu WORK. Możesz też użyć innej nazwy pliku oraz katalogu, ale wtedy nie zapomnij zmienić ścieżki do pliku w kodzie. Jeśli plik nie zostanie odnaleziony, program wyrzuci wyjątek FileNotFoundException.
To dobry materiał szkoleniowy pozwalający zrozumieć, co tak naprawdę się dzieje w trakcie wykonywania operacji obsługujących plik. Widać, że jest to złożony proces generujący duże ilości często powtarzanego kodu (tzw. boilerplate). Na szczęście w kolejnych wersjach Javy większość tych zadań została umieszczona bezpośrednio w jej kodzie, przez co programista nie musi się już tyle napracować podczas tych stosunkowo prostych operacji.

Odczyt danych z pliku w Javie 7

Wiele zmieniło się już w Javie 7. W tej wersji pojawiła się nowa instrukcja o nazwie try-with-resources. Co to zmieniło? Jest znacznie krócej i przez to bardziej przejrzyście! Nowa instrukcja wygląda podobnie do instrukcji try catch, z tą różnicą, że automatyzuje proces obsługi zasobów. Tworzenie nowego bufora tekstowego jest tutaj wykonywane w nawiasie, jeszcze przed właściwym blokiem instrukcji try. Tak inicjowane zasoby są automatycznie zamykane przez Javę w momencie zakończenia instrukcji. Nie musi tego robić programista!

W taki sposób zachowają się wszystkie obiekty implementujące interfejs AutoCloseable. W naszym przypadku BufferedReader rozszerza klasę Reader, która implementuje Closeable, który to rozszerza interfejs AutoCloseable, a więc obiekt klasy BufferedReader jest typu AutoCloseable.
try (BufferedReader bufferedReader = new BufferedReader(new FileReader("C:/WORK/testfile.txt"))) {
    StringBuffer textBuffer = new StringBuffer();
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        textBuffer.append(line); 
        textBuffer.append("\n"); 
    }
    System.out.println("Content: ");
    System.out.println(textBuffer);     
}
catch (FileNotFoundException exception) {
    exception.printStackTrace();
}
catch (IOException exception) {
    exception.printStackTrace();
}    
Powyższy kod tworzymy, jeśli iterujemy po kolejnych liniach pliku. W przypadku gdy od razu chcemy odczytać całą zawartość pliku, możemy to zrobić nawet jeszcze krócej, w zaledwie kilku linijkach:
try {
    String content = new String(Files.readAllBytes(Paths.get("C:/WORK/testfile.txt")));
    System.out.println("Content: ");
    System.out.println(content);            
} catch (IOException exception) {
    exception.printStackTrace();
} 

Odczyt danych z pliku w Javie 8

W Javie 8 pojawił się szereg nowych rozwiązań programistycznych, które przedstawiamy w ramach Kursu Javy 8 do 14. Niemniej zobaczmy teraz, w jaki sposób można czytać dane z pliku w tej wersji Javy. Najpierw korzystamy z nowej metody lines, która zwraca strumień (ale nie strumień do pliku, tylko nowy rodzaj bytu z Javy 8, przeczytasz o nim we wspomnianym kursie). Następnie dane ze strumienia zapisujemy do listy, po czym drukujemy je na konsoli.
List list = new ArrayList<>();
try (BufferedReader bufferedReader = Files.newBufferedReader(Paths.get("C:/WORK/testfile.txt"))) {
    // Metod lines z BufferedReader zwraca strumień (nowe pojęcie wprowadzone w Javie 8) 
    // i zapisuje wiersze do listy (metodą collect z Javy 8)
    list = bufferedReader.lines().collect(Collectors.toList());

} catch (IOException exception) {
    exception.printStackTrace();
}

// Drukujemy listę za pomocą nowej metody interfejsu Iterable - forEach
list.forEach(System.out::println); 

Odczyt danych z pliku w Javie 11

W Javie 11 pojawiła się nowa metoda, która pozwala odczytać cały tekst z pliku do zmiennej tekstowej. Zamykanie zasobów odbywa się poza naszym udziałem. W porównaniu do metody readAllBytes z Javy 7 teraz nie trzeba tworzyć własnoręcznie obiektu typu String. Nowa metoda, zgodnie z nazwą, automatycznie zwraca obiekt tego typu.
try {
    String content = Files.readString(Paths.get("C:/WORK/testfile.txt"));
    System.out.println("Content:");
    System.out.println(content);
} catch (IOException exception) {
    exception.printStackTrace();
}  

Zapis do pliku od Javy 7

Zobaczmy jak zapis do pliku wygląda w Javie 7. Najpierw wykorzystujemy znaną nam już klasę Files:
try {
    String someText = "To be or not to be";
    Files.write(Paths.get("C:/WORK/newtestfile.txt"), someText.getBytes());
} catch (IOException exception) {
    exception.printStackTrace();
} 
Następnie korzystamy z instrukcji try-with-resources. W poniższym kodzie na uwagę zasługują pozostałe parametry (poza nazwą pliku). Określamy w nich kodowanie pliku oraz rodzaj operacji do wykonania. Kodowanie to parametr, który możemy ustawiać też w innych metodach, również tych odczytujących. Zwykle metody te korzystają z kodowania UTF-8. My również ustawiamy takie kodowanie, choć tak naprawdę, gdybyśmy pominęli ten parametr to i tak domyślnie zostałoby ustawione UTF-8.
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("C:/WORK/newtestfile.txt"),
        StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW)) {
    String someText = "To be or not to be";
    writer.write(someText);
} catch (Exception exception) {
    exception.printStackTrace();
}

Zapis do pliku od Javy 11

W Javie 11 wprowadzono również metodę do zapisu tekstu do pliku bez potrzeby martwienia się o instrukcję zamykającą strumienie do zasobów:
try {
    String someText = "To be or not to be";
    Files.writeString(Paths.get("C:/WORK/newtestfile.txt"), someText, StandardOpenOption.CREATE);
} catch (IOException exception) {
    exception.printStackTrace();
}
W tym rozdziale pokazaliśmy większość metod odczytujących i zapisujących dane do pliku w kilku istotnych wersjach Javy. Oczywiście wskazane przykłady warto przetestować własnoręcznie i sprawdzić różne możliwe opcje wywołań wymienionych metod. Czasem różne sytuacje trzeba odpowiednio przemyśleć w programie. Należy pamiętać o tym, by sprawdzić, czy plik, który chcemy stworzyć, już przypadkiem nie istnieje, albo czy plik, z którego chcemy czytać dane, został wcześniej stworzony.
Linki
https://docs.oracle.com/javase/tutorial/essential/io/file.html
Masz pytanie dotyczące tego rozdziału? Zadaj je nam!
Masz pytanie dotyczące prezentowanego materiału?
Coś jest dla Ciebie niejasne i Twoje wątpliwości przeszkadzają Ci w pełnym zrozumieniu treści?
Napisz do nas maila, a my chętnie znajdziemy odpowiednie rozwiązanie.
Najciekawsze pytania wraz z odpowiedziami będziemy publikować pod rozdziałem.
Nie czekaj. Naucz się programować jeszcze lepiej.
kursjava@javappa.com

Stale się rozwijamy, a więc bądź na bieżąco!
Na ten adres będziemy przesyłać informacje o ważniejszych aktualizacjach, a także o nowych materiałach pojawiających się na stronie.
Polub nas na Facebooku:
Nasi partnerzy: stackshare
Javappa to również profesjonalne usługi programistyczne oparte o technologie JAVA. Jeśli chesz nawiązać z nami kontakt w celu uzyskania doradztwa bądź stworzenia aplikacji webowej powinieneś poznać nasze doświadczenia.
Kliknij O nas .