Skansholm: Kapitel 16
Du hittar alla programexempel här.
Jag listar några saker som man vill kunna göra med strömmar och IO.
Vi vill
Vi vill också
Så, man vill kunna läsa och skriva alla tänkbara olika typer av data. Dessutom vill man ha olika destinationer för läsning och skrivning: filer, terminal, interna datastrukturer, andra datorer på internet.
Javas lösning är en liten bygglåda för IO där man själv plockar ihop de bitar man behöver. Nackdelen är uppenbar; även ganska enkla saker blir krångliga att göra i Java (att läsa input från användaren till exempel). Fördelen är att man kan återanvända funktionaliteten i andra sammanhang. Man kan också ta fram egna komponenter som kan kombineras med de befintliga bitarna i bygglådan.
Strategin för att kombinera objekt på olika sätt beroende på vilken funktionalitet man vill ha kallas decorator pattern. Googla gärna.
import java.io.*; public class IO1 { public static void main(String[] args) throws IOException { String s = ""; InputStream istream = System.in; Reader r = new InputStreamReader(istream); BufferedReader br = new BufferedReader(r); s = br.readLine(); System.out.println("You typed: "+s); } }
System.in
är det som i andra sammanhang kallas standard input; den
ström av tecken som normalt ges av användaren.
System.in
är ett objekt av klassen InputStream
. InputStream
representerar en ström av bytes.
Reader
är en ström av Char
(Unicode). Vi konverterar en ström av bytes
till en ström av unicode med konstruktorn InputStreamReader
.
(Om vi ska vara lite mer noggranna: Vi skapar ett objekt av klassen
InputStreamReader
som procucerar Char
genom att läsa och konvertera
bytes.)
Om man vill läsa en hel rad måste man använda en buffrad läsare. På
nästa rad konverterar vi vår Reader
till en buffrad läsare.
Till sist läser vi en rad från användaren och skriver ut den.
$ javac IO1.java $ java IO1 kaka You typed: kaka $
public class LasFil1 { public static void main(String [] arg) throws IOException { InputStream is = new FileInputStream(arg[0]); OutputStream os = System.out; int i = 0; while ((i = is.read()) != -1) { os.write(i); } is.close(); os.close(); } }
Här läser vi från en fil, med klassen FileInputStream
. Filnamnet ges
som argument på kommandoraden. Utskrift sker till standard output,
System.out
.
Här kopierar vi en fil. Namnen på den existerande filen och den nya filen ges på kommandoraden.
public class Copy { public static void main(String [] arg) throws IOException { InputStream is = new FileInputStream(arg[0]); OutputStream os = new FileOutputStream(arg[1]); int i = 0; while ((i = is.read()) != -1) { os.write(i); } is.close(); os.close(); } }
Notera att FileInputStream
och FileOutputStream
är subklasser till
InputStream
och OutputStream
.
Javas standardbibliotek definierar fyra abstrakta klasser som representerar olika typer av strömmar.
Vi har två strömmar för läsning och skrivning av bytes:
InputStream
OutputStream
samt två strömmar för läsning och skrivning av char
(Unicode):
Reader
Writer
De abstrakta klasserna ingår förstås i Javas api: https://docs.oracle.com/en/java/javase/17/docs/api/index.html
Klasserna definierar operationer för läsning och skrivning (naturligtvis).
Andra ström-klasser ärver någon av dessa klasser.
Javas standardbibliotek definierar konkreta klasser som representerar olika typer av strömmar. Vi har till exempel
Exempel på olika typer av filter:
BufferedInputStream
, DataInputStream
, LineNumberInputStream
,
PushbackInputStream
ObjectInputStream
, ObjectOutputStream
: läser och skriver objekt!Vid testning kan det vara praktiskt att styra output till en array av byte. På samma sätt kan man även låta ett program läsa från en array av byte.
Följande program visar hur en array av byte kan användas som en ström, både vid läsning och vid skrivning.
ByteArrayTest.java
ByteArrayTest2.java
BinaryWrite.java
Ge namnet på filen du vill skriva till som argument vid kommandoraden.
BinaryRead.java
Ge namnet på filen du vill läsa från som argument vid kommandoraden.
Här läser och skriver vi binärt data som en ström av bytes. En int
är
fyra byte, tex.
I Java skapar man ofta strömmar från andra strömmar.
Exempel på konstruktorer som tar andra strömmar som argument.
BufferedReader(Reader in)
BufferedWriter(Writer out)
LineNumberReader(Reader in)
PushbackReader(Reader in)
DataInputStream(InputStream in)
PushbackInputStream(InputStream in)
DataOutputStream(OutputStream out)
Man kan också skapa egna filter.
Exempel: FilterReader
Det är en abstrakt klass som ärver klassen Reader
. Den har en
konstruktor, som tar en Reader
som argument. Den har också en
"protected" instansvariabel in
(den ström som filtreras). För övrigt
har den samma metoder som Reader.
För att konvertera från stora till små bokstäver räcker det om man definierar ett filter som överskuggar (overrides) metoderna för läsning.
Exempel: ToLowerCaseReader.java
, FilterDemo.java
Om vi har ett antal filter kan de kombineras enligt våra önskemål.
Egna filter kan kombineras med existerande,
Kan samma idé tillämpas i andra sammanhang?
Klasserna ObjectInputStream
och ObjectOutputStream
kan användas för
att läsa och skriva (nästan) vilka objekt som helst.
Vi har följande operationer:
void writeObject(Object o)
skriver ett objekt
Object readObject()
läser ett objekt
readObject
och writeObject
kräver att objektet tillhör en klass som
implementerar interfacet Serializable
.
Om in
och out
är av klasserna InputStream
och OutputStream
, skriv
ObjectInputStream sin = new ObjectInputStream(in); ObjectOutputStream sout = new ObjectOutputStream(out);
för att skapa strömmar som kan läsa och skriva objekt.
Programmen nedan testar att skapa ett objekt av klassen Person
,
skriver det till fil, och läser sen tillbaka det.
SeriWrite.java
, SeriRead.java