15
Tags:
.net
Skrevet af
Bruger #4522
@ 20.04.2008
Klassen Stream
Den abstrakte klasse
Stream er grundpillen i streams-arkitekturen. Det er fra den stream klasserne nedarver. Som nævnt tidligere er stream klasserne byte-orienterede så når vi brugder dem læser, skriver eller søger vi bytes (og som også nævnt bruger vi stream adaptere såfremt vi ønsker at læse/skrive tekst,
ints, xml, osv.).
Da alle de stream klasser man bruger nedarver fra
Stream-klassen er det vigtigt at forstå dens funktionalitet.
Når vi har oprettet en stream via dens konstruktør (
constructor) er der tre "offentlige" (dvs.
public) properties vi kan bruge til at sikre os at den understøtter det vi ønsker før vi udfører handlingen:
CanRead,
CanWrite og
CanSeek.
CanRead bruges før vi læser fra en stream for at sikre os at den nuværende stream understøtter det. Der returneres sand såfremt der kan læses fra datastrømmen, ellers returneres der falsk. Hvis man læser fra en strøm der ikke understøtter en læsehandling, kastes en undtagelse af typen
NotSupportedException.
Hvis du arbejder med en datastrøm hvis lager-steam er en
FileStream eller en
MemoryStream kan du søge (på eng.:
seek) i strømmen, hvilket betyder at du kan specificere en position i strømmen hvorfra du ønsker at læse/skrive. Andre lager-streams, såsom en
NetworkStream, understøtter ikke muligheden for at søge i datastrømmen. For at finde ud af om den datastrøm du arbejder med understøtter
seeking, kan du bruge den property der hedder
CanSeek. Der returneres sand hvis søgning understøttes, ellers falsk. Igen kastes en
NotSupportedException såfremt der forsøges at udføre en seek-handling på en strøm der ikke understøtter dette. Hvis datastrømmen understøtter søgning, bruges den property der hedder
Position til at sætte/oplyse den nuværende position i datastrømmen - dvs. den bruges til at navigere rundt i strømmen med.
Det er ganske sikkert at du nu kan regne ud hvad den property der hedder
CanWrite gør, og hvordan den bruges. Men for en god ordens skyld:
CanWrite bruges til at undersøge om en datastrøm understøtter muligheden for at skrive til den. Der returneres sand hvis det er tilfældet eller falsk. Hvis du er fræk og alligevel skriver til en datastrøm der ikke understøtter det, kastes en
NotSupportedException.
Den property der hedder
Length, og som der kun kan læses fra, returnerer en værdi af typen
long som angiver datastrømmens længde i bytes. Den bruges f.eks. når man skal finde ud af hvor stor en array med bytes man skal bruge til at lagre strømmen i.
Når vi skal i gang med at arbejde med vores datastrøm har
Stream, ikke overraskende, nogle metoder til det. Der er metoder der giver os mulighed for at læse fra strømmen, skrive til den, eller søge (altså
seeke) i den.
Lad os starte med det sidst: seeke. Som nævnt tidligere kan du sætte din ønskede nuværende position (hvilket netop er at
seeke) ved brug af den property der hedder
Position. Når du bruger
Poistion kalder den faktisk en metoder der hedder
Seek(). Du kan også selv bruge
Seek() direkte i stedet hvis du ønsker. Når du seeker laver du faktisk det der hedder en
random access i datastrømmen - dvs. du specificer selv helt præcist hvorfra du ønsker at foretage en læse/skrive handling fra/på datastrømmen. Dette vil du gøre når du ønsker adgang til specifikke dele af den strøm du arbejder med.
Seek() metoden tager to argumenter: en
long som er den afstand i bytes du ønsker fra et referencepunkt i strømmen. Referencepunktet sættes med det andet argument som er af typen
SeekOrigin.
SeekOrigin er en enum-datatype, og har følgende medlemmer:
Begin,
Current og
End.
Så hvis du f.eks. bruger
Seek() sådanne:
Seek(100, SeekOrigin.Begin);
har du sat en ny position i datastrømmen som er 100 bytes fra dens begyndelse. Og du kan altså herfra begynde at læse og/eller skrive.
Gør du derimod følgende:
Seek(50, SeekOrigin.Current);
sætter du din nye position i datastrømmen til at være 50 bytes fra den nuværende position.
De metoder vi bruger til at læse til og skrive fra en datastrøm er inddelt i to grupper: de synkrone og de asynkrone.
De synkrone metoder hedder
Read(byte[] buffer, int offset, int count),
ReadByte(),
Write(byte[] buffer, int offset, int count) og
WriteByte(byte value).
Read(byte[] buffer, int offset, int count) læser
count antal bytes fra strømmen og gemmer dem i
buffer fra position
offset. Der kan være færre end
count bytes i datastrømmen, så
count er det maksimale antal læste bytes. Efter læsningen er din nye position i datstrømmen avanceret det læste antal bytes.
ReadByte() læser en enkelt byte, returnerer den og avancerer positionen i datastrømmen med en byte. Hvis der ikke er mere at læse fra datastrømmen, returneres i stedet værdien -1.
Write(byte[] buffer, int offset, int count) bruges til at skrive til datastrømmen. Der skrives
count antal bytes fra position
offset i
buffer til datstrømmens nuværende position. Positionen i datastrømmen er derefter avanceret det skrevne antal bytes.
WriteByte(byte value) skrive byten
value til datastrømmen, og avancerer positionen i datastrømmen én byte.
Det var de synkrone metoder. Nu til de asynkrone.
Vi starter med
BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state). Denne metoder starter en asynkron læsning fra datastrømmen.
buffer er der hvor de læste bytes skal gemmes.
offest er den position i
buffer lagringen af de læste bytes skal begynde.
count er det maksimale antal bytes der skal læses.
callback, som er valgfri, er en delegate der skal kaldes når læsningen er fuldført. Det sidste argument er et eller andet objekt som kan bruges til at adskille denne asynkrone forespørgsel fra en anden. Den nuværende position i datastrømmen opdateres med det samme og ikke først når læsningen er udført. Når du kalder
BeginRead returnerer metoden et objekt at typen
IAsyncResult som er en reference til den asynkrone læseforespørgsel du lige har startet.
Asynkron skrivning til en strøm, initieres med
BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state). Denne metode skriver data fra position
offest i tabellen
buffer. Der skrives maksimalt
count bytes til datastrømmen. Som ovenfor er
callback en delegate som kaldes når skrivningen er fuldført - det er frivilligt om hvorvidt man ønsker at gøre brug af dette argument.
state er ligeså som ovenfor - et objekt der bruges til at skelne denne asynkrone forespørgsel fra en anden. Den nuværende position i datastrømmen opdateres med det samme og ikke først når skrivningen er udført. Når du kalder
BeginWrite returnerer metoden et objekt at typen
IAsyncResult som er en reference til den asynkrone skriveforespørgsel du lige har startet.
Når du har startet en asynkron læse eller skrive forespørgsel er det vigtigt at du husker at afslutte den. Det gøres med metoderne
EndRead og
EndWrite respektivt, som blokerer indtil I/O-handlingen er udført. Disse metoder tager begge et argument af typen
IAsyncResult som er en reference til den forespørgsel du ønsker at vente på bliver afsluttet. Der returneres en
int som er antallet af bytes læst fra datastrømmen. For hver gang du kalder
BeginWrite/
BeginRead skal du altid kalde den tilsvarende
EndWrite/
EndRead. Hvis du ikke afslutter din asynkrone forespørgsel kan du opleve uønsket adfærd fra dit program, inklusiv for eksempel en såkaldt
deadlock. Man vil typisk kalde
EndRead/
EndWrite fra den delegate man gav da man kaldte den tilsvarende
BeginRead/
BeginWrte.
Når du arbejder med en
Stream-nedarvet klasse skal du også kende til
Flush()-metoden. Den sørger for at al data der er i en buffer skrives til den pågældende underliggende enhed.
Du kan også kalde
Close()-metoden.
Close() sørger for at alle resurser der er tilknyttet strømmen, såsom filhåndtag, netværksforbindelser (
socket) osv., bliver frigjort. Når du kalder
Close() "flushes" der også automatisk så du behøver ikke selv at kalde
Flush() før
Close().
Med metoden
SetLength() kan du sætte en datastrøms længde. Den tager et
long argument som er den længde i bytes du ønsker strømmen skal være. Hvis den værdi du angiver er mindre end datastrømmens nuværende længde bliver datastrømmen forkortet. Derimod bliver datastrømmen forlænget hvis den længde du angiver er større end den nuværende længde; hvad der er indeholdt i den nye del af strømmen er udefineret. En datastrøm skal selvklart understøtte både seeking og skrivning før du kan bruge
SetLength() (hvilket du kan undersøge med de to properties
CanWrite og
CanSeek som forklaret tidligere).
De properties og metoder vi netop har diskuteret er disse:

Nu da du ved hvad idéen er bag metoderne som de forskellige klasser der nedarver fra
Stream implementerer - burde det være ligetil at bruge de forskellige lager-stream klasser. Vi vil nu kigge på hvordan
FileStream kan bruges.
FileStream nedarver fra
Stream så vi vil bl.a. gøre brug af de properties og metoder vi netop har diskuteret.
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (2)
En meget god artikel. Jeg synes at når du begynder at skrive om MemoryStream, så er det synd du ikke går mere i dybden med det når du alligevel begynder at fortælle lidt om klassen. Der burde du måske bare have undværet det helt og så skrevet i starten at det ikke var med i artiklen. Ellers faldt jeg ikke over det helt store. Godt beskrivende og helt sikkert noget jeg kommer til at bruge. Keep up the good work
Tak for dine kommentarer Martin. Du har en pointe omkring MemoryStream og jeg vil prøve at uddybe det lidt mere i artiklen.
Du skal være
logget ind for at skrive en kommentar.