AGENDA
W Javie do obsługi strumieni służą klasy z pakietu java.io
Klasy te można podzielić na grupy wg następujących kryteriów:
Klasy dla strumieni bajtowych
Żródło: Java Tutorial, JavaSoft
Klasy dla strumieni znakowych
Żródło: Java Tutorial, JavaSoft
UWAGA: Przy przetwarzaniu tekstowym należy korzystać ze strumieni znakowych, bowiem tylko one zapewniają poprawne przetwarzanie unikodu.
Nadklasy, z których wywodzą się wszystkie inne klasy strumieniowe (czyli nadklasy InputStream, OutputStream, Reader, Writer) są abstrakcyjne i zawierają deklaracje podstawowych metod przetwarzania strumieni, które podklasy winny implementować.
KLASY PRZEDMIOTOWE
(związane z konkretnym źródłem/odbiornikiem)
Ź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 |
Przykład:
kopiowanie plików bajt po bajcie.
import java.io.*;
public class Copy1 {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Syntax: in out");
System.exit(1);
}
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream(args[0]);
out = new FileOutputStream(args[1]);
int c = 0;
while ((c = in.read()) != -1) out.write(c);
} catch(FileNotFoundException exc) {
System.out.println("Plik " + args[0] + " nie istnieje.");
System.exit(1);
} catch(IOException exc) {
System.out.println(exc.getMessage());
System.exit(1);
} finally {
try {
if (in != null) in.close();
if (out != null) out.close();
} catch (IOException exc) { System.out.println(""+exc);
}
}
}
Przy tej okazji omówimy wyjątki.
KLASY PRZETWARZAJĄCE
(implementujące "abstrakcyjne" przetwarzanie)
Rodzaj przetwarzania | Strumienie znakowe | Strumienie bajtowe |
---|---|---|
Buforowanie | BufferedReader,
BufferedWriter |
BufferedInputStream,
BufferedOutputStream |
Filtrowanie | FilterReader,
FilterWriter |
FilterInputStream,
FilterOutputStream |
Konwersja: bajty-znaki | InputStreamReader,
OutputStreamWriter |
|
Konkatenacja | SequenceInputStream | |
Serializacja obiektów | ObjectInputStream,
ObjectOutputStream |
|
Konwersje danych | DataInputStream,
DataOutputStream |
|
Zliczanie wierszy | LineNumberReader | LineNumberInputStream |
Podglądanie | PushbackReader | PushbackInputStream |
Drukowanie | PrintWriter | PrintStream |
Komentarze:
FileReader fr = new FileReader("plik.txt");
// tu powstaje związek z fizycznym źródłem
BufferedReader br = new BufferedReader(fr); // tu dodajemy "opakowanie",
umożliwiające buforowanie
//... teraz wszelkie odwołania czytania itp. kierujemy do obiektu
br
Przykładowy program: stworzyć metodę umożliwiającą określenie czy w pliku podanym jako argument występuje którekolwiek z podanych słów.
Zarys czytania:
try {
String line;
FileReader fr = new FileReader(fname);
// fname jest nazwą pliku
BufferedReader br = new BufferedReader(fr);
while ((line = br.readLine()) !=
null) { // kolejny wiersz pliku: metoda readLine
...
// tu stwierdzenie,
czy w wierszu występuje któreś ze słów
}
br.close(); // zamknięcie strumienia
}
catch (IOException e) {
System.err.println(e);
}
Przy okazji tego programu użyjemy trzech ważnych klas Javy:
public class Search {
public boolean hasAnyWord(String fname, Hashtable wordtab) {
boolean result = false;
try {
String line;
FileReader fr = new FileReader(fname);
BufferedReader br = new BufferedReader(fr);
search:
while ((line = br.readLine())
!= null) {
StringTokenizer st = new
StringTokenizer(line, " ,.:;()\t\r\n");
while (st.hasMoreTokens())
{
String
word = st.nextToken();
if (wordtab.get(word)
!= null) {
result = true;
break search;
}
}
}
br.close();
}
catch (IOException e) {
System.err.println(e);
}
return result;
}
}
Jest tu jedna metoda - hasAnyWord - stwierdzająca, czy plik podany jako
jej pierwszy argument zawiera którekolwiek ze słów, zapisanych w tablicy
asocjacyjnej wordtab. Użycie tej tablicy przyspiesza wyszukiwanie, bowiem
słów może być dużo, a gdy są one zapisane jako klucze (jakichś) wartości
w tablicy asocjacyjnej, to stwierdzenie czy jakieś słowo w niej występuje
jest błyskawiczne: odwołanie wordtab.get(word) (wynik jest null jeśli nie
ma, jeśli jest dostajemy Object skojarzony z danym kluczem; tu nieważny).
A co to są słowa? Tu traktujemy je jako ciągi znaków nie zawierające
" ,.:;()\t\r\n" i używamy StringTokenizera do ich wyodrębnienia (konstruktor
StringTokenizer ma za argumentu String na którym ma dzialać oraz ograniczniki
definiujące jakie symbole będą wyodrębniane).
Testująca klasa ma następującą postać:
public class Test {
public static void main(String[] args) { // argumenty:
nazwa_pliku slowo1 slowo2 ... slowoN
if (args.length < 2) System.exit(1);
Object dummy = new Object();
Hashtable words = new Hashtable();
for (int i = 1; i<args.length; i++) words.put(args[i], dummy); // wartość nas nie obchodzi, tylko klucz
Search srch = new Search();
boolean result = srch.hasAnyWord(args[0],
words);
String msg = " nie zawiera żadnego ze słów:";
if (result) msg = " zawiera któreś ze słów:";
System.out.println("Plik "+args[0]+msg);
Enumeration en = words.keys();
// uzyskujemy wszystkie klucze tablicy
while (en.hasMoreElements()) {
// ... i przebiegamy je po kolei
String word = (String) en.nextElement();
System.out.println(word);
}
}
}
Java posługuje się znakami w formacie Unicode (UTF8). Są to - ogólnie
- wielkości 16-bitowe.
Środowiska natywne (np. Windows) najczęściej zapisują teksty jako sekwencje
bajtów (z przyjętą stroną kodową).
Jak pogodzić najczęściej bajtowy charakter plików natywnych ze znakowymi
strumieniami?
Otóż strumienie znakowe potrafią - niewidocznie dla nas - przekształcać
bajtowe źródła w znakowe strumienie i odwrotnie. "Pod pokrywką" tego procesu
znajdują się dwie klasy: InputStreamReader i OutputStreamWriter,
które dokonują właściwych konwersji w trakcie czytania/pisania.
Klasy te możemy wykorzystać również samodzielnie.
Jeśli w konstruktorach tych klas nie podamy enkodowania przy konwersjach
zostanie przyjęte domyślne.
Aby się dowiedzieć, jakie jest domyślne enkodowanie można użyć następującego
programiku:
public class DefaultEncoding {
public static void main(String args[])
{
String p = System.getProperty("file.encoding");
System.out.println(p);
}
}
W zależności od ustawień na danej platformie otrzymamy różne wyniki. Np. ibm-852 lub Cp852 (Latin 2) albo Cp1252 (Windows Western Europe / Latin-1).
Inna wersja konstruktorów pozwala na podanie enkodowania.
Napiszmy program wykonujący konwersje plików z-do dowolnych (dopuszczalnych
przez Javę) formatów kodowania.
Dopuszczalne symbole enkodowania można znaleźć na stronie:
http://java.sun.com/products/jdk/1.1/docs/guide/intl/encoding.doc.html
import java.io.*;
public class Convert {
public static void main(String[] args) {
if (args.length != 4) {
System.out.println("Syntax: in in_enc out out_enc");
System.exit(1);
}
String infile = args[0], // plik wejściowy
in_enc = args[1],
// wejściowa strona kodowa
outfile = args[2],
// plik wyjściowy
out_enc = args[3];
// wyjściowa strona kodowa
try {
FileInputStream fis = new FileInputStream(infile);
BufferedReader in = new BufferedReader(new InputStreamReader(fis,
in_enc));
FileOutputStream fos = new FileOutputStream(outfile);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(fos,
out_enc));
String line;
while ((line = in.readLine()) != null) {
out.write(line);
out.newLine();
// zapis znaku końca wiersza
}
in.close();
out.close();
}
catch (IOException e) {
System.err.println(e);
}
}
Przykładowe wykorzystanie do konwersji pliku zle.htm (zapisanego w Windows 1250) na plik dobrze.htm ( ISO-8859-2):
java Convert zle.htm Cp1250 dobrze.htm ISO8859_2
Abstrakcyjny strumień (np. InputStream) może być związany z zasobem
sieci, oznaczanym przez URL.
To co dostaniemy w rezultacie czytania takiego strumienia zależy od
tego w jaki sposób serwer definiuje przesyłanie informacji dotyczących
tego zasobu.
Dwa sposoby działania:
Przykład - czytanie z Internetu dokumentów html, których adresy
(np. w postaci: http://....) zapisane są w pliku podanym jako argument
programu.
import java.net.*; // konieczne
do posługiwania się klasą URL
import java.io.*;
import java.util.*;
public class URLReader {
public static void main(String[] args) throws Exception {
BufferedReader list = new BufferedReader(
new FileReader(args[0]));
String urlString;
while ((urlString = list.readLine()) != null) {
System.out.println("Trying to read "+
urlString);
readAndSave(new URL(urlString));
// tworzony nowy obiekt klasy URL
}
list.close();
System.exit(0);
}
static void readAndSave(URL url) throws Exception {
BufferedReader in = new BufferedReader(
new InputStreamReader(url.openStream()));
// url.openStream() zwraca InputStream
String fname = null;
StringTokenizer st = new StringTokenizer(url.getFile(),
"/");
while (st.hasMoreTokens()) fname = st.nextToken();
// pobieramy nazwę pliku pod którą ma być zachowany
BufferedWriter out = new BufferedWriter(new FileWriter(fname));
String s;
while ((s = in.readLine()) != null) {
out.write(s);
out.newLine();
}
in.close();
out.close();
}
}
Serializacja polega na zapisywaniu obiektów do strumienia.
Zapisywany jest pełny stan obiektu (w tym - rekursywnie - obiektów
składowych).
Nie są zapisywane pola statyczne oraz pola deklarowane
z identyfikatorem
transient.
Wykorzystanie:
Do zapisywania/odczytywania obiektów służą klasy ObjectOutputStream
oraz ObjectInputStream.
Przykład: tablica asocjacyjna kolorów, umożliwiająca odwoływanie się do kolorów przez nazwy.
import java.io.*;
import java.awt.*;
import java.util.*;
public class Kolory {
public static void main(String[] args) {
Hashtable c = new Hashtable();
c.put("black", Color.black);
c.put("blue", Color.blue);
c.put("red", Color.red);
c.put("yellow", Color.yellow);
c.put("white", Color.white);
try {
FileOutputStream out = new FileOutputStream("KOLORY.SER");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject(c);
s.flush();
}
catch(IOException exc) { System.out.println(exc);
}
}
W jakiejkolwiek innej aplikacji możemy teraz wybrać kolor z zapisanej tablicy, używając jego nazwy np.:
FileInputStream in = new FileInputStream("KOLORY.SER");
ObjectInputStream ois = new ObjectInputStream(in);
Hashtable ht = (Hashtable) ois.readObject(); // zapamiętany
obiekt Hashtable - odtworzony
Color c = (Color) ht.get(nazwa_koloru);
Aby obiekt jakiejś klasy mógł być zapisany do strumienia, klasa ta musi
implementować interfejs Serializable.
Standardowe klasy Javy implementują ten interfejs.
W naszych klasach musimy zapewnić tę implementację (co nie jest trudne,
bowiem interfejs jest pusty).
Przykład:
Serializowalna klasa Slist stanowi okno z listą i przyciskiem "Save",
który pozwala zapamiętać bieżący stan obiektu (zawartość listy, kolory,
pismo etc.) w pliku SLIST.SER.
import java.io.*;
import java.awt.*;
import java.util.*;
class Slist extends Frame implements Serializable, ActionListener {
List list = new List();
Button b = new Button("Save");
Slist() {
b.addActionListener(this);
add(list, "Center");
add(b, "South");
}
public List getList() { return list; }
public void actionPerformed(ActionEvent e) {
try {
FileOutputStream out = new FileOutputStream("SLIST.SER");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject(this);
s.flush();
}
catch(IOException exc) { System.out.println(exc);
}
}
}
Następująca aplikacja:
import java.io.*;
- uruchomiona z argumentami nazwa_koloru elt1 elt2 .. elt3 tworzy i pokazuje listę o tym kolorze i elementach
- uruchomiona bez żadnych argumentów wczytuje i pokazuje zapamiętany obiekt (lista, elementy, kolory)
public class Serial {
public static void main(String[] args)
throws IOException, ClassNotFoundException {
Slist sl = null;
if (args.length == 0) {
FileInputStream in = new
FileInputStream("SLIST.SER");
ObjectInputStream s = new
ObjectInputStream(in);
sl = (Slist) s.readObject();
}
else {
FileInputStream in = new
FileInputStream("KOLORY.SER");
ObjectInputStream ois =
new ObjectInputStream(in);
Hashtable ht = (Hashtable)
ois.readObject();
Color c = (Color) ht.get(args[0]);
sl = new Slist();
sl.getList().setBackground(c);
for (int i = 0; i < args.length
- 1; i++) sl.getList().add(args[i+1]);
}
if (sl != null) {
sl.pack();
sl.setVisible(true);
}
}
}