|
1. Input- og OutputStreams
|
Datakilden er sekvens af bytes
|
Alle Input-/OutputStreams ser på datakilden som en sekvens af
bytes. De to abstrakte klasser InputStream og OutputStream er superklasser
for en lang række streams. Selv har de kun få metoder, men
deres subklasser realiserer forskellige måder at arbejde med datakilden.
De er ofte brugt som et abstrakte interfaces af andre streams, der kan
hente deres data fra datakilden gennem en anden stream uden at vide
konkret hvilken slags der er tale om.
|
|
1.1 InputStream
|
| En InputStream henter data fra datakilden og sender dem til client.
|
Figur 1:
Data fra datakilden gennem InputStream
|
|
| InputStream giver naturligvis mulighed for at læse bytes fra datakilden.
Dette kan gøres med en af følgende tre metoder:
|
|
int read()
int read( byte[] buffer )
int read( byte[] buffer, int offset, int length ) |
|
| Den parameterløse version er erklæret abstract og er dermed
den der gør selve klassen abstract. Alle andre metoder i InputStream
er som et minimum implementeret med stubbe. Metoden returnerer den næste
byte fra datakilden som int. Det betyder at byten behandles som 8 bit
uden fortegn, og derfor ikke som den primitive datatype byte. De mulige
værdier bliver derfor fra 0 til 255. Hvis der ikke er flere bytes
tilbage i datakilden returneres -1.
|
| De to sidste versioner af read, returnerer data i et array henholdsvis
et del-array, som er givet som parameter. I begge tilfælde indlæses
der det antal bytes der er plads til i arrayet henholdsvis del-arrayet.
Metoderne returnerer hvor mange bytes der reelt blev indlæst; hvilket
kan være mindre end pladsen tillod.
|
|
1.1.1 Del-arrays
|
| Del-arrays anvendes i forbindelse med en del streams. Et del-array
angives ved arrayet, et offset og en længde. F.eks. følgende
array.
|
Figur 2:
Del-array
|
|
| En del af arrayet er markeret lysere, det er det del-array vil vi arbejde
med. Det angives med offset 2, der er index for første position
i arrayet, og længden 5, der er længden af del-arrayet.
|
| Hvis vi derfor ønskede at indlæse dette del-array fra
en InputStream kunne vi gøre det med:
|
|
int antal;
antal = s.read( vorTabel, 2, 5 );
System.out.println( "Indlæst " + antal + " bytes" ); |
|
| Hvor antal efterfølgende ville indeholde antallet af bytes det
lykkedes at læse fra datakilden.
|
|
1.1.2 Blokkering
|
| Alt efter hvilken datakilde der gemmer sig bag InputStreams interface
kan den parameterløse read blokkere, dvs. den ikke returnerer før
datakilden kan levere en byte. Dette gør sig f.eks. gældende
hvis der er tale om en netværksforbindelse. Man har derfor mulighed
for at anvende metoden:
|
|
|
| til at undersøge hvor mange bytes der er klar fra datakilden.
F.eks. kunne man betinge kaldet af read for at undgå blokkering:
|
Source 1:
Sikring mod blokkering
|
if ( s.available() > 0 )
next = s.read();
else
... |
|
| En anden metode der beskæftiger sig med bytes ud fra en mængde-betragtning
er:
|
|
|
| Metoden prøver at overspringe de næste n bytes, og returnerer
hvor mange det lykkedes at overspringe. Metoden blokkerer derfor ikke.
|
|
1.1.3 Mærker
|
| Det er muligt at sætte et mærke, for senere at vende tilbage
til et sted i datastrømmen og gentage læsningen. Man kan
kun sæt ét sådant mærke, hvilket gøres
med metoden:
|
|
|
| Når metoden kaldes, vil InputStream huske positionen og hvis
man senere kalder metoden:
|
|
|
| vil den repositionere sig hvor mærket blev sat, og man kan gentage
læsningen af de bytes man tidligere har haft lejlighed til at læse.
|
| Fra starten er mærket placeret på første byte. Hvis
man kalder reset uden at have kaldt mark tidligere, kommer man derfor
tilbage til starten af datastrømmen og kan læse det hele
igen.
|
| Implementeringen af mærker kan være problematisk alt efter
hvilken datakilde dergemmer sig bag InputStream interfacet. Derfor behøver
en stream ikke understøtte mærker. Til at angive om man understøtter
mærker, findes følgende metode:
|
|
|
| Hvis man er i tvivl bør man derfor kalde denne metode for at
få vished om hvorvidt mærker understøttes.
|
| Som man måske bemærkede vedrørende mark, så
tager den en parameter. Denne parameter vedrører også vanskeligheden
i at tilbyde mærker. Parameteren angiver hvor mange bytes frem (fra
mærket) streamen er forpligtiget til at understøtte en tilbagevenden
til mærket. Når man har læst ud over denne afstand kan
man ikke komme tilbage ved at kalde reset. Denne udformning af mark overlader
ansvaret for effektiviteten til client, da det er op til den ikke at fråse.
|
| Den sidste metode i InputStream er:
|
|
|
| men den vil vi i det følge kun berøre i forbindelse med
filer.
|
|
1.2 OutputStream
|
| En OutputStream modtager data fra client og sender dem videre til en
destination.
|
Figur 3:
Data til destinationen gennem OutputStream
|
|
| Svarende til InputStream's tre read-metoder har OutputStream tre write-metoder:
|
|
void write()
void write( byte[] buffer )
void write( byte[] buffer, int offset, int length ) |
|
| Den parameterløse version er igen den der gør klassen
abstract. Den tager en int som parameter, der maskes til en byte før
den sendes til destinationen.
|
| Analogt til InputStream's to sidste read-metoder, arbejder de to sidste
versioner af write med henholdsvis et array og et del-array. Data hentes
fra disse parametre og sendes til destinationen.
|
| OutputStream understøtter ikke mærker. Man kunne i princippet
forestille sig mærker anvendt på den måde, at man kunne
vende tilbage og overskrive data man allerede havde skrevet.
|
| Alt efter hvilken stream der gemmer sig bag OutputStream's interface,
kan der være anvendt en buffer i implementationen. Vi skal se nærmere
på buffere i forbindelse med BufferedInput-/OutputStream. Til at
gennemtvinge en tømning af bufferen findes metoden:
|
|
|
| Metoden er naturligvis virkningsløs hvis der ikke anvendes en
buffer i implementationen af streamen.
|
| Endelig har OutputStream en close-metode svarende til InputStream's.
Vi vil ligeledes kun berøre close i forbindelse med filer.
|
|
1.3 ByteArrayInput-/OutputStream
|
Datakilde og destination er et array af bytes
| ByteArrayInputStream og ByteArrayOutputStream bruger et array af bytes
som datakilde/destination. datakilden/destionationen er derfor meget konkret,
da den ikke er en anden InputStream, men derimod den mest primitive sekventielle
datastruktur der findes: et array.
|
|
1.3.1 ByteArrayInputStream
|
| ByteArrayInputStream har to konstruktorer:
|
|
ByteArrayInputStream( byte[] buffer )
ByteArrayInputStream( byte[] buffer, int offset, int length ) |
|
| De tager begge datakilden som parameter, henholdsvis et array eller
et del-array af bytes. Man skal være opmærksom på at
der ikke sker nogen kopiering af datakilden, hvilket åbner mulighed
for at man kan ændre i den mens man læser fra den.
|
| Mærker er understøttet. I den forbindelse har reset en
speciel effekt hvis man har taget udgangspunkt i et del-array. Hvis man
ikke har sat nogen mærker vil et kald af reset bringe os tilbage
til elementet med index 0, også selv om offset var angivet til
en værdi større end 0. Dette giver adgang til elementer før
del-arrayet, mens det ikke er muligt at få adgang til elementer
på positioner efter del-arrayet. Om dette er man kan springe fra
del-arrayet og ud i arrayet på denne måde er en fejl der er
blevet ophævet til feature er uvist!
|
|
1.3.2 ByteArrayOutputStream
|
| ByteArrayOutputStream er naturligvis modstykket til ByteArrayInputStream,
og de fleste metoder er da også i tråd med dette.
|
| Der er to konstruktorer:
|
|
ByteArrayOutputStream()
ByteArrayOutputStream( int size ) |
|
| Parameteren size angiver startstørrelsen på det array
som bruges som intern datarepræsentation (default er 32). Hvis arrayet
bliver for lille "udvides" det på samme måde som
java.util.Vector gør. Altså på samme måde et
dynamisk tilbud til client, som implementeres med en statisk datastruktur.
Behageligt at arbejde med, men måske ikke det mest effektive i større
målestok.
|
| Interfacet for ByteArrayOutputStream er udvidet med en række
metoder. Først og fremmest er der:
|
|
|
| der returneres ikke arrayets længde, men hvor mange bytes der
er skrevet til arrayet.
|
| Hvis man skulle fortryde de data man har sendt til arrayet, kan man
slette dem alle med:
|
|
|
| Hvis man ønsker at få arrayet efter endt påfyldning
af data, kan man bruge
|
|
|
| der returnerer et nyt array af bytes, indeholdende de data der står
i den interne repræsentation. Bortset fra kopieringen over i et
nyt array, kommer ByteArrayOutputStream dermed til at fungere som en Builder
med toByteArray som getProduct-metode (se evt. Builder pattern).
|
| Specielt til testformål kan metoden toString være nyttig.
Der returneres en String, hvor de enkelte bytes er oversat til tegn.
|
|
1.3.3 Eksempel
|
| Følgende eksempel illustrerer hvordan ByteArrayInputStream og
ByteArrayOutputStream kan anvendes:
|
Source X:
Anvendelse af ByteArray-InputStream og ByteArray-OutputStream
|
import java.io.*;
class TestByteArrayStreams {
public static void main( String[] argv ) {
ByteArrayOutputStream output = new ByteArrayOutputStream( 3 );
for ( byte b=65; b<91; b++ )
output.write( b );
System.out.println( output.size() );
System.out.println( output.toString() );
ByteArrayInputStream input = new ByteArrayInputStream( output.toByteArray() );
output.reset();
System.out.println( output.size() );
input.skip( 20 );
while ( input.available() > 0 )
System.out.print( input.read() + " " );
System.out.println();
}
} |
26
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0
85 86 87 88 89 90 |
|
| Værdierne 65 til 90 er valgt fordi de er ASCII-værdierne
for de store bogstaver i det engelske alfabet. Der startes ud med et lille
internt array på længde 3 for at illustrere at det "udvides"
ved flere værdier.
|
|
1.3 FilterInput-/OutputStream
|
| FilterInputStream og FilterOutputStream er kun Bridges til InputStream
henholdsvis OutputStream (se evt. Bridge pattern).
|
| Selvom man kan lave instanser af dem begge er deres rolle i virkeligheden
at være superklasser for en række streams der ikke selv har
data men blot er delegerende streams til og fra en anden stream. Disse
subklasser realiserer en udvidet funktionalitet i forhold til det grundlæggende
interface fra InputStream og OutputStream.
|
|
1.3.1 DataInput-/OutputStream
|
| Idéen med DataInputStream og DataOutputStream er at gøre
det nemt at arbejde med de primitive datatyper. De har en række
af read- og write-metoder for hver af disse typer.
|
| Disse metoder er en implementation af interfacene DataInput og DataOutput.
|
|
1.3.1.1 DataInput
|
| Dette interface specificerer en række metoder der retter sig
mod de primitive typer. Det er:
|
|
boolean readBoolean()
byte readByte()
char readChar()
double readDouble()
float readFloat()
int readInt()
long readLong()
short readShort() |
|
| Ud over disse er der også:
|
|
int readUnsignedByte()
int readUnsignedShort() |
|
| der er nyttige, hvis man vil arbejde med bytes uden fortegn (en short
er to bytes) og ikke som i java, hvor alle primitive numeriske typer regnes
med fortegn.
|
| To andre metoder er
|
|
void readFully( byte[] buffer )
void readFully( byte[] buffer, int offset, int length ) |
|
| der er blokkerende read-metoder, der kun returnerer når arrayet
henholdsvis del-arrayet er fyldt med data fra datakilden, eller hvis slutningen
af streamen nås (f.eks. eof) eller der opstår en IOException.
|
| metoden
|
|
|
| der bruges til at læse tekst i et modificeret UTF-8 format. Vi
skal ikke her se på dette format, men jeg vil anbefale, at man kun
bruger det på data der er fremkommet ved den tilsvarende metode
|
|
void writeUTF( String s ) |
|
| i DataOutputs interface.
|
| endelig er der:
|
|
|
| der er analog til skip i InputStreams interface og derfor er overflødig
i den sammenhæng.
|
|
1.3.1.2 DataOutput
|
| DataOutput har den tilsvarende write-metode for de primitive typer
og modificeret UTF-8.
|
| Der findes ingen metoder til byte og short uden fortegn, men disse
ville heller ikke være relevante.
|
| Ligeledes er der ingen blokkerende fully-metoder, da de almindelige
write-metoder med array henholdsvis del-array af bytes opfylder behovet
for denne funktionalitet.
|
|
1.3.2 DataInputStream
|
| Ud over interfacet fra FilterInputStream (nedarvet uændret fra
DataInputStream) og DataInput supplerer DataInputStream kun selv med en
enkelt static metode
|
|
static String readUTF( DataInput in ) |
|
| der kan bruges til at læse modificeret UTF-8 fra et DataInput.
|
|
1.3.3 DataOutputStream
|
| Derimod har DataOutputStream selv en række metoder der rækker
ud over de to interfaces fra FilterOutputStream (nedarvet uændret
fra DataOutputStream) og DataOutput. Det er
|
|
|
| der fortæller hvor mange bytes der pt. er skrevet ud på
streamen, og
|
|
|
| der tømmer en evt. buffer ved at kalde videre til OutputStream.
|
|
1.3.2 BufferedInput-/OutputStream
|
| BufferedInputStream og BufferedOutputStream arbejder begge med en intern
buffer, men så hører ligheden også op. Motivationen
for bufferen er vidt forskellig.
|
|
1.3.2.1 BufferedInputStream
|
| Idéen med en buffer i BufferedInputStream er at understøtte
en InputStream med mærker, hvis den ikke selv gør det.
|
| Mærkerne implementeres ved at man gemmer alle indlæste
bytes, siden sidste mærke blev sat, i en buffer så man på
den måde kan understøtte reset. Bufferen droppes dog hvis
læsningen når ud over den grænse man specificerer i
mark-kaldet.
|
| Det hele er rimelig enkelt, men bevirker at BifferInputStream adapter
InputStream, så man opnår den øgede funktionalitet
(se evt. Adapter pattern).
|
| BufferedInputStream har to konstruktorer:
|
|
BufferedInputStream( InputStream in )
BufferedInputStream( InputStream in, int size ) |
|
| in er den InputStream der skal adaptes, mens size giver mulighed for
selv at bestemme den initielle størrelse af bufferen, der internt
er et array af bytes. Default er 2 KB.
|
| Hvis bufferen bliver for lille reallokerer den á la Vectors
implementation.
|
|
1.3.2.2 BufferedOutputStream
|
| Idéen med en buffer i BufferedOutputStream er at beskytte en
OutputStream mod mange små stykker data og i stedet samle passende
portioner i en buffer før OutputStream besværes med dem.
|
| Om det er hensigtsmæssigt at anvende BufferedOutputStream afhænger
naturligvis af hvad der gemmer sig bag OutputStreams interface.
|
| BufferedOutputStream har to konstruktorer, der ligner BufferedInputStreams:
|
|
BufferedOutputStream( OutputStream in )
BufferedOutputStream( OutputStream in, int size ) |
|
| out er naturligvis den OutputStream der skal adaptes, mens size er
størrelsen på bufferen. Default er ½ KB.
|
| Til forskel fra BufferedInputStream sker der ingen udvidelse af bufferen
hvis den løber fuld. I stedet tømmes den ud på OutputStream.
En sådan tømning sker naturligvis også hvis man kalder
metoden flush.
|
|
|
|
|
|
|
|
|
|
|
|
1.4 PipedInput-/OutputStream
|
| PipedInputStream og PipedOutputStream bruges til at sende data mellem
threads. Afsenderen har en PipedOutputStream og modtageren har en PipedInputStream.
De to streams er associerede. Det er et simplificeret Forwarder-Receiver
pattern uden marshalling, hvor bytes sendes mellem processer (se evt.
Forwarder-Receiver pattern).
|
|
1.4.1 PipedInputStream
|
| PipedInputStream har en cirkulær buffer på 1 KB, som
den opbevarer data i efterhånden som de ankommer. Modtagelsen af
bytes sker ved at PipedPotputStream kalder en protected metode receive,
der skriver i bufferen. Hvis bufferen løber fuld blokkerer denne
metoder og venter på at der bliver plads.
|
| PipedInputStream har to konstruktorer:
|
|
PipedInputStream()
PipedInputStream( PipedOutputStream src ) |
|
| Default-konstruktoren initialiserer en PipedInputStream, der andnu
ikke er forbundet med nogen PipedOutputStream.
|
| Den anden forbinder ved initialiseringen instansen til den PipedOutputStream,
der angives som parameter. Man skal i den forbindelse være opmærksom
på at forbindelsen oprettes gensidig. Man skal derfor ikke efterfølgende
tilmelde PipedInputStream hos PipedOutputStream, der klarer de selv blot
den ene bliver tilmeldt hos den anden.
|
| Hvis man venter med at tilmelde en PipedOutputStream, så gøres
det senere med metoden:
|
|
void connect( PipedOutputStream src ) |
|
| Når en PipedInputStream først er blevet forbundet med
en PipedOutputStream kan den ikke genbruges i forbindelse med en anden
PipedOutputStream. Man kan altså ikke lade connect på PipedInputStreamen
igen. Det omvendte gør sig ikke gældende, en PipedOutputStream
kan godt genbruges i forbindelse med en anden PipedInputStream, som altså
skal være "ubrugt". En PipedOutputStream har flere liv!
|
| Man kan se hvor mange bytes der står og venter i bufferen med
et kald af available.
|
| read.metoderne blokkerer alle, hvis der ikke er mindst én byte
at læse fra bufferen. Dette gælder altså også
de read-metoder der læser til et array henholdsvis del-array.
|
| read-metoderne kaster naturligvis en IOException hvis der opstår
en I/O-fejl, men i den forbindelse regnes det også som en I/O-fejl
hvis den tråd der har PipedOutputStreamen terminerer uden at have
kaldt close, og bufferen løber tom. I denne situation blokkeres
altså ikke selvom bufferen løber tom. Bemærk at en
thread der har en PipedOutputStream bør kalde close på den
før den selv terminerer.
|
|
1.4.2 PipedOutputStream
|
| PipedOutputStream har analogt konstruktorer svarende til PipedInputStreams
|
|
PipedOutputStream()
PipedOutputStream( PipedInputStream src ) |
|
| og de fungerer fuldstændig analogt, blot set fra sender-siden.
|
| Også connect-metoden er svrende til PipedOutputStreams.
|
| Som nævnt overfor blokkerer receive hvis bufferen løber
fund, derfor kan write-metoderne blokkere i denne situation og man har
ingen mulighed for at undgå det. I forbindelse med at receive blokkerer
kaldet, kalder den med et sekunds mellemrum notifyAll i et forsøg
på at vække en eller anden den forhåbentlig vil læse
fra bufferen så der bliver plads.
|
| Man kan selv gøre det inden man evt. foretager et blokkerende
write-kald ved at kalde flush på PipedOutputStream, der får
PipedInputStream i den adnen ende til at udføre notifyAll.
|
|
1.4.3 Eksempel
|
| Lad os se et eksempel på to threads der kommunikerer med hinanden
i det klassiske Producer-Consumer pattern.
|
|
import java.io.*;
class Producer extends Thread {
private PipedOutputStream output;
public Producer( PipedOutputStream out ) {
output = out;
}
public void run() {
try {
for ( byte b=0; b<10; b++ )
output.write( b );
}
catch ( IOException e ) {
System.out.println( "[Producer] I/O fejl" );
}
}
} |
import java.io.*;
class Consumer extends Thread {
private PipedInputStream input;
public Consumer( PipedInputStream in ) {
input = in;
}
public void run() {
try {
while ( true )
System.out.println( "[Consumer] " + input.read() );
}
catch ( IOException e ) {
System.out.println( "[Consumer] I/O fejl" );
}
}
} |
import java.io.*;
class TestPipedStreams {
public static void main( String[] argv ) {
PipedOutputStream output=null;
PipedInputStream input=null;
try {
output = new PipedOutputStream();
input = new PipedInputStream( output );
}
catch ( IOException e ) {
System.out.println( "[TestPipedStreams] I/O fejl" );
System.exit( 1 );
}
new Consumer( input ).start();
new Producer( output ).start();
}
} |
[Consumer] 0
[Consumer] 1
[Consumer] 2
[Consumer] 3
[Consumer] 4
[Consumer] 5
[Consumer] 6
[Consumer] 7
[Consumer] 8
[Consumer] 9
[Consumer] I/O fejl |
|
| Bemærk at vi her lidt brutalt anvender den IOException der kastes
når tråden med PipedOutputStream terminerer uden at kalde
close og bufferen løber tom, til også at stoppe Consumeren.
|
|
1.5 Specielle InputStreams
|
| Ved specielle InputStreams forstå dem der ikke har en korresponderende
OutputStream.
|
|
1.5.1 SequenceInputStream
|
| Idéen med SequenceInputStream er at sammensætte (concatenere)
flere InputStreams. Når en stream slutter, starter den næste
osv.
|
| SequenceInputStream har to konstruktorer alt efter om man vil samle
to eller flere InputStreams
|
|
SequenceInputStream( InputStream s1, InputStream s2 )
SequenceInputStream( Enumeration e ) |
|
| Den første tager to InputStreams, hvoraf den venstre bliver
den første der læses fra.
|
| Den anden tager en Enumeration af InputStreams (Smid f.eks. først
InputStreams i en Vector og kald elements-metoden for at få den
som en Enumeration).
|
| available er begrænset idet den kun fortæller hvor mange
bytes der er til rådighed fra den stream den pr. er igang med. Det
giver lidt problemer, betragt følgende eksempel:
|
|
SequenceInputStream input = new SequenceInputStream( ... );
while ( input.available() > 0 )
... |
|
| Her vil while-løkken terminerer når den første
InputStream løber tom. Hvis man vil lave en løkke der gennemløber
hele SequenceInputStream skal man desværre selv beregne hvor mange
elementer der er og bruge en standard for-løkke.
|
| Mht. close-metoden og Enumerations skal man være opmærksom
på at alle InputStreams der endnu ikke er færdiglæst
vil blive lukket en efter en i forbindelse med close-kaldet..
|
|
1.5.1.1 Eksempel
|
| Lad os se et eksempel med SequenceInputStream.
|
|
import java.io.*;
class TestSequenceInputStream {
public static void main( String[] argv ) {
ByteArrayOutputStream out_1 = new ByteArrayOutputStream();
ByteArrayOutputStream out_2 = new ByteArrayOutputStream();
for ( int b=0; b<10; b++ )
out_1.write( b );
for ( int b=10; b<20; b++ )
out_2.write( b );
ByteArrayInputStream in_1 = new ByteArrayInputStream( out_1.toByteArray() );
ByteArrayInputStream in_2 = new ByteArrayInputStream( out_2.toByteArray() );
try {
SequenceInputStream input = new SequenceInputStream( in_1, in_2 );
for ( int i=0; i<20; i++ )
System.out.print( input.read() + " " );
System.out.println();
}
catch ( IOException e ) {
System.out.println( "I/O fejl" );
}
}
} |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
| Der laves to ByteArrayOutputStreams der fyldes med talene fra 0 til
9 henholdsvis fra 11 til 19. Dernæst laves de tilsvarende ByteArrayOutputStreams
der skal bruge til SequenceOutputStreamen. I try-blokken er samlet alt
med SequenceOutputStream selv om det rent faktisk kun er read der kan
kaste en IOException. Dernæst udskrives bytes fra den sammensatte
stream og man ser at tallene nu som forventet passer sammen.
|
|
1.5.2 PushbackInputStream
|
| Idéen med PushbackInputStream er at kunne fortryde læsning
fra InputStream. PushbackInputStream er en FilterInputStream og føjer
derfor denne undo-funktionalitet til en InputStream.
|
| PushbackInputStream har tre unread-metoder
|
|
void unread( int b )
void unread( byte[] buffer )
void unread( byte[] buffer, int offset, int length ) |
|
| der en for en er en undo-metode for de tre tilsvarende read-metoder.
|
| Bemærk at unread-metoderne giver mulighed for at unreade bytes
man ikke har læst, da man selv skal anføre de pågældende
bytes som parameter.
|
| Selve unread-funktionaliteten er implementeret med en buffer i form
af et byte array. Bufferens størrelse fastlægges ved instantiering
og man kan ikke senere udvide den ved reallokering af arrayet. Det betyder
at bufferen kan løbe fuld. Hvis det sker vil unread-metoden kaste
en IOException og bufferen data vil være uberørte af kaldet
(fra bufferen løb fuld).
|
| PushbackInputStream har to konstruktorer:
|
|
PushbackInputStream( InputStream in )
PushbackInputStream( InputStream in, int size ) |
|
| size er størrelsen af bufferen. Default er en!
|
| At default er sølle en byte skyldes primært at der i JDK
1.0 ikke var nogen buffer. På daværende tidspunkt var det
kun muligt at unreade én byte. Bufferen, og den anden konstruktor,
kom i JDK 1.1.
|
| PushbackInputStream understøtter ikke mærker.
|
| available returnerer summen af hvad der ligger i bufferen og hvad InputStreamen
siger den har.
|
|
1.5.3 Eksempel
|
| Lad os se et eksempel med PushbackInputStream.
|
|
import java.io.*;
class TestPushbackInputStream {
public static void main( String[] argv ) {
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
for ( byte b=0; b<10; b++ )
output.write( b );
PushbackInputStream input =
new PushbackInputStream(
new ByteArrayInputStream( output.toByteArray() ), 10 );
for ( int i=0; i<5; i++ )
System.out.print( input.read() + " " );
System.out.println();
for ( byte b=0; b<5; b++ )
input.unread( b );
while ( input.available() > 0 )
System.out.print( input.read() + " " );
System.out.println();
}
catch ( IOException e ) {
System.out.println( "I/O fejl ved læsning fra PushbackInputStream" );
}
}
} |
0 1 2 3 4
4 3 2 1 0 5 6 7 8 9 |
|
| Vi fylder først tallene 0 til 9 i en ByteArrayOutputStream.
Dernæst laver vi en PushbackInputStream med denne stream som InputStream
og med en bufferen på 10 bytes (skulle være rigeligt). Dernæst
læser vi fem bytes fra PushbackInputStreamen og unreader de samme
fem tal igen, men i forkert rækkefølge. Vi ser den forkerte
rækkefølge af de første fem tal da vi tømmer
PushbackOutputStreamen og udskriver den.
|
|
1.6 Specielle OutputStreams
|
| Ved specielle OutputStreams forstås, analogt til specielle InputStreams,
dem der ikke har en korresponderende InputStream.
|
|
1.6.1 PrintStream
|
| Idéen med PrintStream er at have en række bekvemme print-metoder
der kan skrive forskellige typer som almindelig tekst i form af platformafhængige
bytes (typisk en ASCII-variant).
|
| De velkendte System.out.og System.in er instanser af PrintStream og
har som bekendt en lang række metoder ved navn print og println.
|
| Metoderne er her vist for print, men findes naturligvis fuldstændig
tilsvarende for println:
|
|
void print( boolean b )
void print( char c )
void print( char[] s )
void print( double d )
void print( float f )
void print( int i )
void print( long l )
void print( Object obj )
void print( String s ) |
|
| Ud over disse har println også en parameterløs udgange
til linieskift, der derfor ikke findes for print.
|
| En speciel ting ved PrintStream er at den aldrig vil kaste en exception.
I stedet sætter den et internt flag der indikerer om der er sket
en fejl. Man kan aflæse dette flag med
|
|
|
| der samtidig flusher.
|
| Flushing er ikke unødvendig, da PrintStream er bufferet. Man
kan sætte PrintStream til automatisk at flushe (hvilket ere gjort
for System.out og System.err) når man har skrevet et array af chars
eller lavet et linieskift. Dette gøres ved instantieringen
|
|
PrintStream( OutputStream out )
PrintStream( OutputStream out, int size ) |
|
| hvor man anvender den sidste af disse konstruktorer med true som anden
parameter.
|
| [Det er min erfaring at System.out flusher for hvert eneste tegn -
jeg er ikke klar over hvorfor!]
|
| Da System.out er så ofte anvendt, skal jeg ikke trætte
med et eksempel på PrintStream.
|
|
1.7 Oversigt
|
|
1.7.1 InputStreams
|
|
Stream |
Mærker |
Datakilde |
JDK |
ByteArrayInputStream |
Ja
|
byte[] |
1.0
|
FileInputStream |
Nej
|
file |
1.0
|
FilterInputStream |
-
|
InputStream |
1.0
|
DataInputStream |
-
|
InputStream |
1.0
|
PushbackInputStream |
Ja
|
InputStream |
1.0
|
BufferedInputStream |
Nej
|
InputStream |
1.0
|
ObjectInputStream |
Nej
|
InputStream |
1.1
|
PipedInputStream |
Nej
|
PipedOutputStream |
1.0
|
SequenceInputStream |
Ja
|
InputStreams |
1.0
|
|
|
1.7.2 OutputStreams
|
|
Stream |
Buffer |
Destination |
JDK |
ByteArrayOutputStream |
Nej
|
byte[] |
1.0
|
FileOutputStream |
Nej
|
file |
1.0
|
FilterOutputStream |
-
|
OutputStream |
1.0
|
DataOutputStream |
-
|
OutputStream |
1.0
|
PrintStream |
Ja
|
OutputStream |
1.0
|
BufferedOutputStream |
Ja
|
OutputStream |
1.0
|
ObjectOutputStream |
-
|
OutputStream |
1.1
|
PipedOutputStream |
Nej
|
PipedInputStream |
1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
2. Reader og Writers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1.X Oversigt
|
|
1.X.1 Readers
|
|
Reader |
Mærker |
Datakilde |
JDK |
CharArrayReader |
Ja
|
char[] |
1.1
|
StringReader |
Ja
|
String |
1.1
|
InputStreamReader |
Nej
|
InputStream |
1.1
|
FileReader |
Nej
|
file |
1.1
|
BufferedReader |
Ja
|
Reader |
1.1
|
LineNumberReader |
Nej
|
Reader |
1.1
|
FilterReader |
-
|
Reader |
1.1
|
PushbackReader |
Nej
|
Reader |
1.1
|
PipedReader |
Nej
|
PipedWriter |
1.1
|
|
|
1.X.1 Writers
|
|
Reader |
Buffer |
Datakilde |
JDK |
CharArrayWriter |
Nej
|
char[] |
1.1
|
StringWriter |
Nej
|
String |
1.1
|
OutputStreamWriter |
-
|
OutputStream |
1.1
|
FileWriter |
Nej
|
file |
1.1
|
BufferedWriter |
Ja
|
Writer |
1.1
|
FilterWriter |
-
|
Writer |
1.1
|
PipedWriter |
Nej
|
PipedReader |
1.1
|
PrintWriter |
-
|
Writer/OutputStream |
1.1
|
|
|
|
|
|
|
3. StreamTokenizer
|
|
StreamTokenizer læser tekst fra en Reader og inddeler det i mindre
tekststykker, kaldet tokens. Inspirationen til StreamTokenizers funktionalitet
skal man søge i parsning af source-filer i forbindelse med compilering.
En StreamTokenizer løser mange af de traktiske problemer i forbindelse
med leksikografisk analyse, idet den gør det bekvemt at indlæse
en kildetekst i syntaktiske entiteter eller tokens.
|
|
Lad os først se et eksempel på anvendelsen af en StreamTokenizer,
for at få et indtryk af hvad den mere præcist gør:
|
|
import java.io.*;
class TestStreamTokenizer {
/* En C-kommentar */
public static void main( String[] argv ) {
int x=3; // blot for at få et tal med, og samtidig en C++ kommentar
try {
StreamTokenizer st =
new StreamTokenizer(
new FileReader( "TestStreamTokenizer.java" ) );
while ( st.nextToken() != StreamTokenizer.TT_EOF ) {
if ( st.ttype == StreamTokenizer.TT_WORD )
System.out.println( "Ord: " + st.sval );
else if ( st.ttype == StreamTokenizer.TT_NUMBER )
System.out.println( "Tal: " + st.nval );
else if ( st.ttype == StreamTokenizer.TT_EOL )
System.out.println( "Linieskift" );
}
}
catch ( FileNotFoundException e ) {
System.out.println( "Source-filen mangler" );
}
catch ( IOException e ) {
System.out.println( "Fejl ved læsning fra source-filen" );
}
}
} |
Ord: import
Ord: java.io.
Ord: class
Ord: TestStreamTokenizer
Ord: public
Ord: static
Ord: void
Ord: main
Ord: String
Ord: argv
Ord: int
Ord: x
Tal: 3.0
Ord: try
Ord: StreamTokenizer
Ord: st
... |
|
|
I eksemplet er der indsat lidt ekstra fyld for at illustrere flere
egenskaber ved StreamTokenizeren. Som man ser ignorerer den i default-indstillingen
alle specielle tegn (pånær punktum) og indlæser kun
det man almindeligvis kan betegne som "ord".
|
|
Ved instantieringen angiver vi en Reader som bliver datakilden. StreamTokenizer
har kun denne ene konstruktor:
|
|
StreamTokenizer( Reader r ) |
|
|
Eftersom StreamTokenizer ser på datakilden som værende
en sekvens af tegn, er det kun naturligt at man ikke kan anføre
en InputStream som parameter til konstruktoren (der findes dog en deprecated
konstruktor, hvor man netop kan gøre dette).
|
|
Dernæst følger en while-løkke der kører
så længe vi ikke har nået slutningen af datakilden.
|
|
|
|
Metoden nextToken returnerer ikke det næste token. Den flytter
os frem til det næste token, og returnerer hvilken type det har.
Tokens kan være en af to typer: tekst (TT_WORD) eller tal (TT_NUMBER).
Dette er interger konstanter i klassen. Der findes to andre konstanter
til at indikere hvad man kunne kalde specielle "tokens": end
of file (TT_EOF) og end of line (TT_EOL). Den første af disse
bliver netop brugt i eksemplets kørselsbetingelsen. De tre andre
bliver brugt inde i løkkens if-sætning der ved udskrift
beskriver de forskellige tokens efterhånden som datakilden itereres.
|
|
Adgangen til en indikation af tokentype, samt selve token sker vi public
instans-variable. Det er rædselsfuldt grimt, set med objektorienterede
øjne, men Gosling, der har lavet StreamTokenizer, vil sikkert
forsvare sig med at det virker; hvilket er en ringe trøst. Det
er er følgende tre public instans-variable:
|
|
int ttype
String sval
double nval |
|
|
ttype indeholder typeangivelsen for det aktuelle token. Såfremt
det er en tekst vil token befinde sig i sval, modsat hvis det er et
tal, hvor det vil befinde sig i sval.
|
|
StreamTokenizers metoder kan inddeles i to grupper: Indstillings-metoder
og tre andre metoder. Lad os først gøre de tre andre metoder
færdige, og dernæst se på indstillings-mulighederne.
|
|
Af de tre har vi allerede set den ene, nemlig nextToken.
|
|
Den næste er:
|
|
|
|
StreamTokenizer har altså pushback egenskaber, ligesom PushbackStream
og PushbackReader. For StreamTokenizer er det dog lidt mere beskedent,
man kan kun undo læsningen af det sidste token. Det betyder at
det næste kan af nextToken på sin vis vil være virkningsløst,
idet der efter kaldet vil stå nøjagtig det samme i ttype,
sval og nval som før kaldet. I forbindelse med syntaktisk analyse
er det meget nyttigt da kan på den måde kan kigge et token
frem og se om der er tale om en syntaktisk delimiter.
|
|
Den tredie metode er:
|
|
|
|
Igen en metode der retter sig mod syntaktisk analyse, idet en fejl
kan meddeles på skærmen med linie-nummer, idet man til enhver
tid kan kalde denne metode for at få linie-nummeret svarende til
det aktuelle token.
|
|
3.1 Indstillinger
|
|
Der findes mange metoder (12 stk.) til at indstille en StreamTokenizer.
Den normale anvendelse af en StreamTokenizer er at man efter instantieringen
foretager en række kald der indstiller den syntaktiske opsætning,
hvorefter man lave en iteration over datakilden indtil den er udtømt.
|
|
3.1.1 Kommentarer
|
|
De to kommentarer der optrådte i eksemplet blev ignoreres af
StreamTokenizeren. De repræsenterer to forskellige former for
kommentarer. Den første slags med /*...*/ betegnes i denne forbindelse
som C-kommentarer, fordi det er den eneste type der findes i C. Den
anden type med // betegnes tilsvarende som C++-kommenaterer, fordi de
i modsætning til C findes i C++. Betegnelser er mere populære
end retvisende, da "C-kommentarer" også findes i C++,
og de begge fandte i forgængeren til C.
|
|
Default er, at både C og C++ kommentarer ignoreres af StreamTokenizer.
Man kan ændre på dette med følgende metoder:
|
|
void slashSlashComments( boolean b )
void slashStarComments( boolean b ) |
|
|
Parameteren indikerer om den pågældende type kommentarer
ignoreres (true) eller regnes som almindelig tekst på lige fod
med alt andet (false). Metode-navnene kræver måske en forklaring
på dansk. Slash er betegnelsen for skråstreg, mens star
naturligvis er * (også kaldet asterix).
|
|
Metoden
|
|
void commentChar( int c ) |
|
|
bruges til at anføre specielle kommentarer. Det tegn man anfører
som parameter til metoden bliver regnet som starten på en til
end of line kommentar, svarende til //. Det bruges nogen gange i forbindelse
med visse assemblere, der anvender semikolon som sådan et tegn,
hvor alt efter semikolon på en linie ignoreres.
|
|
3.1.2 Delimiters
|
|
Med metoden
|
|
void whitespacesChars( int start, int end ) |
|
|
kan man angive hvilke tegn der skal regnes som whitespaces og dermed
som delimiters mellem tokens. Bemærk at dette er ASCII-orienterede
værdier, ikke UniCode værdier; low og high skal ligge i
intervallet [0:255]. Der er tale om en tilføjelse af whitespaces.
Tegn der tidligere har været angivet som whitespaces vedbliver
med at være det, også efter denne metode er kaldt.
|
|
Default whitespaces er [0:32], hvor 32 svarer til mellemrum. Dette
interval indeholder alle kontrol-tegnene, dvs. tabulering, linieskift
osv.
|
|
3.1.3 Tokens
|
|
Ovenfor så vi hvorledes man kunne anføre delimiters, modsat
kan man anføre at tegn er almindelige og derfor kan indgå
i tokens. Dette gøres med en af følgende metoder:
|
|
void wordChars( int start, int end ) |
|
|
Her vil tegne i intervallet [start:end] blive regnet som tegn der indgår
i et ord. Default for ord-tegn er: 'a' til 'z', 'A' til 'Z' og intervallet
[160:255]. [punktum er ud fra eksempelt også et word-tegn men
det ligger da ikke i det øverste interval? eller gør det?
Jeg skal have checket UniCode!]
|
|
Visse tegn betegnes som almindelige (eng.: ordinary). De regnes som
tokens i sig selv, og nextToken sætter ttype til deres værdi,
når den støder på den. Mao., de er enkelt-tegns-tokens.
Man kan angive at tegn skal regnes som sådanne enkelt-tegns-tokens
med en af følgende to metoder:
|
|
void ordinaryChar( int c )
void ordinaryChars( int start, int end ) |
|
|
Der er ingen default enkelt-tegns-tokens.
|
|
Skal linieskift regnes som et specielt token eller skal den ignoreres?
det kan man bestemme med metoden
|
|
void eolIsSignificant( boolean b ) |
|
|
true betyder at linieskift regnes som et token, false ej.
|
|
Man kan i visse tilfælde ønske at man kan behandle hele
sætninger som tokens. At man kan indramme dem i f.eks. anførselstegn.
Man kan angive, at man ønsker et tegn behandlet som et sådant
citat-tegn med metoden
|
|
|
| Når et token som står mellem sådanne citat-tegn læses,
returnerer nextToken tegnet,
f.eks. et anførselstegn, og man kan dernæst hente teksten
i sval.
|
|
Default er anførselstegn " og enkelt-apostrof ', der begge
er citat-tegn.
|
|
Når ord behandles som tokens har man mulighed for at dem automatisk
konverteret til små bogstaver (lowercase) med metoden:
|
|
void lowerCaseMode( boolean b ) |
|
|
Skal cifre behandles som tal eller er det bare tegn som alle andre?
Det bestemmer man med metoden
|
|
|
|
der indstiller StreamTokenizeren så den behandler cifre som tal.
Default er at cifre behandles som tal, og den eneste mulighed man har
for at ændre på dette er ved at anvende følgende
metode, der fjerner enhver for syntaks-indstilling i StreamTokenizeren:
|
|
|
|
Efter at have kaldt denne metode regnes alle tegn for ordinary, dvs.
at nextToken altid returnerer det næste tegn.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Opgaver
|
1
| Lav to klasser VectorInputStream
og VectorOutputStream, der nedarver
fra henholdsvis Input- og OutputStream.
Implementer dem således at datakilden er en Vector.
VectorInputStream skal have en
passende konstruktor, og VectorOutputStream
skal have en get-metode, så man kan arbejde videre med Vector'en
efter at skrivning til VectorOutputStream
er afsluttet.
|
|
|
|
|
|
|
|
Vejledende løsning til opgaverne
|
1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |