Zajęcia laboratoryjne nr 9 z programowania w języku Java - jedne zajęcia 1,5h. |
1. System wejścia-wyjścia w Javie
Ważnym i bardzo często wykorzystywanym obszarem zastosowań platformy Java jest programowanie aplikacji wykorzystujących dane odczytywane bezpośrednio od użytkownik, z plików lub z zasobów sieciowych. Java dostarcza dwóch podstawowych pakietów, służących do przeprowadzania operacji wejścia-wyjścia:
-
Pakiet -
java.io.*
-
Pakiet -
java.nio.*
Pakiet java.io
jako pakiet podstawowy zawiera przede wszystkim klasy, które
pozwalają operować na strumieniach danych. Klasy reprezentujące strumienie
(inaczej klasy strumieniowe) są podstawowym środkiem programowania operacji
wejścia-wyjścia w Javie.
Pakiet java.nio
(Java new input-output) wprowadzono dodatkowe środki
wejścia-wyjścia, takie jak kanały, bufory i selektory . Mimo nazwy
(new input-output) środki te nie zastępują klas strumieniowych. Służą przede
wszystkim do zapewnienia wysokiej efektywności i elastyczności programów,
które w bardzo dużym stopniu obciążone są operacjami wejścia-wyjścia. W szczególności
dotyczy to serwerów, które muszą równolegle obsługiwać ogromną liczbę połączeń
sieciowych.
Oprócz tego Java dostarcza klasy reprezentujące inne od strumieni obiekty
operacji wejścia-wyjścia. Do klas tych należy np. klasa File z pakietu java.io
- opisująca pliki i katalogi, a także w pakiecie java.net - klasy reprezentujące
obiekty "sieciowe", takie jak URL czy gniazdo (socket), mogące stanowić
źródło lub odbiornik danych w sieci (w szczególności w Internecie).
|
2. Klasy strumieniowe, operacje na strumieniach
Na wstępie należy zdefiniować pojęcie strumienia danych. Strumień danych jest pojęciem abstrakcyjnym, logicznym oznaczającym ciąg danych, właśnie "strumień", do którego dane mogą być dodawane i/lub z którego dane mogą być pobierane.
Przy czym:
-
strumień związany jest ze źródłem lub odbiornikiem danych,
-
źródło lub odbiornik mogą być dowolne: plik, pamięć, URL, gniazdo, potok …,
-
strumień służy do zapisywania-odczytywania informacji - dowolnych danych,
-
program:
-
kojarzy strumień z zewnętrznym źródłem/odbiornikiem,
-
otwiera strumień,
-
dodaje lub pobiera dane ze strumienia,
-
zamyka strumień.
-
-
przy czytaniu lub zapisie danych z/do strumienia mogą być wykonywane dodatkowe operacje (np. buforowanie, kodowanie-dekodowanie, kompresja-dekompresja),
-
w Javie dostarczono klas, reprezentujących strumienie. Hierarchia tych klas pozwala na programowanie w sposób abstrahujący od konkretnych źródeł i odbiorników.
Na strumieniach możemy wykonywać dwie podstawowe operacje:
-
odczytywanie danych,
-
zapisywanie danych.
Z tego punktu widzenia możemy mówić o strumieniach wejściowych i wyjściowych. I odpowiednio do tego – Java wprowadza dwie rozłączne hierarchie klas strumieniowych:
-
klasy strumieni wejściowych,
-
klasy strumieni wyjściowych.
Wejście | Wyjście | |
---|---|---|
Strumienie bajtowe |
InputStream |
OutputStream |
Strumienie znakowe |
Reader |
Writer |
Są to klasy abstrakcyjne, zatem bezpośrednio nie można tworzyć obiektów tych klas. |
Przy przetwarzaniu tekstów należy korzystać ze strumieni znakowych ze względu na to, iż w trakcie czytania/pisania wykonywane są odpowiednie operacje kodowania/dekodowania ze względu na stronę kodową właściwą dla źródła/odbiornika. |




Klasy strumieniowe – przedmiotowe (implementacje)
Źródło/odbiornik | Strumienie znakowe | Strumienie bajtowe |
---|---|---|
Pamięć |
CharArrayReader CharArrayWriter |
ByteArrayInputStream ByteArrayOutputStream |
StringReader StringWriter |
StringBufferInputStream |
|
Potok |
PipedReader PipedWriter |
PipedInputStream PipedOutputStream |
Plik |
FileReader FileWriter |
FileInputStream FileOutputStream |
Użycie klas przedmiotowych nie jest jedynym sposobem związania logicznego
strumienia z fizycznym źródłem lub odbiornikiem. Inne klasy (spoza pakietu java.io
,
np. klasy sieciowe) mogą dostarczać metod, które zwracają jako wynik referencję
do abstrakcyjnego strumienia związanego z konkretnym źródłem odbiornikiem
(np. plikiem w Sieci).
Klasy strumieniowe – przetwarzające (implementacje)
Rodzaj przetwarzania | Strumienie znakowe | Strumienie bajtowe |
---|---|---|
Buforowanie |
BufferedReader |
BufferedInputStream |
Filtrowanie |
FilterReader |
FilterInputStream |
Konwersja: bajty-znaki |
InputStreamReader |
|
Konkatenacja |
SequenceInputStream |
|
Serializacja obiektów |
ObjectInputStream |
|
Konwersje danych |
DataInputStream |
|
Zliczanie wierszy |
LineNumberReader |
LineNumberInputStream |
Podglądanie |
PushbackReader |
PushbackInputStream |
Drukowanie |
PrintWriter |
PrintStream |
import java.io.*;
public class CopyFile {
public static void main(String args[]) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("input.txt");
out = new FileOutputStream("output.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
}finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
import java.io.*;
public class CopyFile {
public static void main(String args[]) throws IOException {
FileReader in = null;
FileWriter out = null;
try {
in = new FileReader("input.txt");
out = new FileWriter("output.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
}finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
import java.io.*;
public class FileStreamTest {
public static void main(String args[]) {
try {
byte bWrite [] = {11,21,3,40,5};
OutputStream os = new FileOutputStream("test.txt");
for(int x = 0; x < bWrite.length ; x++) {
os.write( bWrite[x] ); // writes the bytes
}
os.close();
InputStream is = new FileInputStream("test.txt");
int size = is.available();
for(int i = 0; i < size; i++) {
System.out.print((char)is.read() + " ");
}
is.close();
}catch(IOException e) {
System.out.print("Exception");
}
}
}
3. Buforowanie
Buforowanie ogranicza liczbę fizycznych odwołań do urządzeń zewnętrznych, dzięki temu że fizyczny odczyt lub zapis dotyczy całych porcji danych, gromadzonych w buforze (wydzielonym obszarze pamięci). Jedno fizyczne odwołanie wczytuje dane ze strumienia do bufora lub zapisuje zawartość bufora do strumienia.
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
public class BufferedReaderDemo {
public static void main(String[] args) throws Exception {
String thisLine = null;
try {
// open input stream test.txt for reading purpose.
BufferedReader br = new BufferedReader("c:/test.txt");
while ((thisLine = br.readLine()) != null) {
System.out.println(thisLine);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringWriter;
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
StringWriter sw = null;
BufferedWriter bw = null;
String str = "Hello World!";
try {
// create string writer
sw = new StringWriter();
//create buffered writer
bw = new BufferedWriter(sw);
// writing string to writer
bw.write(str);
// forces out the characters to string writer
bw.flush();
// string buffer is created
StringBuffer sb = sw.getBuffer();
//prints the string
System.out.println(sb);
} catch(IOException e) {
// if I/O error occurs
e.printStackTrace();
} finally {
// releases any system resources associated with the stream
if(sw!=null)
sw.close();
if(bw!=null)
bw.close();
}
}
}
4. Serializacja obiektów
Obiekty tworzone przez program rezydują w pamięci operacyjnej, w przestrzeni adresowej procesu. Są zatem nietrwałe, bo kiedy program kończy działanie wszystko co znajduje się w jego przestrzeni adresowej ulega wyczyszczeniu i nie może być odtworzone. Serializacja (szeregowanie) pozwala na utrwalaniu obiektów. W Javie polega ona na zapisywaniu obiektów do strumienia.
Podstawowe zastosowania serializacji:
-
komunikacja pomiędzy obiektami/aplikacjami poprzez gniazdka (sockets),
-
zachowanie obiektu (jego stanu i właściwości) do późniejszego odtworzenia i wykorzystania przez tę samą lub inną aplikację.
Do zapisywania/odczytywania obiektów służą klasy ObjectOutputStream
oraz ObjectInputStream , które należą do strumieniowych klas przetwarzających.
|
Do strumieni mogą być zapisywane tylko serializowalne obiekty.
Obiekt jest serializowalny jeśli jego klasa implementuje interfejs Serializable .
Prawie wszystkie klasy standardowych pakietów Javy implementują ten interfejs.
|
5. Operacje na plikach i katalogach
Zarówno pliki jak i katalogi w języku Java reprezentowane są przez klasę java.io.File
.
Klasy File
używamy aby sprawdzić, czy interesujący nas plik lub katalog istnieje,
do tworzenia nowych plików oraz katalogów a także aby sprawdzić, czy dana instancja
klasy File
reprezentuje plik czy katalog. Klasa ta umożliwia także takie operacje
jak kasowanie oraz zmianę nazwy, natomiast nie służy ona do odczytu czy zapisu
z/do pliku.
public void readFile(String filePath) throws IOException {
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
try {
String textLine = bufferedReader.readLine();
do {
System.out.println(textLine);
textLine = bufferedReader.readLine();
} while (textLine != null);
} finally {
bufferedReader.close();
}
}
public void writeFile(String filePath, String[] textLines)
throws IOException {
FileWriter fileWriter = new FileWriter(filePath);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
try {
for (String line : textLines) {
bufferedWriter.write(line);
bufferedWriter.newLine();
}
} finally {
bufferedWriter.close();
}
}
6. Pliki o dostępie swobodnym
Klasa RandomAccessFile
definiuje pliki o dostępie swobodnym, które mogą być
otwarte w trybie "czytania" lub "czytania i pisania". Swobodny dostęp oznacza
dostęp do dowolnego bajtu danych bez potrzeby sekwencyjnego przetwarzania pliku
od początku. Pliki o dostępie swobodnym mogą być traktowane jako ciągi bajtów.
Bieżący bajt do odczytu lub miejsce do zapisu określa specjalny wskaźnik pozycji
w pliku (filePointer). Pozycję tę możemy zmieniać za pomocą metod seek()
i skip()
.
Jest także zmieniana przy każdej operacji czytania lub pisania. Do czytania/pisania
służy wiele metod read()
i write()
, które pozwalają operować na różnych rodzajach
danych odczytywanych i zapisywanych do pliku (np. readDouble, readLine, writeInt itp.).
Pliki o dostępie swobodnym nie są strumieniami. Klasa RandomAccessFile
nie należy do hierarchii klas strumieniowych.
|
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileExample {
public static void main(String[] args) {
try {
String filePath = "source.txt";
System.out.println(new String(readCharsFromFile(filePath, 1, 5)));
writeData(filePath, "Data", 5);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeData(String filePath, String data, int seek) throws IOException {
RandomAccessFile file = new RandomAccessFile(filePath, "rw");
file.seek(seek);
file.write(data.getBytes());
file.close();
}
private static byte[] readCharsFromFile(String filePath, int seek, int chars) throws IOException {
RandomAccessFile file = new RandomAccessFile(filePath, "r");
file.seek(seek);
byte[] bytes = new byte[chars];
file.read(bytes);
file.close();
return bytes;
}
}
7. Bibliografia
-
Bruce Eckels, "Thinking in Java. Edycja polska. Wydanie IV", wydawnictwo Helion.
-
Cay S. Horstmann, Gary Cornell, "Java. Podstawy. Wydanie IX", wydawnictwo Helion.
-
Cay S. Horstmann, Gary Cornell, "Java. Techniki zaawansowane. Wydanie IX", wydawnictwo Helion.
-
Krzysztof Barteczko, "Podstawy programowania w języku Java, PJWSTK", http://edu.pjwstk.edu.pl/wyklady/ppj/scb/
-
Konrad Kurczyna, "Laboratorium Java", Politechnika Świętokrzyska w Kielcach.
-
Mariusz Lipiński, "Nauka Javy", http://www.naukajavy.pl/