1. Dataströmmar

Sällan existerar ett program som en ö för sig själv. Program brukar på något sätt interagera med "omvärlden". Detta kan ske genom att läsa data från tangentbordet, skicka meddelanden, ladda ner sidor från Internet eller, omvänt, ladda upp filer till en fjärrserver.

Vi kan referera till alla dessa beteenden i ett ord: datautbyte mellan programmet och omvärlden. Vänta, det är inte bara ett ord.

Självklart kan datautbytet i sig delas upp i två delar: ta emot data och skicka data. Till exempel läser du data från tangentbordet med hjälp av ett Scannerobjekt — det här är att ta emot data. Och du visar data på skärmen med ett System.out.println()kommando - det här är att skicka data.

I programmering används termen "ström" för att beskriva datautbyte. Var kom den termen ifrån?

I verkliga livet kan du ha en ström av vatten eller en ström av medvetande. Inom programmering har vi dataströmmar .

Strömmar är ett mångsidigt verktyg. De tillåter programmet att ta emot data var som helst (indataströmmar) och skicka data var som helst (outputströmmar). Det finns alltså två typer:

  • En ingångsström är för att ta emot data
  • En utgångsström är till för att skicka data

För att göra strömmar "påtagliga" skrev Javas skapare två klasser: InputStreamoch OutputStream.

Klassen InputStreamhar en read()metod som låter dig läsa data från den. Och OutputStreamklassen har en write()metod som låter dig skriva data till den. De har andra metoder också, men mer om det senare.

Byteströmmar

Vilken typ av data pratar vi om? Vilket format tar det? Med andra ord, vilka datatyper stöder dessa klasser?

Dessa är generiska klasser, så de stöder den vanligaste datatypen - byte. En OutputStreamkan skriva bytes (och byte-arrayer), och ett InputStreamobjekt kan läsa bytes (eller byte-arrayer). Det är det — de stöder inte några andra datatyper.

Som ett resultat kallas dessa strömmar även byteströmmar .

En egenskap hos strömmar är att deras data endast kan läsas (eller skrivas) sekventiellt. Du kan inte läsa data från mitten av en ström utan att läsa all data som kommer före den.

Så här fungerar att läsa data från tangentbordet genom klassen Scanner: du läser data från tangentbordet sekventiellt, rad för rad. Vi läser en rad, sedan nästa rad, sedan nästa rad och så vidare. Passande nog kallas metoden för att läsa rader nextLine().

Att skriva data till en OutputStreamsker också sekventiellt. Ett bra exempel på detta är konsolutgång. Du matar ut en rad, följt av en och en till. Detta är sekventiell utgång. Du kan inte skriva ut den första raden, sedan den tionde och sedan den andra. All data skrivs till en utgångsström endast sekventiellt.

Karaktärsströmmar

Du lärde dig nyligen att strängar är den näst mest populära datatypen, och det är de faktiskt. Mycket information skickas runt i form av karaktärer och hela strängar. En dator utmärker sig på att skicka och ta emot allt som byte, men människor är inte så perfekta.

Med hänsyn till detta, skrev Java-programmerare ytterligare två klasser: Readeroch Writer. Klassen Readerär analog med InputStreamklassen, men dess read()metod läser inte bytes, utan tecken ( ) char. Klassen Writermotsvarar klassen OutputStream. Och precis som Readerklassen fungerar den med tecken ( char), inte byte.

Om vi ​​jämför dessa fyra klasser får vi följande bild:

Byte (byte) Tecken (char)
Läser data
InputStream
Reader
Att skriva data
OutputStream
Writer

Praktisk applikation

Klasserna InputStream, OutputStream, Readeroch Writersjälva används inte direkt av någon, eftersom de inte är förknippade med några konkreta objekt från vilka data kan läsas (eller i vilka data kan skrivas). Men dessa fyra klasser har gott om ättlingklasser som kan mycket.


2. InputStreamklass

Klassen InputStreamär intressant eftersom den är föräldraklass för hundratals efterkommande klasser. Den har inga egna data, men den har metoder som alla dess härledda klasser ärver.

I allmänhet är det sällsynt att strömobjekt lagrar data internt. En stream är ett verktyg för att läsa/skriva data, men inte lagring. Som sagt, det finns undantag.

Metoder för InputStreamklassen och alla dess efterkommande klasser:

Metoder Beskrivning
int read()
Läser en byte från strömmen
int read(byte[] buffer)
Läser en array av byte från strömmen
byte[] readAllBytes()
Läser alla bytes från strömmen
long skip(long n)
Hoppa över nbyte i flödet (läser och slänger dem)
int available()
Kontrollerar hur många byte som finns kvar i strömmen
void close()
Stänger strömmen

Låt oss kortfattat gå igenom dessa metoder:

read()metod

Metoden read()läser en byte från strömmen och returnerar den. Du kan bli förvirrad av intreturtypen. Denna typ valdes eftersom intden är standard heltalstyp. De första tre byten av intkommer att vara noll.

read(byte[] buffer)metod

Detta är den andra varianten av read()metoden. Det låter dig läsa en byte-array från en InputStreamallt på en gång. Arrayen som kommer att lagra byte måste skickas som ett argument. Metoden returnerar ett tal — antalet byte som faktiskt lästs.

Låt oss säga att du har en 10 kilobyte buffert och att du läser data från en fil med hjälp av FileInputStreamklassen. Om filen endast innehåller 2 kilobyte kommer all data att laddas in i buffertmatrisen och metoden returnerar talet 2048 (2 kilobyte).

readAllBytes()metod

En mycket bra metod. Den läser bara all data från InputStreamtills den tar slut och returnerar den som en enda byte-array. Detta är mycket praktiskt för att läsa små filer. Stora filer kanske inte får plats fysiskt i minnet, och metoden kommer att skapa ett undantag.

skip(long n)metod

Denna metod låter dig hoppa över de första n byte från objektet InputStream. Eftersom data läses strikt sekventiellt läser denna metod helt enkelt de första n byten från strömmen och kasserar dem.

Returnerar antalet byte som faktiskt hoppades över (om strömmen avslutades innan nbyte hoppades över).

int available()metod

Metoden returnerar antalet byte som fortfarande finns kvar i flödet

void close()metod

Metoden close()stänger dataströmmen och frigör de externa resurser som är associerade med den. När en ström är stängd kan ingen mer data läsas från den.

Låt oss skriva ett exempelprogram som kopierar en mycket stor fil. Vi kan inte använda readAllBytes()metoden för att läsa in hela filen i minnet. Exempel:

Koda Notera
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);
   }
}



InputStreamför läsning från filen
OutputStreamför skriv till fil

Buffert som vi kommer att läsa in data i
Så länge det finns data i strömmen

Läs data in i bufferten
Skriv data från bufferten till den andra strömmen

I det här exemplet använde vi två klasser: FileInputStreamär en avkomling av InputStreamför att läsa data från en fil, och FileOutputStreamär en avkomling av OutputStreamför att skriva data till en fil. Vi kommer att prata om den andra klassen lite senare.

En annan intressant punkt här är realvariabeln. När det sista datablocket läses från en fil kan det lätt ha mindre än 64KB data. Följaktligen behöver vi inte mata ut hela bufferten, utan bara en del av den - de första realbyten. Det är precis vad som händer i write()metoden.



3. Readerklass

Klassen Readerär en komplett analog till InputStreamklassen. Den enda skillnaden är att det fungerar med tecken ( char), inte med byte. Precis som InputStreamklassen Readeranvänds klassen inte någonstans på egen hand: den är överordnad klass för hundratals avkomliga klasser och definierar gemensamma metoder för dem alla.

Metoder för Readerklassen (och alla dess underliggande klasser):

Metoder Beskrivning
int read()
Läser en charfrån streamen
int read(char[] buffer)
Läser en chararray från strömmen
long skip(long n)
Hoppa över n charsi flödet (läser och slänger dem)
boolean ready()
Kontrollerar om det fortfarande finns något kvar i strömmen
void close()
Stänger strömmen

Metoderna är väldigt lika de i InputStreamklassen, även om det finns små skillnader.

int read()metod

Den här metoden läser en charfrån strömmen och returnerar den. Typen charvidgas till en int, men de första två byten av resultatet är alltid noll.

int read(char[] buffer)metod

Detta är den andra varianten av read()metoden. Det låter dig läsa en char-array från en Readerallt på en gång. Arrayen som kommer att lagra tecknen måste skickas som ett argument. Metoden returnerar ett nummer — antalet tecken som faktiskt lästs.

skip(long n)metod

Denna metod låter dig hoppa över de första n tecknen från objektet Reader. Det fungerar exakt på samma sätt som den analoga metoden för InputStreamklassen. Returnerar antalet tecken som faktiskt hoppades över.

boolean ready()metod

Returnerar trueom det finns olästa bytes i strömmen.

void close()metod

Metoden close()stänger dataströmmen och frigör de externa resurser som är associerade med den. När en ström är stängd kan ingen mer data läsas från den.

För jämförelse, låt oss skriva ett program som kopierar en textfil:

Koda Notera
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);
   }
}



Readerför att läsa från en fil
Writerför att skriva till en fil

Buffert som vi kommer att läsa in data i
Så länge det finns data i strömmen

Läs data i en buffert
Skriv data från bufferten till den andra strömmen