zapis i odczyt danych w plikach

Transkrypt

zapis i odczyt danych w plikach
Podstawy otwartych języków
programowania
Wyjątki i strumienie I/O
Wiktor Wandachowicz
Wyjątki
Podstawą filozofii Javy jest założenie, że:
źle sformułowany kod nie zostanie wykonany
●
●
●
Wyjątki są mechanizmem zabezpieczającym program
podczas jego działania – dynamicznie (w odróżnieniu od
błędów pojawiających się podczas kompilacji – przy
statycznym sprawdzaniu poprawności programu).
Stanowią element sięgający podstaw języka i szeroko
stosowany we wszelkich bibliotekach Java.
Lepiej jest przerwać wykonywanie programu, niż dopuścić
do nieoczekiwanych efektów wynikających z błędu.
Typowa obsługa wyjątków
●
●
●
●
●
●
Sytuacja błędna powoduje utworzenie nowego obiektu
wyjątku i rzucenie go.
Kod, który wyrzuca wyjątki można spróbować wykonać,
a pojawiające się wyjątki można łapać.
Złapany wyjątek można obsłużyć albo rzucić ponownie.
Jeśli kod w metodzie rzuca wyjątek (nie jest on obsłużony),
musi być to wyspecyfikowane w deklaracji metody.
Można także wskazać finalne operacje wykonywane
zawsze, niezależnie od tego czy wyjątek wystąpił, czy nie.
Wyjątki pochodzą od klasy java.lang.Throwable, jej
główne podklasy to: java.lang.Exception (najczęściej
używana), java.lang.RuntimeException oraz
java.lang.Error.
Kategorie wyjątków
●
●
●
Exception – do typowej obsługi wyjątków. Tworząc
własne klasy wyjątków najczęściej dziedziczy się je
właśnie z tej klasy.
RuntimeException oraz pochodne – określają wyjątki,
które mogą pojawić się podczas normalnego działania
maszyny wirtualnej. Nie trzeba deklarować ich w
definicjach metod, mogą pojawić się w każdej metodzie.
Przykłady: NullPointerException,
IndexOutOfBoundsException.
Error oraz pochodne – określają poważne problemy,
sytuacje, które nie powinny zdarzyć się przy normalnym
wykonaniu programu. Nie trzeba deklarować ich w
definicjach metod. Przykłady: VirtualMachineError,
AssertionError.
Łapanie wyjątków
●
●
Odbywa się z użyciem bloków try i catch.
– Pierwszy blok (try – spróbuj) obejmuje kod, który
może spowodować wyjątek.
– Drugi blok (catch – złap) określa jakie wyjątki chce
obsłużyć i zawiera kod z obsługą błędów.
Jeśli wyjątek został złapany, można m.in.
– wyświetlić informację o pochodzeniu błędu (jest to
najczęściej używane): ex.printStackTrace()
– odczytać komunikat błędu: ex.getMessage()
– odczytać źródło błędu: ex.getCause()
Łapanie wyjątków – przykład (1)
import java.io.*;
public class Imie {
static void odczytajImie() {
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
try {
System.out.print("Podaj imie: ");
blok try
String imie = in.readLine();
// może być wyjątek
System.out.print("Witaj " + imie + "!");
} catch (IOException e) {
e.printStackTrace();
lokalna obsługa wyjątku
}
}
}
public static void main(String[] args) {
odczytajImie();
}
Łapanie wyjątków – przykład (2)
import java.io.*;
specyfikacja wyjątku
public class Imie {
static void odczytajImie() throws IOException {
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
System.out.print("Podaj imie: ");
String imie = in.readLine();
// tu może być wyjątek
System.out.print("Witaj " + imie + "!");
}
public static void main(String[] args) {
try {
odczytajImie();
blok try
} catch (IOException e) {
e.printStackTrace();
}
}
}
obsługa wyjątku
Rzucanie wyjątków
●
●
Aby zasygnalizować błąd, należy utworzyć obiekt
wyjątku (podając przyczynę błędu).
Następnie należy rzucić wyjątek, z użyciem słowa
kluczowego throw:
long suma = 0;
int iloscNieujemnych = 0;
void dodajNieujemna(int liczba) throws IllegalArgumentException {
if (liczba > 0) {
suma += liczba;
iloscNieujemnych++;
} else {
throw new IllegalArgumentException("liczba <= 0");
}
}
Tworzenie własnych klas
wyjątków
●
●
●
Nowa klasa wyjątku powinna dziedziczyć z Exception.
Może zawierać dodatkowe pola i metody.
Powinna deklarować przynajmniej dwa konstruktory:
– bezparametrowy
– z jednym argumentem typu String
public class ProgramException extends Exception {
public ProgramException() {
super();
}
public ProgramException(String message) {
super(message);
}
}
Własne klasy wyjątków oraz
wiązanie wyjątków
●
Żeby móc używać mechanizmu wiązania wyjątków,
wskazane jest dodanie jeszcze dwóch konstruktorów:
–
–
z jednym argumentem typu Throwable
z argumentami typu String oraz typu Throwable
public class ProgramException extends Exception {
public ProgramException() {
super();
}
public ProgramException(String message) {
super(message);
}
public ProgramException(Throwable cause) {
super(cause);
}
public ProgramException(String message, Throwable cause) {
super(message, cause);
}
}
Wiązanie wyjątków
●
Wiązanie wyjątków (exception chaining) pozwala dowiedzieć
się jaka była oryginalna przyczyna powstania wyjątku w
przypadku, gdy wyjątek jest rzucany ponownie:
void openFile() throws ProgramException {
FileReader file = null;
try {
String fileName = selectFile();
file = new FileReader(fileName);
parseContents(file);
} catch (IOException ex) {
throw new ProgramException("Błąd odczytu pliku: "
+ ex.getMessage(), ex);
} catch (NumberFormatException ex) {
throw new ProgramException("Błędny format pliku: "
+ ex.getMessage(), ex);
} finally {
if (file != null) try {
file.close(); /* mamy wyjątek - zrób porządki i zamknij plik */
} catch (IOException ex) { /* pominięcie ew. błędów */ }
}
}
Efekt wiązania wyjątków
public class Program {
public static void main(String[] args) {
try {
openFile();
} catch (ProgramException ex) {
ex.printStackTrace();
}
}
> java Program
ProgramException: Błąd odczytu pliku: test.txt (No such file or directory)
at Program.openFile(Program.java:34)
at Program.main(Program.java:15)
Caused by: java.io.FileNotFoundException: test.txt (No such file or directory)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:106)
at java.io.FileInputStream.<init>(FileInputStream.java:66)
at java.io.FileReader.<init>(FileReader.java:41)
at Program.openFile(Program.java:30)
... 1 more
Dziedziczenie klas wyjątków
●
●
●
Kolejność zapisu sekcji catch ma znaczenie, ponieważ
przy wystąpieniu wyjątku są one przeglądane w
kolejności występowania. Pierwsze dopasowanie do klasy
wyjątku pomija pozostałe sekcje catch.
Pojawiają się błędy kompilacji, gdy obsługa wyjątków
klas bardziej ogólnych poprzedza obsługę wyjątków klas
pochodnych.
Kod zawarty w sekcji finally wykonywany jest zawsze,
niezależnie od tego czy wystąpił wyjątek, czy nie. Służy to
do czynności porządkowych, zwalniania zasobów, itp.
Podsumowanie
Aby stworzyć niezawodny system,
każdy jego komponent musi być niezawodny
●
●
Obsługa wyjątków jest kluczowym sposobem zwiększenia
niezawodności programów. Drugim sposobem jest
używanie testów jednostkowych i testów modułowych
(unit testing, module testing).
Java wymusza wiele aspektów związanych z wyjątkami,
co gwarantuje, że będą one spójnie używane zarówno
przez twórców jak i użytkowników bibliotek.
Podstawy otwartych języków
programowania
Strumienie wejścia/wyjścia
Wiktor Wandachowicz
Strumienie danych
Strumień jest abstrakcyjną reprezentacją
dowolnego źródła lub ujścia danych
●
●
●
●
Na podstawie dwóch logicznych operacji wyróżniamy
dwa typy strumieni:
– strumienie wejściowe (odczyt danych)
– strumienie wyjściowe (zapis danych)
Strumień jest zawsze stowarzyszony z jakimś źródłem
danych (odczyt) lub ujściem (zapis).
Wyróżniamy także strumienie bajtowe i znakowe.
W Javie obsługę strumieni zapewnia pakiet java.io
Strumienie
wejściowe i wyjściowe
strumień wejściowy
źródło
danych
informacja
odczyt
Program
strumień wyjściowy
Program
zapis
informacja
ujście
danych
Algorytmy odczytu i zapisu
Odczyt
otwórz strumień
jeśli jest jeszcze informacja [określane przez źródło]
odczytaj informację
zamknij strumień
Zapis
otwórz strumień
jeśli jest jeszcze informacja [określane przez program]
zapisz informację
zamknij strumień
Hierarchia strumieni
Strumienie
bajtowe
●
●
●
Strumienie
znakowe
Klasy strumieni są w pakiecie java.io
Przy nieudanych operacjach pojawiają się wyjątki klasy
java.io.IOException
Strumienie bajtowe i znakowe zapewniają praktycznie
taką samą funkcjonalność (API), różnią się głównie
obsługiwanymi typami danych.
Strumienie bajtowe
Dwa najbardziej ogólne strumienie bajtowe to:
●
InputStream – do odczytu, oraz
●
OutputStream – do zapisu ciągów bajtów.
InputStream
abstract int read()
OutputStream
abstract void write(int b)
int read(byte[] b)
void write(byte[] b)
int read(
byte[] b, int off, int len)
void write(
byte[] b, int off, int len)
long skip(long n)
void flush()
void close()
void close()
Strumienie znakowe
Podstawowymi strumieniami znakowymi są:
●
Reader
– do odczytu, oraz
●
Writer
– do zapisu ciągów znaków.
Reader
int read()
Writer
void write(int c)
int read(char[] cbuf)
void write(char[] cbuf)
abstract int read(char[]
cbuf, int off, int len)
abstract void write(char[]
cbuf, int off, int len)
long skip(long n)
void flush()
void close()
void close()
Rozszerzanie funkcjonalności
●
●
Zwiększenie funkcjonalności odbywa się przez użycie
wyspecjalizowanych strumieni. Korzystają one z innych
strumieni, aby wykonywać swoją pracę.
Robi się to przez opakowanie jednego typu strumienia
w inny (aby to zadziałało musi istnieć w wybranej klasie
strumienia odpowiedni konstruktor).
InputStream
int read(byte[] b)
InputStreamReader
int read(char[] cbuf)
BufferedReader
String readLine()
Odczyt standardowego wejścia
(Java 1.0 – 1.4)
import java.io.*;
public class PowielajLinie {
public static void main(String[] args) throws IOException {
System.out.println("Wpisuj linie tekstu. " +
"Koniec gdy pusta linia.");
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
String linia;
do {
linia = in.readLine();
System.out.println(linia);
} while (linia.length() > 0);
}
}
InputStream
Odczyt standardowego wejścia
(Java 5, 6)
import java.io.*;
import java.util.Scanner;
public class PowielajLinie2 {
public static void main(String[] args) {
System.out.println("Wpisuj linie tekstu. " +
"Koniec gdy pusta linia.");
Scanner sc = new Scanner(System.in));
String linia;
do {
linia = sc.nextLine();
System.out.println(linia);
} while (linia.length() > 0);
}
}
InputStream
Odczyt i zapis plików
●
Żeby otworzyć plik, wystarczy stworzyć obiekt wybranej
klasy, podając nazwę pliku w postaci łańcucha znaków
lub obiektu klasy File.
– FileInputStream – odczyt pliku binarnego
– FileOutputStream – zapis pliku binarnego (*)
– FileReader – odczyt pliku znakowego
– FileWriter – zapis pliku znakowego (*)
(*) możliwe otwarcie pliku do dopisywania, bez usuwania
poprzedniej zawartości
●
●
Otwarty strumień można opakować w wybrany strumień
wyspecjalizowany
Po zakończeniu strumień należy zamknąć – close()
Klasa File
●
NIE JEST to plik – służy raczej jako opis ścieżek do
plików lub katalogów (klasa ta równie dobrze mogłaby
nazywać się FilePath):
File plik = new File("plik.txt");
FileReader in = new FileReader(plik);
●
Przy użyciu tej klasy można także m.in.
– sprawdzać informacje o pliku: isDirectory(), isFile(),
canRead(), canWrite(), exists(), length(),
lastModified()
– wykonywać operacje na plikach i katalogach:
delete(), mkdir(), mkdirs(), renameTo()
– odczytywać zawartość katalogu: list(), listFiles()
Strumienie specjalizowane
●
●
●
●
Zamiana strumieni bajtowych na znakowe:
– InputStreamReader / OutputStreamWriter
Odczyt i zapis z uwzględnieniem znaków końca linii:
– BufferedReader – readLine()
– BufferedWriter – newLine()
Strumień z buforem (większa wydajność):
– BufferedInputStream / BufferedOutputStream
Przeciążone metody println():
– PrintStream / PrintWriter
Strumienie specjalizowane
●
Odczyt i zapis tablic bajtów lub znaków (w pamięci):
–
CharArrayReader / CharArrayWriter
Odczyt i zapis łańcuchów napisów (w pamięci):
– StringReader / StringWriter
Odczyt i zapis plików (bajtowo i znakowo):
– FileInputStream / FileOutputStream
– FileReader / FileWriter
Serializacja obiektów:
– ObjectInputStream / ObjectOutputStream
–
●
●
●
ByteArrayInputStream / ByteArrayOutputStream
Klasa Osoba (JavaBean)
public class Osoba {
// opcjonalny dodatkowy konstruktor
private String imie;
// (łatwiejsze tworzenie obiektów)
public Osoba(String imie, String nazwisko)
private String nazwisko;
this.imie = imie;
public Osoba() {
this. nazwisko = nazwisko;
}
}
public String getImie() {
return imie;
}
public void setImie(String noweImie) {
this.imie = noweImie;
}
public String getNazwisko() {
return nazwisko;
}
public void setNazwisko(String noweNazwisko) {
this.nazwisko = noweNazwisko;
}
}
{
Zapis i odczyt danych w pliku
import java.util.*;
import java.io.*;
public class ZapisOdczyt {
private static List<Osoba> osoby = new ArrayList<Osoba>();
public static void main(String[] args) throws IOException {
osoby.add(new Osoba("Jan", "Kowalski"));
osoby.add(new Osoba("Adam", "Nowak"));
zapisz();
osoby.clear();
odczytaj();
wyswietl();
}
private static void wyswietl() {
int nr = 1;
for (Osoba o : osoby) {
System.out.println(" " + (nr++) + ". " +
o.getImie() + " " + o.getNazwisko());
}
}
Zapis i odczyt danych w pliku
private static void zapisz() throws IOException {
PrintWriter plik = new PrintWriter(
new BufferedWriter(new FileWriter("dane.txt")));
for (Osoba o : osoby) {
plik.println(o.getImie() + ":" + o.getNazwisko());
}
plik.close();
}
}
private static void odczytaj() throws IOException {
BufferedReader plik = new BufferedReader(
new FileReader("dane.txt"));
while (plik.ready()) {
String s = plik.readLine();
String[] dane = s.split(":");
osoby.add(new Osoba(dane[0], dane[1]));
}
plik.close();
}
Serializacja
Serializacja pozwala zamienić obiekt na
sekwencję bajtów, w sposób umożliwiający
później wierne odtworzenie jego zawartości
●
●
●
Inna nazwa to trwałość (ang. persistence) – cecha
oznaczająca, że życie obiektu nie jest ograniczone tylko
do czasu działania programu.
Format binarny, skuteczny również w sieci pomiędzy
komputerami działającymi pod różnymi JVM.
Dużo klas z bibliotek Javy jest zmodyfikowanych tak, że
wspierają serializację (np. kolekcje).
Zastosowania serializacji
●
●
●
Trwałość – zapisanie obiektów na dysk oraz
odtworzenie przy kolejnym uruchomieniu programu.
Zdalne wywoływanie metod między komputerami
poprzez RMI (ang. Remote Method Invocation). Pozwala
to obiektowi istniejącemu na jednym komputerze
zachowywać się tak, jakby istniał na drugim komputerze.
Serializacja jest używana do przekazywania
argumentów przy wywołaniu metod odległego obiektu
oraz do zwracania wartości.
Komponenty JavaBeans – elementy z których buduje
się interfejs użytkownika. W trakcie projektowania stan
komponentu jest serializowany, a przy starcie programu
jest on odtwarzany.
Sposoby realizacji
Serializację typowo realizuje się przez:
1. Implementację interfejsu Serializable w klasie,
która ma być serializowana (nie ma żadnych metod,
jest „znacznikiem”).
Można także kontrolować przebieg serializacji przez:
2. Implementację w klasie interfejsu Externalizable
oraz jego dwóch metod: writeExternal() – zapis
obiektu i readExternal() – odczyt obiektu, musi być
też dostępny publiczny konstruktor domyślny.
3. Implementację w klasie interfejsu Serializable oraz
dodatkowo (!) dwóch prywatnych metod
writeObject() i readObject().
Działanie
●
●
●
●
●
Żeby zserializować obiekt, trzeba utworzyć strumień
ObjectOutputStream i wywołać jego metodę
writeObject() podając obiekt do serializacji.
Może się tu pojawić wyjątek IOException.
Żeby odtworzyć obiekt, trzeba utworzyć strumień
ObjectInputStream, wywołać jego metodę
readObject() i dokonać rzutowania (readObject()
zwraca bowiem obiekt klasy Object).
Mogą się tu pojawić wyjątki IOException oraz
ClassNotFoundException.
Słowo kluczowe transient pozwala wskazać, które
składowe obiektu mają nie być serializowane.
Przykład serializowania
obiektów
import java.io.*;
class Wezel implements Serializable {
String nazwa;
/* ... */
}
public class Serializacja {
Wezel korzen = new Wezel();
/* ... */
void zapiszWezly() throws IOException {
/* ... utworzenie strumienia wyjściowego ... */
ObjectOutputStream out =
new ObjectOutputStream( /*strumień wyj.*/ );
out.writeObject(korzen);
/* ... zamknięcie strumienia wyjściowego ... */
}
Przykład odtwarzania obiektów
/* ... */
void przywrocWezly() throws
IOException, ClassNotFoundException {
/* ... utworzenie strumienia wejściowego ... */
ObjectInputStream in =
new ObjectInputStream( /*strumień wej.*/ );
// odczytanie obiektu i rzutowanie do
// odpowiedniego typu
korzen = (Wezel) in.readObject();
}
}
/* ... zamknięcie strumienia wejściowego ... */