Biblioteki wejścia/wyjścia
Strumienie we/wy (I/O)
Pojęcie strumienia
• Strumień reprezentuje źródło lub odbiorcę danych (obiekt zdolny odbierać lub
produkować dane).
• Strumień ukrywa szczegóły związane z rzeczywistymi operacjami wykonywanymi na danych w urządzeniach we/wy.
• Hierarchię klas biblioteki we/wy moŜna podzielić ze względu na kierunek
przepływu danych (wejście i wyjście) oraz na elementarne jednostki danych
Strumienie:
• bajtowe
• InputStream
• OutputStream
• znakowe
• Reader
• Writer
Strumienie bajtów stosuje się do danych binarnych (obraz, dźwięk), strumienie znakowe do sformatowanych danych liczbowych i oczywiście tekstu. Preferować znakowe (Unicode, efektywniejsze)
Unicode (UCS)
• Reprezentacja 2-bajtowa
• MoŜliwość prezentacji, wymiany i
przetwarzania tekstów w róŜnych językach
• Około 50 000 znaków
• www.unicode.org
• 127 pierwszych znaków jest zgodnych z ASCII
• 256 pierwszych –ISO 8859-1
UTF-8
• Pewne sekwencje bajtów posiadają
specjalne znaczenie w róŜnych systemach operacyjnych. Unicode moŜe być
niebezpieczny!
• UTF (UCS Transformation Format)
• UTF-8 : 1 bajt dla ASCII,2 bajty dla znaków 8-bitowych i 3 bajty dla pozostałych
Hierarchia klas strumieniowych obejmuje tzw. klasy przedmiotowe to jest związane z konkretnym źródłem lub odbiorcą danych (tablica, łańcuch znaków, plik,) potok...)* oraz klasy przetwarzające (dekoracyjne), których zadanie polega na dostarczeniu dodatkowych uŜytecznych własności (nazywane teŜ filtrami).
Przykładem klasy dekoracyjnej jest DataInputStream umoŜliwiająca odczyt ze strumienia danych wszystkich podstawowych typów (readByte(), readFloat() ...) i stringów. „Odwrotne” usługi oferuje DataOutputStream.
* Nazywane równieŜ strumieniami ujścia danych
Klasy przedmiotowe i przetwarzające
Typy strumieni wejściowych
Interfejs dla klas dekoracyjnych FilterInputStream
Z potoku PipedInputStream
Z pliku FileInputStream
Ze String-u StringBufferInputStream
Z bufora w pamięci.
ByteArrayInputStream
Typy strumieni wyjściowych
Interfejs dla klas dekoracyjnych FilterOutputStream
Do potoku PipedOutputStream
Do pliku FileOutputStream
Uwaga! Nie ma!
String jest niezmienny
>>StringBufferOutputStream<<
Do bufora w pamięci.
ByteArrayOutputStream
Wejściowe klasy „dekoracyjne”
dekompresja InflaterInputStream
Pozwalają na
„zwracanie”
odczytanego znaku PushbackInputStream
PushbackReader
Zapewniają numerację wierszy
LineNumberInputStream LineNumberReader
Zapewniają buforowanie BufferedInputStream
BufferedReader
Pozwalają na
odczytywanie typów prostych.
DataInputStream
Wyjściowe klasy „dekoracyjne”
kompresja DeflaterOutputStream
Do wypisywania typów prostych w czytelny sposób)
PrintStream PrintWriter
Zapewniają buforowanie BufferedOutputStream
BufferedWriter
Pozwala na zapisywanie typów prostych.
writeFloat() ...
DataOutputStream
Łańcuchy strumieni
• Strumienie zwiększają swoje moŜliwości gdy wyjście jednego strumienia połączymy do wejścia innego tworząc łańcuch.
Przykład
File f = new File („phrases.utf”) ; FileReader r = new FileReader(f);
BufferedReader in = new BufferedReader(r);
String line = in.readLine() ; //unexpected result!
// System.getProperty(„file.encoding”) ;
Readers & Writers
• Z uwagi na operowanie kodami Unicode
(internacjonalizacja) biblioteki we/wy w języku Java uległy w wersji 1.1 gruntownej modyfikacji (rozbudowie)
• Są sytuacje gdy w programie uŜywa się zarówno klas bajtowych jak i znakowych wówczas
poŜyteczne mogą być klasy konwerujące:
• InputStream InputStreamReader Reader
• OutputStream OutputStreamWriter Writer
Przykład again
File f = new File („phrases.utf”) ;
FileInputStream is = new FileInputStream(f);
InputStreamReader in =
new InputStreamReader (is, „UTF8”);
BufferedReader in = new BufferedReader(in);
String line = in.readLine() ; // is ok
„Odpowiedniki znakowe” klas przedmiotowych
PipedWriter PipedOutputStream
PipedReader PipedInputStream
CharArrayWriter ByteArrayOutputStream
CharArrayReader ByteArrayInputStream
StringWriter Brak odpowiednika
StringReader StringBufferInputStream
FileWriter FileOutputStream
FileReader FileInputStream
Writer OutputStream
Reader InputStream
Odpowiedniki znakowe” klas dekoracyjnych
PushBackReader PushBackInputStream
StreamTokenizer(Reader) StreamTokenizer
LineNumberReader LineNumberInputStream
PrintWriter PrintStream
DataInputStream ! DataInputStream
BufferedWriter BufferedOutputStream
BufferedReader BufferedInputStream
FilterWriter FilterOutputStream
FilterReader FilterInputStream
RandomAccessFile
• Pliki zbudowane z rekordów o znanej długości, do których uzyskuje się dostęp przez zmianę połoŜenia wskaźnika (seek()) „obsługiwane” są przez klasę
RandomAccessFile
Kolekcje
W wielu programach deklaracje typu:
MyObject myReference;
są uprawnione, jednak często zwłaszcza przy bardziej skomplikowanych programach nowe obiekty są kreowane w oparciu o pewne kryteria znane dopiero w czasie wykonania. Wcześniej nie jest znana ani liczba, a niekiedy nawet typ
potrzebnych obiektów.
W pewnym stopniu problem rozwiązuje
wbudowany w Javie typ array.
Tablica (Array) to po prostu sekwencja obiektów lub wartości typu prostego opatrzona wspólną nazwą.
Wszystkie elementy tablicy muszą być tego samego typu! Tablice definiujemy uŜywając nawiasów
prostokątnych []:
int[] a1; // zapis naturalny: a1 jest typu tablica int
lub
int a1[]; // styl języka C
Alokacja elementów tablicy
Nie zaalokowano jeszcze miejsca dla tablicy, a1 jest referencją do tablicy.
MoŜna to zrobić za pomocą wyraŜenia inicjalizującego:
int[] a1 = { 1, 2, 3, 4, 5 };
int[] a1 = new [11] ; // The primitives inside //the array are automatically initialized to zero Weeble[] a;
a = new Weeble[] { new Weeble(), new Weeble() };
Tablice posiadają nieodłączne pole składowe length, które moŜna odczytywać (ale nie zmieniać) wskazujące ile elementów jest w tablicy (a1.length jest równe 5). Indeks tablicy zmienia się od 0 do length-1. Wyjście poza ten zakres generuje błąd czasu wykonania (exception).
Kontrola indeksów tablic jest jednym z powodów mniejszej efektywności środowiska Java, pozwala
natomiast uniknąć wielu nieprzewidzianych i trudnych do wykrycia błędów programu.
Uwaga: tablica jest obiektem, nawet jeśli elementy tablicy są typu prostego.
Object[] a; // uninitialized variable
Object[] b = new Object[5]; // Null references for(int i; i < b.length; i++)
b[i] = new Object() ;
Tablice wielowymiarowe
int[][] a1 = { { 1, 2, 3, }, { 4, 5, 6, }, };
int[][][] a2 = new int[2][2][4];
Klasa Arrays
W pakiecie java.util znajduje się klasa Arrays, w której zdefiniowano szereg uŜytecznych metod statycznych. Są to cztery następujące przeciąŜone (dla typów prostych i Object) grupy:
equals( ), - do porównania zawartości tablic;
fill( ), do wypełnienia tablicy określoną wartością (taka sama) sort( ), do sortowania tablic;
binarySearch( ), wyszukanie elementu w posortowanej tablicy.
Ponadto metoda asList( ) umieszcza elementy tablicy w kontenerze List.
W klasie System zdefiniowano uŜyteczną metodę słuŜącą do kopiowania zawartości tablic:
void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
Kopiowanie zawartości tablic
int[] i = new int[7];
int[] j = new int[10];
Arrays.fill(i, 47);
Arrays.fill(j, 99);
System.arraycopy(i, 0, j, 0, i.length); // "j = //[47, 47, 47, 47, 47, 47, 47, 99, 99, 99]„
Oczywiście kaŜde naruszenie zakresu indeksów spowoduje wygenerowanie wyjątku.
Dla tablic obiektów kopiowane są tylko referencje nie same obiekty!
Przykład
Porównywanie tablic metoda Arrays.equals()
Aby tablice były równe musza mieć taką samą liczbę elementów oraz odpowiadające sobie elementy muszą być równe w sensie metody equals() typu tych
elementów, a dla typów prostych w sensie metody equals() odpowiednich typów opakowujących.
Porównywanie elementów tablic.
Jednym z podstawowych celów projektowania
programów jest „separacja rzeczy, które się zmieniają od tych, które są niezmienne”.
Jako przykład rozwaŜmy problem sortowania. To co jest niezmienne to sam algorytm sortowania, natomiast zmienny jest sposób porównywania obiektów. Stąd zamiast wbudowywać te sposoby porównywania w róŜne procedury sortowania stosujemy rozwiązanie, w którym niezmienny fragment kodu wywołuje zmienną część (technika callback).
Java oferuje dwa sposoby porównywania obiektów.
Pierwszy oparty jest na metodzie wprowadzonej do klasy drogą implementacji interfejsu java.lang.Comparable. Interfejs ten definiuje tylko jedną metodę: int compareTo(Object o).
Metoda ta porównuje przekazany argumentem obiekt i zwraca wartość mniejszą od zera jeśli bieŜący obiekt jest mniejszy od argumentu, zero jeśli oba obiekty są równe i wartość dodatnią jeśli obiekt jest większy od argumentu.
Od implementacji metody compareTo() zaleŜy „co to oznacza, Ŝe obiekt jest mniejszy, równy, większy”. Jeśli klasa
implementuje interfejs Comparable moŜna posortować tablicę obiektów a:
2 sposoby porównywania
public class CompType implements Comparable { int i; int j;
public CompType(int n1, int n2) { i = n1;
j = n2;
}
public String toString() {
return "[i = " + i + ", j = " + j + "]"; } public int compareTo(Object rv) { int rvi = ((CompType)rv).i;
return (i < rvi ? -1 : (i == rvi ? 0 : 1));
} }
CompType[] a = new CompType[12] ; // …
Arrays.sort(a);
Co jeśli klasa nie implementuje interfejsu Comparable, lub implementacja nas nie satysfakcjonuje? Rozwiązaniem moŜe być zastosowanie wzorca projektowego strategy, zgodnie z którym zmienny kod umieszczany jest w oddzielnej klasie (strategy object). Następnie do niezmiennej części algorytmu przekazujemy ten obiekt, który umoŜliwia rozwiązanie problemu. Zgodnie z tym wzorcem moŜemy zaprojektować róŜne sposoby porównywania obiektów i uzupełniać nimi niezmienny algorytm. Przy sortowaniu definiujemy klasy implementujące interfejs Comparator, w którym zdefiniowano metody: compare( ) i equals( ). PoniewaŜ implementację metody equals( ) wszystkie klasy dziedziczą po klasie Object niezbędne jest jedynie zdefiniowanie metody compare( ).
2 sposób
Podsumowując, sortowanie tablic zawierających obiekty jest moŜliwe jeśli implementują one interfejs Comparable lub posiadają skojarzony z nim
Comparator.
W obecnych wersjach API języka Java (od 1.2) klasa String implementuje interfejs Comparable co umoŜliwia leksykograficzne sortowanie tablic stringowych (z uwzględnieniem wielkości liter).
Jeśli ta reguła sortowania nie jest adekwatna do danego zastosowania to definiujemy własną klasę komparatora np.
public class AlphabeticComparator implements Comparator { public int compare(Object o1, Object o2) {
String s1 = (String)o1;
String s2 = (String)o2;
return s1.toLowerCase().compareTo(s2.toLowerCase());
} }
Searching a sorted array
Once an array is sorted, you can perform a fast search for a particular item by using Arrays.binarySearch( ). However, it’s very important that you do not try to use binarySearch( ) on an unsorted array; the results will be unpredictable.
Arrays.binarySearch( ) produces a value greater than or equal to zero if the search item is found. Otherwise, it produces a negative value representing the place that the element should be inserted if you are maintaining the sorted array by hand. The value produced is
-(insertion point) - 1
Podsumowując tablice są podstawowym i najefektywniejszym sposobem przechowywania grupy obiektów (i jedynym sposobem dla typów prostych). Jeśli jednak nie jest znana z góry liczba obiektów lub potrzebujemy bardziej
wyrafinowanego sposobu przechowywania obiektów naleŜy skorzystać z biblioteki klas kontenerowych (Collections).
Pakiet utilities zawiera zbiór klas kontenerowych znanych jako kolekcje. Kontenery te (List, Set, Map) mogą
przechowywać wyłącznie obiekty i traktują je właśnie jako instancje klasy Object. Wartości typu prostego naleŜy przed umieszczeniem w kontenerze „opakować” (klasy Integer, Double, etc.).