1. Gegevensstromen

Zelden bestaat een programma als een eiland op zich. Programma's hebben meestal op de een of andere manier interactie met de "buitenwereld". Dit kan gebeuren door gegevens van het toetsenbord te lezen, berichten te verzenden, pagina's van internet te downloaden of, omgekeerd, bestanden naar een externe server te uploaden.

We kunnen al deze gedragingen in één woord aanduiden: gegevensuitwisseling tussen het programma en de buitenwereld. Wacht, dat is niet zomaar een woord.

Natuurlijk kan de gegevensuitwisseling zelf in twee delen worden verdeeld: gegevens ontvangen en gegevens verzenden. U leest bijvoorbeeld gegevens van het toetsenbord met behulp van een Scannerobject - dit is het ontvangen van gegevens. En u geeft gegevens op het scherm weer met een System.out.println()opdracht - dit is het verzenden van gegevens.

Bij het programmeren wordt de term "stroom" gebruikt om gegevensuitwisseling te beschrijven. Waar komt die term vandaan?

In het echte leven kun je een stroom van water of een stroom van bewustzijn hebben. Bij het programmeren hebben we datastromen .

Streams zijn een veelzijdige tool. Ze stellen het programma in staat om overal vandaan gegevens te ontvangen (invoerstromen) en gegevens overal te verzenden (uitvoerstromen). Er zijn dus twee soorten:

  • Een invoerstroom is voor het ontvangen van gegevens
  • Een uitvoerstroom is voor het verzenden van gegevens

Om streams 'tastbaar' te maken, schreven de makers van Java twee klassen: InputStreamen OutputStream.

De InputStreamklasse heeft een read()methode waarmee u er gegevens uit kunt lezen. En de OutputStreamklasse heeft een write()methode waarmee je er gegevens naar kunt schrijven. Ze hebben ook andere methoden, maar daarover later meer.

Bytestromen

Over wat voor gegevens hebben we het? Welk formaat heeft het nodig? Met andere woorden, welke gegevenstypen ondersteunen deze klassen?

Dit zijn generieke klassen, dus ze ondersteunen het meest voorkomende gegevenstype: de byte. Een OutputStreamkan bytes (en byte-arrays) schrijven en een InputStreamobject kan bytes (of byte-arrays) lezen. Dat is alles - ze ondersteunen geen andere gegevenstypen.

Daarom worden deze streams ook wel bytestreams genoemd .

Een kenmerk van streams is dat hun gegevens alleen sequentieel kunnen worden gelezen (of geschreven). U kunt geen gegevens uit het midden van een stream lezen zonder alle gegevens te lezen die eraan voorafgaan.

Dit is hoe het lezen van gegevens van het toetsenbord door de Scannerklas gaat: u leest de gegevens opeenvolgend van het toetsenbord, regel voor regel. We lezen een regel, dan de volgende regel, dan de volgende regel, enzovoort. Passend heet de methode voor het lezen van regels nextLine().

Het schrijven van gegevens naar een OutputStreamgebeurt ook sequentieel. Een goed voorbeeld hiervan is console-uitvoer. U voert een regel uit, gevolgd door nog een en nog een. Dit is sequentiële uitvoer. U kunt niet de eerste regel uitvoeren, dan de tiende en dan de tweede. Alle gegevens worden slechts sequentieel naar een uitvoerstroom geschreven.

Karakterstromen

U heeft onlangs vernomen dat tekenreeksen het op één na populairste gegevenstype zijn, en dat zijn ze ook. Er wordt veel informatie doorgegeven in de vorm van karakters en hele strings. Een computer blinkt uit in het verzenden en ontvangen van alles als bytes, maar mensen zijn niet zo perfect.

Om dit feit te verklaren, schreven Java-programmeurs nog twee klassen: Readeren Writer. De Readerklasse is analoog aan de InputStreamklasse, maar de read()methode leest geen bytes, maar tekens ( char). De Writerklas komt overeen met de OutputStreamklas. En net als de Readerklasse werkt het met tekens ( char), niet met bytes.

Als we deze vier klassen vergelijken, krijgen we het volgende beeld:

byte (byte) Karakters (char)
Gegevens lezen
InputStream
Reader
Gegevens schrijven
OutputStream
Writer

Praktische toepassing

De klassen InputStream, OutputStream, Readeren Writerzelf worden door niemand rechtstreeks gebruikt, omdat ze niet zijn gekoppeld aan concrete objecten waaruit gegevens kunnen worden gelezen (of waarin gegevens kunnen worden geschreven). Maar deze vier klassen hebben genoeg afstammelingen die veel kunnen.


2. InputStreamklasse

De InputStreamklasse is interessant omdat het de ouderklasse is voor honderden afstammelingenklassen. Het heeft geen eigen gegevens, maar het heeft wel methoden die alle afgeleide klassen erven.

Over het algemeen is het zeldzaam dat stream-objecten gegevens intern opslaan. Een stream is een hulpmiddel voor het lezen/schrijven van gegevens, maar geen opslag. Dat gezegd hebbende, er zijn uitzonderingen.

Methoden van de InputStreamklasse en al zijn onderliggende klassen:

methoden Beschrijving
int read()
Leest één byte uit de stream
int read(byte[] buffer)
Leest een reeks bytes uit de stream
byte[] readAllBytes()
Leest alle bytes uit de stream
long skip(long n)
Slaat nbytes in de stream over (leest en verwijdert ze)
int available()
Controleert hoeveel bytes er nog over zijn in de stream
void close()
Sluit de stroom af

Laten we deze methoden kort doornemen:

read()methode

De read()methode leest één byte uit de stream en retourneert deze. U kunt in de war raken door het intretourtype. Dit type is gekozen omdat intdit het standaard integer type is. De eerste drie bytes van de intzullen nul zijn.

read(byte[] buffer)methode

Dit is de tweede variant van de read()methode. Hiermee kunt u een byte-array van een InputStreamalles tegelijk lezen. De array waarin de bytes worden opgeslagen, moet als argument worden doorgegeven. De methode retourneert een getal — het aantal werkelijk gelezen bytes.

Laten we zeggen dat je een buffer van 10 kilobyte hebt en dat je gegevens uit een bestand leest met behulp van de FileInputStreamklasse. Als het bestand slechts 2 kilobytes bevat, worden alle gegevens in de bufferarray geladen en retourneert de methode het getal 2048 (2 kilobytes).

readAllBytes()methode

Een hele goede methode. Het leest gewoon alle gegevens van InputStreamtotdat het op is en retourneert het als een enkele byte-array. Dit is erg handig voor het lezen van kleine bestanden. Grote bestanden passen mogelijk fysiek niet in het geheugen en de methode genereert een uitzondering.

skip(long n)methode

Met deze methode kunt u de eerste n bytes van het object overslaan InputStream. Omdat de gegevens strikt opeenvolgend worden gelezen, leest deze methode eenvoudigweg de eerste n bytes uit de stream en verwijdert deze.

Retourneert het aantal bytes dat daadwerkelijk is overgeslagen (in het geval dat de stream eindigde voordat er nbytes werden overgeslagen).

int available()methode

De methode retourneert het aantal bytes dat nog over is in de stream

void close()methode

De close()methode sluit de gegevensstroom af en geeft de bijbehorende externe bronnen vrij. Zodra een stream is gesloten, kunnen er geen gegevens meer uit worden gelezen.

Laten we een voorbeeldprogramma schrijven dat een heel groot bestand kopieert. We kunnen de readAllBytes()methode niet gebruiken om het hele bestand in het geheugen te lezen. Voorbeeld:

Code Opmerking
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStreamvoor het lezen van het bestand
OutputStreamvoor het schrijven naar het bestand

Buffer waarin we de gegevens zullen lezen
Zolang er gegevens in de stroom zijn

Lees gegevens in de buffer
Schrijf de gegevens van de buffer naar de tweede stroom

In dit voorbeeld hebben we twee klassen gebruikt: FileInputStreamis een afstammeling van InputStreamvoor het lezen van gegevens uit een bestand, en FileOutputStreamis een afstammeling van OutputStreamvoor het schrijven van gegevens naar een bestand. We zullen het later over de tweede klas hebben.

Een ander interessant punt hier is de realvariabele. Wanneer het laatste gegevensblok uit een bestand wordt gelezen, kan het gemakkelijk minder dan 64 KB aan gegevens bevatten. Daarom moeten we niet de hele buffer uitvoeren, maar slechts een deel ervan - de eerste realbytes. Dit is precies wat er in de write()methode gebeurt.



3. Readerklasse

De Readerklas is een volledig analoog van de InputStreamklas. Het enige verschil is dat het werkt met karakters ( char), niet met bytes. Net als de InputStreamklasse wordt de Readerklasse nergens op zichzelf gebruikt: het is de bovenliggende klasse voor honderden onderliggende klassen en definieert gemeenschappelijke methoden voor al deze klassen.

Methoden van de Readerklasse (en al zijn onderliggende klassen):

methoden Beschrijving
int read()
Leest er een charuit de stream
int read(char[] buffer)
Leest een chararray uit de stream
long skip(long n)
Springt n charsin de stream (leest en verwijdert ze)
boolean ready()
Controleert of er nog iets in de stream staat
void close()
Sluit de stroom af

De methoden lijken erg op die van de InputStreamklas, hoewel er kleine verschillen zijn.

int read()methode

Deze methode leest er een charuit de stream en retourneert deze. Het chartype verbreedt naar een int, maar de eerste twee bytes van het resultaat zijn altijd nul.

int read(char[] buffer)methode

Dit is de tweede variant van de read()methode. Hiermee kunt u Readerin één keer een char-array van een all-in lezen. De array waarin de tekens worden opgeslagen, moet als argument worden doorgegeven. De methode retourneert een getal — het aantal tekens dat daadwerkelijk is gelezen.

skip(long n)methode

Met deze methode kunt u de eerste n tekens van het object overslaan Reader. Het werkt precies hetzelfde als de analoge methode van de InputStreamklasse. Retourneert het aantal tekens dat daadwerkelijk is overgeslagen.

boolean ready()methode

Retourneert trueals er ongelezen bytes in de stream zijn.

void close()methode

De close()methode sluit de gegevensstroom af en geeft de bijbehorende externe bronnen vrij. Zodra een stream is gesloten, kunnen er geen gegevens meer uit worden gelezen.

Laten we ter vergelijking een programma schrijven dat een tekstbestand kopieert:

Code Opmerking
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Readervoor het lezen van een bestand
Writervoor het schrijven naar een bestand

Buffer waarin we de gegevens zullen lezen
Zolang er gegevens in de stroom zijn

Lees gegevens in een buffer
Schrijf de gegevens van de buffer naar de tweede stroom