Jason. Cała informatyka w jednym miejscu!

Zaprezentuję teraz drugi wariant zapisu i odczytu danych (poprzednim była serializacja). Język Java oferuje dodatkowo klasy zdolne do zapisywania danych w pliku tekstowym. Tutaj procedura jest zdecydowanie bardziej rozbudowana i uprzedzam, że może sprawiać duże trudności w rozumieniu, gdyż kod przeze mnie prezentowany będzie od razu zaopatrzony w metody, których mogliście jeszcze nie widzieć :D. Skoro już uprzedziłem, przechodzimy do rozpoczęcia tematu. Zapis i odczyt z pliku tekstowego w języku Java!

ZAPIS I ODCZYT Z PLIKU TEKSTOWEGO W JĘZYKU JAVA. CZYM SIĘ RÓŻNI OD SERIALIZACJI?

W pierwszych słowach mojego wywodu chcę wspomnieć o istotnej rzeczy odnośnie dokonania wyboru "kiedy jedno, a kiedy drugie". Rezygnujemy z serializacji obiektów wówczas, gdy nasz program ma obsługiwać dane wejściowe, a nie został napisany w języku Java. Jakbyśmy byli w stanie zaimportować takie dane na przykład w programie napisanym w C++ :D? Jedyny sposób to właśnie ten, o którym jest teraz mowa, czyli eksport do pliku o formacie uniwersalnym, który nie dotyczy jedynego języka. Wtedy otwarcie takiego pliku staje się możliwe i mamy zaletę niniejszego rozwiązania. Wadą jest możliwość edycji w zwykłym notatniku. Zapis i odczyt z pliku tekstowego to przecież podstawowe funkcje edytorów tekstu. Są dostępne warianty szyfrowania danych tekstowych, ale to już wykracza poza temat. Nie czas na takie rewelacje, zatem przedstawię na razie sam proces zapisywania i odczytywania danych z pliku ".txt".

PRZYKŁAD KODU ŹRÓDŁOWEGO

Oto przykładowy kod ukazujący proces zapisywania i odczytywania danych jednocześnie:

KLASA "MAIN"

public class Main {
	public static void main(String[] args) {
		new Launcher();
	}
}

KLASA "LAUNCHER"

import java .io.*;
import java .util.ArrayList;

public class Launcher {
	private static final String SAVE_FILENAME = "save.txt";

	private final ArrayList<Point> points = new ArrayList<>();

	public Launcher() {
		addPoints();

		try {
			writeToFile();
			//readFromFile();
		}
		catch (Exception e) {
			e.printStackTrace();
		}

		printPoints();
	}

	private void addPoints() {
		points.add(new Point());
		points.add(new Point(8, 6));
		points.add(new Point(14, 13));
	}

	private void writeToFile() throws IOException {
		File file = new File(SAVE_FILENAME);
		FileWriter fw = new FileWriter(file);
		BufferedWriter bw = new BufferedWriter(fw);

		for (Point point : points) {
			bw.write(point.saveFormat());
			bw.newLine();
		}

		bw.close();
	}

	private void readFromFile() throws IOException {
		File file = new File(SAVE_FILENAME);
		FileReader fr = new FileReader(file);
		BufferedReader br = new BufferedReader(fr);
		String data;

		while ((data = br.readLine()) != null) {
			String[] coordinates = data.split(",");
			int x = Integer.parseInt(coordinates[0]);
			int y = Integer.parseInt(coordinates[1]);
			Point newPoint = new Point(x, y);

			points.add(newPoint);
		}
	}

	private void printPoints() {
		points.forEach(System.out::println);
	}
}

KLASA "POINT"

public class Point {
	private final int x, y;

	public Point() {
		this(0, 0);
	}

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
	
	@Override
	public String toString() {
		return "(" + x + ", " + y + ")";
	}

	public String saveFormat() {
		return x + "," + y;
	}
}

Zachowałem tę samą strukturę, aczkolwiek z drobnymi zmianami. Po pierwsze, skorzystałem tym razem z kolekcji "ArrayList" będącą najbardziej podstawową w języku Java. Program obsługuje zapis i odczyt wielu wierszy z pliku, stąd taka zmiana. Główne punkty programu też uległy zmianie i czas na spory zastrzyk wyjaśnień obu metod ("writeToFile" oraz "readFromFile") wykonujących kolejno zapis i odczyt z pliku tekstowego.

ZAPIS DO PLIKU TEKSTOWEGO

Metoda zapisująca do pliku korzysta z trzech zmiennych lokalnych. Wytłumaczmy z grubsza o co w każdej z nich chodzi:

  1. "File" tworzy nowy plik o podanej nazwie wraz ze ścieżką do niego w postaci łańcucha znaków,
  2. Typ "FileWriter" umożliwia wykonywanie operacji na strumieniach znaków. Pamiętacie w poprzednim przykładzie nazwę "FileInputStream" i "FileOutputStream"? To są "bliźniaki" sterujące również strumieniami, ale bajtów zamiast znaków. Dlatego właśnie "zmielone" obiekty wyświetlały same dyrdymały w notatniku :D,
  3. "BufferedWriter" to klasa stanowiąca duże "wspomaganie" dla strumienia znaków, która posiada dużo więcej możliwości. Ona sama w sobie nie jest wymagana, aczkolwiek stanowi pomocny dodatek do zapisywania danych, gdyż dysponuje o wiele większą paletą operacji.

Dalej mamy pętle "for", aczkolwiek o innym charakterze niż ta podstawowa pętla "for". To jest tak zwana "pętla rozszerzona", dzięki której możemy dużo wygodniej obsługiwać wszelkie tablice i kolekcje. W środku niej wywołujemy metodę z "BufferedWriter" o nazwie "write" dopisującą tekst do pliku jako zwróconą wartość z przesłoniętej metody "toString". Klasa "Point" posiada dodatkową metodę formatującą współrzędne dla zapisywanych danych ("saveFormat"). Powodem takiego obejścia jest obsługa odczytywania. Odpowiada za nią pętla "while", która zarządza przetwarzaniem pliku tekstowego linijka po linijce. Gdybyśmy umieścili format zgodny z metodą "toString", to musielibyśmy zaprogramować system w taki sposób, aby "przeskakiwał" znak nawiasu oraz znak odstępu tuż po przecinku (to tylko zbędne komplikacje). Zapis i odczyt z pliku wymaga szwajcarskiej precyzji w kwestii "chodzenia" znak po znaku i jeżeli nie macie ochoty na "wydłubywanie" określonych znaków, zróbcie tak jak napisałem i nie łączcie wspólnego formatu do dwóch operacji.

Po zapisaniu wartości w danym wierszu, każemy następnym poleceniem przenieść się do nowego wiersza i w ten sposób każda osobna linijka przechowuje jedną parę dwóch współrzędnych. Na końcu jest zamknięcie strumienia, które stanowi nasz obowiązek. Zapis i odczyt z pliku tekstowego zawsze musi się kończyć zamknięciem pliku, na którym operujemy! Konsekwencją mogą być problemy z dalszymi operacjami na tym samym pliku np. próba usunięcia może zakończyć się niepowodzeniem z powodu otwartego strumienia danych, a system operacyjny będzie twierdził, że trzeba "zamknąć wszystkie programy".

ODCZYT Z PLIKU TEKSTOWEGO

Teraz zerkamy na metodę "readFromFile". Występują te same zmienne, tylko korzystamy teraz z klas przeznaczonych do odczytu strumieni danych ("FileReader" i "BufferedReader"). O wiele ciekawsza jest pętla "while", która weryfikuje każdy następny wiersz tekstu czy nie jest pusty ("null"). Ten specyficzny zapis warunkowy wewnątrz pętli:

while ((data = br.readLine()) != null)

oznacza tyle, że występuje tu od razu przypisywanie i weryfikowanie wartości. Równie dobrze można byłoby napisać tak:

String data = br.readLine();

while (data != null) {
	// instrukcje
	data = br.readLine();
}

jednak nie dość, że tak jest prościej i bardziej zwięźle, to jeszcze będzie bardziej świadczyć o Waszym profesjonalizmie. Jedyna uwaga dotyczy nawiasów. Instrukcja przypisania musi zostać zagnieżdżona!!! Ważne jest to, aby wiedzieć, że metoda "readLine" pobiera wiersz tekstu przesuwając się w stronę kolejnego wiersza i zatrzymując się na nim, aż trafi na sam koniec (wtedy będzie "null").

W środku pętli mamy tablicę pobierającą nasze dwie liczby całkowite za pomocą kolejnej metody, "split". "split" zwraca tablicę typu "String" dzieląc jeden łańcuch tekstu na kilka fragmentów, sugerując się podanym znakiem (tzw. "separatorem"). W naszym przypadku jest to przecinek, a zatem znak przecinka spowoduje "podział" tekstu i oczyszczenie wartości z wszelkich przecinków (innymi słowy, nastąpi podzielenie łańcucha na dwie połówki w miejscu znaku przecinka, a sam znak "ulegnie zniszczeniu"). Następnie po wyodrębnieniu pożądanych współrzędnych, konwertujemy łańcuch na liczbę przy pomocy statycznej metody "parseInt" z klasy "Integer" (trzeba podać "żywcem" indeksy tablicy). Następnie tworzymy nową instancję typu "Point", przypisujemy nasze liczby i wreszcie dodajemy nasz obiekt do listy. Tak to wygląda "od kuchni" ;).

Na koniec taka uwaga, że odczytując dane i wprowadzając obiekty do listy, niepotrzebne jest wywołanie metody dodające przykładowe punkty. To jest tylko konieczne przy pierwszym zapisie, ale przy odczytywaniu danych powinniście to sobie zakomentować.

Zawartość pliku tekstowego po zapisie w języku Java

Oto efekt zapisu danych do pliku tekstowego. Dzięki formatowaniu tekstu w postaci jednej pary liczb na wiersz, dużo łatwiej będzie przebiegać odczyt.


Jeżeli musicie sobie dać znacznie więcej czasu na przemyślenie tego, nie wahajcie się go sobie dać. Występuje tu dużo metod, które dla większości z Was mogą być jeszcze obce. Zapis i odczyt z pliku tekstowego jest uważany za temat podstawowy, dlatego też warto zabrać się za niego jak najszybciej.

PODOBNE ARTYKUŁY