last

advertisement
OPERACJE I/O: CZYTANIE I ZAPISYWANIE
Aby pobrać dane, program otwiera strumień (stream) do źródła informacji
(pliku, pamięci, soketu) i sekwencyjnie czyta te informacje.
W ten sam sposób odbywa się wysyłanie informacji do zewnętrznych lokalizacji
– poprzez otwarcie strumienia i sekwencyjnego wprowadzania danych.
Bez względu na miejsce zapisu lub odczytu danych oraz na typ tych danych
algorytm sekwencyjnego odczytu i zapisu jest taki sam:
STRUMIENIE ZNAKOWE
Reader i Writer to abstrakcyjne superklasy dla strumieni znakowych java.io.
Reader zapewnia API i częściową implementację dla readers – strumieni
zapisujących 16-bitowe znaki. Podklasy klas Reader i Writer implementują
strumienie specjalne i dzielą się na dwie kategorie:
- te które czytają lub zapisują do danych typu sinks (pokazane na szaro)
- te które przeprowadzają niektóre rodzaje przetwarzania danych (pokazane na
biało)
STRUMIENIE BAJTÓW
Aby czytać i zapisywać bajty 8-bitowe, programy powinny korzystać ze
strumieni bajtów, potomków klas
InputStream
OutputStream dostarczają interfejs API.
i
OutputStream.
InputStream i
Reader I InputStream definiują podobne API ale dla innych typów danych. Na
przykład Reader zawiera metody do czytania znaków i tablic znaków:
int read()
int read(char cbuf[])
int read(char cbuf[], int offset, int length)
InputStream definiuje te same metody ale do czytania bajtów i tablic bajtów:
int read()
int read(byte cbuf[])
int read(byte cbuf[], int offset, int length)
Ponadto zarówno Reader jak i InputStream zapewniają metody do oznaczania
położenia w strumieniu, zmiany wejść i repetowania aktualnego położenia.
Writer i OutputStream są bardzo podobne. Writer definiuje metody zapisu
znaków i tablic znaków:
int write(int c)
int write(char cbuf[])
int write(char cbuf[], int offset, int length)
OutputStream definiuje te same metody w odniesieniu do bajtów:
int write(int c)
int write(byte cbuf[])
int write(byte cbuf[], int offset, int length)
Wszystkie streams--readers, writers, input streams, and output streams są
automatycznie otwierane przy ich tworzeniu.
POSŁUGIWANIE SIĘ STRUMIENIAMI
Strumienie I/O:
JAK KORZYSTAĆ ZE STRUMIENI PLIKOWYCH
Strumienie plikowe są prawdopodobnie najłatwiejszymi do zrozumienia
rodzajami strumieni.
Wszystkie
strumienie:
FileReader,
FileWriter,
FileInputStream,
i
FileOutputStream czytają lub zapisują do pliku w rodzimym systemie plików.
import java.io.*;
public class Copy {
public static void main(String[] args) throws IOException {
File inputFile = new File("farrago.txt");
File outputFile = new File("outagain.txt");
FileReader in = new FileReader(inputFile);
FileWriter out = new FileWriter(outputFile);
int c;
while ((c = in.read()) != -1)
out.write(c);
in.close();
out.close();
}
}
Powyższy program jest bardzo prosty. Otwiera on FileReader do pliku
farrago.txt oraz otwiera FileWriter do pliku outagain.txt.
Program czyta znaki tak długo, aż skończą się dane w pliku wejściowym i
zapisuje te dane.
Poniżej pokazano kod wykonujący tworzenie file reader:
File inputFile = new File("farrago.txt");
FileReader in = new FileReader(inputFile);
Kod ten tworzy obiekt typu File – klasy użytkowej dostarczanej przez java.io.
Po uruchomieniu w aktualnym katalogu powinna się znaleźć kopia pliku
farrago.txt w zbiorze outagain.txt. Oto zawartość tego pliku:
So she went into the garden to cut a cabbage-leaf, to
make an apple-pie; and at the same time a great
she-bear, coming up the street, pops its head into the
shop. 'What! no soap?' So he died, and she very
imprudently married the barber; and there were
present the Picninnies, and the Joblillies, and the
Garyalies, and the grand Panjandrum himself, with the
little round button at top, and they all fell to playing
the game of catch as catch can, till the gun powder ran
out at the heels of their boots.
Samuel Foote 1720-1777
JAK KORZYSTAĆ ZE STRUMIENI TYPU PIPE
Strumienie typu PIPE są używane do łączenia wyjścia jednego wątku do wejścia
innego. PipedReader i PipedWriter implementują komponenty wejścia i wyjścia.
Bez strumieni typu Pipe programy musiałyby przechowywać gdzieś swoje
rezultaty pomiędzy swoimi krokami, tak jak poniżej:
Korzystając ze strumieni typu Pipe programy mogą wyprowadzać wyjście
jednej metody do wejścia innej:
Poniższy program implementuje powyższy schemat:
import java.io.*;
public class RhymingWords {
public static void main(String[] args) throws IOException {
FileReader words = new FileReader("words.txt");
// do the reversing and sorting
Reader rhymedWords = reverse(sort(reverse(words)));
// write new list to standard out
BufferedReader in = new BufferedReader(rhymedWords);
String input;
while ((input = in.readLine()) != null)
System.out.println(input);
in.close();
}
public static Reader reverse(Reader source) throws IOException {
BufferedReader in = new BufferedReader(source);
PipedWriter pipeOut = new PipedWriter();
PipedReader pipeIn = new PipedReader(pipeOut);
PrintWriter out = new PrintWriter(pipeOut);
new ReverseThread(out, in).start();
return pipeIn;
}
public static Reader sort(Reader source) throws IOException {
BufferedReader in = new BufferedReader(source);
PipedWriter pipeOut = new PipedWriter();
PipedReader pipeIn = new PipedReader(pipeOut);
PrintWriter out = new PrintWriter(pipeOut);
new SortThread(out, in).start();
return pipeIn;
}
}
Połączenie utworzone w programie kreuje łańcuch typu pipe:
Klasa SequenceInputStream tworzy pojedynczy strumień z wielu wejść
źródłowych.
Oto przykładowy program:
import java.io.*;
public class Concatenate {
public static void main(String[] args) throws IOException {
ListOfFiles mylist = new ListOfFiles(args);
SequenceInputStream s = new SequenceInputStream(mylist);
int c;
while ((c = s.read()) != -1)
System.out.write(c);
s.close();
}
}
import java.util.*;
import java.io.*;
public class ListOfFiles implements Enumeration {
private String[] listOfFiles;
private int current = 0;
public ListOfFiles(String[] listOfFiles) {
this.listOfFiles = listOfFiles;
}
public boolean hasMoreElements() {
if (current < listOfFiles.length)
return true;
else
return false;
}
public Object nextElement() {
InputStream in = null;
if (!hasMoreElements())
throw new NoSuchElementException("No more files.");
else {
String nextElement = listOfFiles[current];
current++;
try {
in = new FileInputStream(nextElement);
} catch (FileNotFoundException e) {
System.err.println("ListOfFiles: Can't open " + nextElement);
}
}
return in;
}
}
Praca z filtrami łańcuchów
Pakiet java.io dostarcza zbiór klas abstrakcyjnych, które definiują i częściowo
implementują filtry strumieni. Filtry te filtrują dane przeczytane lub zapisywane
do pliku. Filtry strumieni to klasy FilterInputStream, FilterOutputStream,
FilterInputStream, i FilterOutputStream.
Większość filtrów strumieni dostarczanych przez pakiet java.io to podklasy klas
FilterInputStream i FilterOutputStream. Są one wypisane poniżej:
- DataInputStream i DataOutputStream
- BufferedInputStream i BufferedOutputStream
- LineNumberInputStream
- PushbackInputStream
- PrintStream
Aby użyć filtrowanego wejścia lub wyjścia łańcucha, dowiązuje się filtrowany
łańcuch do innego wejścia lub wyjścia podczas jego tworzenia:
BufferedReader d = new BufferedReader(new DataInputStream(System.in));
String input;
while ((input = d.readLine()) != null) {
... //do something interesting here
}
JAK UŻYWAĆ KLAS DATAINPUTSTREAM I
DATAOUTPUTSTREAM
Przykładowy program czyta i zapisuje dane sformatowane w kolumny
oddzielone tabulatorami. Kolumny zawierają ceny sprzedaży, ilość towarów i
opis towarów.
19.99 12
9.99 8
Java T-shirt
Java Mug
Klasa DataOutputStream, tak jak inne strumienie filtrowane, musi być
dowiązana do innej klasy OutputStream. W tym przypadku jest dowiązana do
klasy FileOutputStream, która jest ustawiona do zapisu do pliku invoice1.txt:
DataOutputStream out = new DataOutputStream(
new FileOutputStream("invoice1.txt"));
Następnie, DataIODemo korzysta z metod zapisu klasy DataOutputStream do
zapisu danych:
for (int i = 0; i < prices.length; i ++) {
out.writeDouble(prices[i]);
out.writeChar('\t');
out.writeInt(units[i]);
out.writeChar('\t');
out.writeChars(descs[i]);
out.writeChar('\n');
}
out.close();
Następnie, klasa DataIODemo otwiera strumień DataInputStream do właśnie
zapisywanego pliku:
DataInputStream in = new DataInputStream(
new FileInputStream("invoice1.txt"));
DataInputStream musi też być dowiązany do innej klasy InputStream.
try {
while (true) {
price = in.readDouble();
in.readChar();
//throws out the tab
unit = in.readInt();
in.readChar();
//throws out the tab
char chr;
desc = new StringBuffer(20);
char lineSep = System.getProperty("line.separator").charAt(0);
while ((chr = in.readChar() != lineSep) {
desc.append(chr);
}
System.out.println("You've ordered " + unit + " units of "
+ desc + " at $" + price);
total = total + unit * price;
}
} catch (EOFException e) { }
System.out.println("For a TOTAL of: $" + total);
in.close();
Kiedy przeczytane są wszystkie dane, DataIODemo wyświetla wyrażenie
reasumujące szyk i należną całkowitą wartość i zamyka strumień.
Należy zwrócić uwagę na pętle używaną przez DataIODemo do czytania danych
ze strumienia DataInputStream. Zwykle, gdy dane są czytane, można zobaczyć
taką pętlę:
while ((input = in.read()) != null) {
...
}
Metoda odczytu zwraca wartość null, która oznacza osiągnięcie końca pliku.
Wiele metod DataInputStream nie potrafi tego zrobić.
Po uruchomieniu programu DataIODemo na wyjściu pojawia się następująca
sewencja:
You've ordered 12 units of Java T-shirt at $19.99
You've ordered 8 units of Java Mug at $9.99
You've ordered 13 units of Duke Juggling Dolls at $15.99
You've ordered 29 units of Java Pin at $3.99
You've ordered 50 units of Java Key Chain at $4.99
For a TOTAL of: $892.8800000000001
SERIALIZACJA OBIEKTÓW
Dwa strumienie w java.io - ObjectInputStream i ObjectOutputStream są
strumieniami bajtów i pracują jak inne strumienie wejścia-wyjścia. Jakkolwiek,
są one specjalne gdyż mogą czytać i zapisywać obiekty.
Kluczem do
zapisywania obiektów jest
zapisywanie ich stanów w
serializowanej formie odpowiedniej do rekonstrukcji tego obiektu takim, jakim
był po odczytaniu. W ten sposób czytanie i zapisywanie obiektów jest procesem
nazywanym serializacją. Serializacja obiektów jest niezbędna do budowy prawie
wszystkich aplikacji. Z serializacji obiektów można korzystać na następujące
sposoby:
- Remote Method Invocation (RMI) - komunikacja pomiędzy obiektami i
soketami
- Lightweight persistence – archiwacja obiektów w celu ich użycia w
późniejszym wywołaniu tego samego programu
Praca z plikami o losowym dostępie
Strumienie o sekencyjnym dostępie muszą zapisywać swoje dane sekwencyjnie.
Chociaż takie strumienie są niezwykle przydatne, są one konsekwencją
zastosowania medium sekwencyjnego, takiego jak papier i taśma magnetyczna.
Pliki o losowym dostępie zapewniają niesekwencyjny, lub też losowy, dostep do
swoich zawartości.
Do czego przydatne są pliki o losowym dostępie? Rozważmy format
archiwizacji ZIP:
Przypuśćmy że chcemy rozpakować wybrany plik z zarchiwum ZIP.
Korzystając ze strumienia sekwencyjnego, należałoby:
- otworzyć archiwum
- przeszukać archiwum i zlokalizować pożądany plik
- rozpakować plik
- zamknąć archiwum
Posługując się takim algorytmem, średnio trzeba przeczytać połowę archiwum
zanim znajdzie się poszukiwany zbiór. Ten sam plik można rozpakować
bardziej efektywnie, korzystając z usługi wyszukiwania pliku o losowym
dostępie i posługując się algorytmem:
- otworzyć archiwum
- przeszukać katalog archiwum i zlokalizować wpis dla szukanego pliku
- wyszukać (od tyłu) pozycję pliku
- rozpakować plik
- zamknąć archiwum
Algorytm ten jest bardziej efektywny z powodu tego, że odczytywany jest
jedynie katalog archiwum i plik przeznaczony do rozpakowania.
Download