Java #41: Strumienie znakowe

Ten wpis będzie bardzo podobny do poprzedniego, także jeśli jeszcze tego nie zrobiłeś/aś, przeczytaj wpierw lekcję o strumieniach danych. Tym razem zamiast korzystać z strumieni wejścia i wyjścia, użyję jednej z wielu klas typu Reader i Writer. Strumienie znakowe działają bardzo podobnie jak klasy strumieni, jednak są bardziej przystosowane do pracy z plikami tekstowymi.

Wybrane klasy typu Reader lub Writer:

  • FileReader/FileWriter
  • BufferedWriter/BufferedReader
  • CharArrayReader/CharArrayWriter
  • OutputStreamWriter/InputStreamReader
  • PushbackInput/StreamPushbackReader
  • StringWriter/StringReader
  • PipedWriter/PipedReader
  • FilterWriter/FilterReader
strumienie znakowe
Hierarchia klas typu Writer i Reader

Jak widzisz są one zbieżne z tymi, które obsługują strumienie. Co więcej możesz używać strumień za pomocą klas Reader/Writer, wrzucając go w konstruktor swojego odpowiednika. W takim razie, pewnie spytasz, po co specjalne klasy, które robią prawie to samo. Istotną różnicą pomiędzy nimi jest ich sposób użycia. Klasyczne strumienie pracują na danych binarnych, co sprawia, że w przypadku plików tekstowych (które oczywiście, jak każdy inny plik, też są w gruncie rzeczy jakimiś bajtami), że nie radzą sobie specjalnie dobrze z różnymi kodowaniami (ang encoding)*. Dlatego właśnie istnieją dedykowane klasy, które dużo lepiej spisują się przy pracy z danymi tekstowymi.

Strumienie znakowe buforowane

Jednymi z najbardziej wydajnych z nich są BufferedReader i BufferedWriter. Podobnie jak w przykładzie z ostatniej lekcji, zaimplementuję dla Ciebie dwie metody, które pokażą Ci, jak sobie z nimi radzić. Pierwsza z nich readFromConsole wczyta za pomocą strumienia wejścia (System.in) wpisaną przez Ciebie komendę, a następnie zwróci ją na konsoli. Druga metoda, będzie trochę bardziej skomplikowana. Wczytam w niej log, który stworzyłem w lekcji o logach, dodam do każdej linii nową datę i zapiszę całość do nowego pliku.

W przypadku pierwszej metody nie ma tu nic bardziej skomplikowanego. InputStreamReader opakowuję w BufferedReader i następnie używam go, tak jak zwykły strumień. Jedyna różnica jest, że wczytuję tu całą linię a nie pojedynczy bajt lub znak.

private static void readFromConsole() {
	Reader streamReader = new InputStreamReader(System.in);  
	BufferedReader bufferedReader = null;
	try {
		bufferedReader = new BufferedReader(streamReader);
		System.out.println("Wprowadź komendę");  
		String readLine = bufferedReader.readLine();
		System.out.println("Twoja komenda to: " + readLine);   
	} catch (IOException e) {
		LOGGER.log(Level.WARNING, e.getMessage(), e);
	} finally {
		try {
			if (bufferedReader != null) {
				bufferedReader.close();
			}
		} catch (IOException e) {
			LOGGER.log(Level.WARNING, e.getMessage(), e);
		}
	}
}

Try with resources

Cały czas korzystam tu z klasycznej klauzuli try – catch, która z powodu zamykania strumienia, jest bardzo rozbudowana. Z pomocą przychodzi tu mechanizm dostępny w Javie 7 o nazwie try – with – resources.

private static void copyFileWithDate(String inputfilePath, String outputfilePath) {
		try (Reader streamReader = new FileReader(inputfilePath); BufferedReader bufferedReader = new BufferedReader(streamReader);
				Writer fileWriter = new FileWriter(outputfilePath); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);){			
			// jakiś kod
	} catch (IOException e) {
		LOGGER.log(Level.WARNING, e.getMessage(), e);
	}
}

Jak widzisz w sekcji try pojawiły się nawiasy, w które wrzucam wszystkie zasoby, które powinny być automatycznie zamknięte. Taki kod często nie jest zbyt czytelny, więc jeśli chcesz możesz go zapisać po prostu tak:

try (BufferedReader bufferedReader = new BufferedReader(new FileReader(inputfilePath));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputfilePath)))

Jest trochę krócej, jednak sam musisz zdecydować, który sposób dla Ciebie jest korzystniejszy. Ostatnim elementem jest użycie pętli while do pobrania każdej linii z osobna do zmiennej typu String, a następnie zapisanie edytowanego tekstu do nowego pliku.

String readLine;
while((readLine = bufferedReader.readLine()) != null) {
	bufferedWriter.write("Copy date: " + Instant.now() + "\n" + readLine + "\n");
}

Nie musisz tutaj przypisać wartość zmiennej lokalnej readLine, ponieważ jest oczywiste, że albo przypisze jakąś wartość z pliku albo w najgorszym razie będzie ona nullem. Podczas zapisu poza skopiowaniem linijki z pierwotnego pliku dodałem przed nim nową datę kiedy odbywała się taka operacja. Skorzystałem tu z metody Instant.now(), która podaje mi aktualny timestamp** na podstawie zegara systemowego.

Cała metoda copyFileWithDate:

private static void copyFileWithDate(String inputfilePath, String outputfilePath) {
	try (BufferedReader bufferedReader = new BufferedReader(new FileReader(inputfilePath));
		BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputfilePath))){
		String readLine;
		while((readLine = bufferedReader.readLine()) != null) {
			bufferedWriter.write("Copy date: " + Instant.now() + "\n" + readLine + "\n");
			}
		} catch (IOException e) {
			LOGGER.log(Level.WARNING, e.getMessage(), e);
		}
	}

Kodowanie

Warto podkreślić, że FileReader/Writer sam określa kodowanie pliku na UTF-8. Jeśli chcesz użyć klasy, która pozwala na samodzielne przypisanie innego rodzaju kodowań, to użyj np. klasy InputStreamReader :

Reader reader = new InputStreamReader(new FileInputStream(inputfilePath),”UTF-8″);

*Są różne rodzaje kodowań plików tekstowych. Wynika to z tego, że wiele języków korzysta z innych znaków niż te standardowe – łacińskie. Najpopularniejsze rodzaje kodowań to np. UTF-8 lub UTF-16.

Więcej informacji o unicode znajdziesz tutaj: https://unicode-table.com/en/alphabets/.

Polecam też przeczytać ten artykuł o kodowaniu ogólnie: https://www.w3.org/International/questions/qa-what-is-encoding

**Tutaj dodatkowa informacja o tym czym jest timestamp: https://www.epochconverter.com

Link do kodu: https://github.com/developeronthego/java-middle/blob/master/src/main/java/middle/lesson24/BufferedReaderAndWriter.java

Wczytywanie tekstu za pomocą NIO: Java #42: pakiet NIO (new input-output)

Stay in the Loop

Get the daily email from CryptoNews that makes reading the news actually enjoyable. Join our mailing list to stay in the loop to stay informed, for free.

Ostatnio dodane

- Advertisement - spot_img

Powiązane wpisy