ByteArrayOutputStream - klassen implementerer en utdatastrøm som skriver data til en bytearray. Bufferen vokser automatisk etter hvert som data skrives til den.

ByteArrayOutputStream - klassen lager en buffer i minnet, og alle dataene som sendes til strømmen lagres i bufferen.

ByteArrayOutputStream-konstruktører

ByteArrayOutputStream - klassen har følgende konstruktører:

Konstruktør
ByteArrayOutputStream() Denne konstruktøren lager en buffer i minnet som er 32 byte lang.
ByteArrayOutputStream(int a) Denne konstruktøren lager en buffer i minnet med en bestemt størrelse.

Og slik ser klassen ut inni:


// The buffer itself, where the data is stored.
protected byte buf[];

// Current number of bytes written to the buffer.
protected int count;

public ByteArrayOutputStream() {
    this(32);
}

public ByteArrayOutputStream(int size) {
    if (size < 0) {
        throw new IllegalArgumentException("Negative initial size: "
                                           + size);
    }
    buf = new byte[size];
}
    

Metoder for ByteArrayOutputStream-klassen

La oss snakke om metodene vi kan bruke i klassen vår.

La oss prøve å legge noe i strømmen vår. For å gjøre dette bruker vi metoden write() - den kan godta én byte eller et sett med byte for skriving.

Metode
void skrive(int b) Skriver én byte.
void skrive(byte b[], int av, int len) Skriver en rekke byte av en bestemt størrelse.
void skriveBytes(byte b[]) Skriver en rekke byte.
void writeTo(OutputStream out) Skriver alle data fra den gjeldende utdatastrømmen til den passerte utdatastrømmen.

Metodeimplementering:


public static void main(String[] args) throws IOException {
   ByteArrayOutputStream outputByte = new ByteArrayOutputStream();
   // Write one byte
   while(outputByte.size()!= 7) {
      outputByte.write("codegym".getBytes());
   }

   // Write array of bytes
   String value = "\nWelcome to Java\n";
   byte[] arrBytes = value.getBytes();
   outputByte.write(arrBytes);

   // Write part of an array
   String codeGym = "CodeGym";
   byte[] b = codeGym.getBytes();
   outputByte.write(b, 4, 3);

   // Write to a file
   FileOutputStream fileOutputStream = new FileOutputStream("output.txt");
   outputByte.write(80);
   outputByte.writeTo(fileOutputStream);
}
    

Resultatet er en ny output.txt- fil som ser slik ut:

Metoden toByteArray() returnerer gjeldende innhold i denne utdatastrømmen som en rekke byte. Og du kan bruke toString()- metoden for å få buf- byte-matrisen som tekst:


public static void main(String[] args) throws IOException {
    ByteArrayOutputStream outputByte = new ByteArrayOutputStream();

    String value = "CodeGym";
    outputByte.write(value.getBytes());

    byte[] result = outputByte.toByteArray();
    System.out.println("Result: ");

    for(int i = 0 ; i < result.length; i++) {
        // Display the characters
        System.out.print((char)result[i]);
    }
}
    

Bufferen vår inneholder byte-arrayen som vi sendte til den.

Reset () -metoden tilbakestiller antall gyldige byte i utdatastrømmen for bytearrayen til null (slik at alt som er akkumulert i utdataene nullstilles).


public static void main(String[] args) throws IOException {
   ByteArrayOutputStream outputByte = new ByteArrayOutputStream(120);

   String value = "CodeGym";
   outputByte.write(value.getBytes());
   byte[] result = outputByte.toByteArray();
   System.out.println("Output before reset: ");

   for (byte b : result) {
      // Display the characters
      System.out.print((char) b);
   }

   outputByte.reset();

   byte[] resultAfterReset = outputByte.toByteArray();
   System.out.println("\nOutput after reset: ");

   for (byte b : resultAfterReset) {
      // Display the characters
      System.out.print((char) b);
   }
}
    

Når vi viser bufferen vår etter å ha kalt reset()- metoden, får vi ingenting.

Spesifikke funksjoner ved close()-metoden

Denne metoden fortjener spesiell oppmerksomhet. For å forstå hva det gjør, la oss ta en titt inn:


/**
 * Closing a {@code ByteArrayOutputStream} has no effect. The methods in
 * this class can be called after the stream has been closed without
 * generating an {@code IOException}.
 */
public void close() throws IOException {
}
    

Merk at ByteArrayOutputStream- klassens close() -metode faktisk ikke gjør noe.

Hvorfor det? En ByteArrayOutputStream er en minnebasert strøm (det vil si at den administreres og fylles ut av brukeren i kode), så å kalle close() har ingen effekt.

Øve på

La oss nå prøve å implementere et filsystem ved å bruke våre ByteArrayOutputStream og ByteArrayInputStream .

La oss skrive en FileSystem- klasse ved å bruke singleton- designmønsteret og bruke et statisk HashMap<String, byte[]> , hvor:

  • String er banen til en fil
  • byte[] er dataene i den lagrede filen

import java.io.*;
import java.util.HashMap;
import java.util.Map;

class FileSystem {
   private static final FileSystem fileSystem = new FileSystem();
   private static final Map<String, byte[]> files = new HashMap<>();

   private FileSystem() {
   }

   public static FileSystem getFileSystem() {
       return fileSystem;
   }

   public void create(String path) {
       validateNotExists(path);
       files.put(path, new byte[0]);
   }

   public void delete(String path) {
       validateExists(path);
       files.remove(path);
   }

   public boolean isExists(String path) {
       return files.containsKey(path);
   }

   public InputStream newInputStream(String path) {
       validateExists(path);
       return new ByteArrayInputStream(files.get(path));
   }

   public OutputStream newOutputStream(String path) {
       validateExists(path);
       return new ByteArrayOutputStream() {
           @Override
           public void flush() throws IOException {
               final byte[] bytes = toByteArray();
               files.put(path, bytes);
               super.flush();
           }

           @Override
           public void close() throws IOException {
               final byte[] bytes = toByteArray();
               files.put(path, bytes);
               super.close();
           }
       };
   }

   private void validateExists(String path) {
       if (!files.containsKey(path)) {
           throw new RuntimeException("File not found");
       }
   }

   private void validateNotExists(String path) {
       if (files.containsKey(path)) {
           throw new RuntimeException("File exists");
       }
   }
}
    

I denne klassen har vi laget følgende offentlige metoder:

  • standard CRUD-metoder (opprette, lese, oppdatere, slette),
  • en metode for å sjekke om en fil eksisterer,
  • en metode for å få en forekomst av filsystemet.

For å lese fra en fil returnerer vi en InputStream . Under panseret er ByteArrayInputStream -implementeringen. Bufferen er en byte-array som er lagret i filkartet .

En annen interessant metode er newOutputStream . Når denne metoden kalles, returnerer vi et nytt ByteArrayOutputStream- objekt som overstyrer to metoder: flush og close . Å kalle en av disse metodene bør føre til at skrivingen finner sted.

Og det er akkurat det vi gjør: vi får byte-arrayen som brukeren har skrevet til, og lagrer en kopi somverdii filkartet med en passende nøkkel.

Vi bruker følgende kode for å teste filsystemet vårt (FS):


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import static java.nio.charset.StandardCharsets.UTF_8;

public class MyFileSystemTest {
   public static void main(String[] args) throws IOException {
       FileSystem fileSystem = FileSystem.getFileSystem();
       final String path = "/user/bin/data.txt";

       // Create a file
       fileSystem.create(path);
       System.out.println("File created successfully");

       // Make sure it's empty
       try (InputStream inputStream = fileSystem.newInputStream(path)) {
           System.out.print("File contents:\t");
           System.out.println(read(inputStream));
       }

       // Write data to it
       try (final OutputStream outputStream = fileSystem.newOutputStream(path)) {
           outputStream.write("CodeGym".getBytes(UTF_8));
           System.out.println("Data written to file");
       }

       // Read data
       try (InputStream inputStream = fileSystem.newInputStream(path)) {
           System.out.print("File contents:\t");
           System.out.println(read(inputStream));
       }

       // Delete the file
       fileSystem.delete(path);

       // Verify that the file does not exist in the FS
       System.out.print("File exists:\t");
       System.out.println(fileSystem.isExists(path));

   }

   private static String read(InputStream inputStream) throws IOException {
       return new String(inputStream.readAllBytes(), UTF_8);
   }
}
    

Under testen bekrefter vi følgende handlinger:

  1. Vi lager en ny fil.
  2. Vi sjekker at den opprettede filen er tom.
  3. Vi skriver noen data til filen.
  4. Vi leser tilbake dataene og bekrefter at de stemmer overens med det vi skrev.
  5. Vi sletter filen.
  6. Vi bekrefter at filen er slettet.

Å kjøre denne koden gir oss denne utgangen:

Fil opprettet vellykket
Filinnhold:
Data skrevet til fil
Filinnhold: CodeGym
Filen eksisterer: usant

Hvorfor var dette eksemplet nødvendig?

Enkelt sagt er data alltid et sett med byte. Hvis du trenger å lese/skrive mye data fra/til disk, vil koden din kjøre sakte på grunn av I/O-problemer. I dette tilfellet er det fornuftig å opprettholde et virtuelt filsystem i RAM, og jobbe med det på samme måte som du ville gjort med en tradisjonell disk. Og hva kan være enklere enn InputStream og OutputStream ?

Selvfølgelig er dette et eksempel på instruksjon, ikke produksjonsklar kode. Det tar IKKE hensyn til (følgende liste er ikke uttømmende):

  • flertråding
  • filstørrelsesgrenser (mengden tilgjengelig RAM for en kjørende JVM)
  • verifisering av stistrukturen
  • verifisering av metodeargumenter

Interessant innsideinformasjon:
CodeGym-oppgaveverifiseringsserveren bruker en noe lignende tilnærming. Vi spinner opp en virtuell FS, bestemmer hvilke tester som må kjøres for oppgaveverifisering, kjører testene og leser resultatene.

Konklusjon og det store spørsmålet

Det store spørsmålet etter å ha lest denne leksjonen er "Hvorfor kan jeg ikke bare bruke byte[] , siden det er mer praktisk og ikke pålegger begrensninger?"

Fordelen med ByteArrayInputStream er at den sterkt indikerer at du kommer til å bruke skrivebeskyttede bytes (fordi strømmen ikke gir et grensesnitt for å endre innholdet). Når det er sagt, er det viktig å merke seg at en programmerer fortsatt kan få tilgang til bytene direkte.

Men hvis du noen ganger har en byte[] , noen ganger har du en fil, noen ganger har du en nettverkstilkobling og så videre, trenger du en slags abstraksjon for "en strøm av bytes, og jeg bryr meg ikke om hvor de kommer fra". Og det er det InputStream er. Når kilden tilfeldigvis er en byte-array, er ByteArrayInputStream en god InputStream å bruke.

Dette er nyttig i mange situasjoner, men her er to spesifikke eksempler:

  1. Du skriver et bibliotek som mottar bytes og behandler dem på en eller annen måte (anta for eksempel at det er et bibliotek med bildebehandlingsverktøy). Brukere av biblioteket ditt kan gi byte fra en fil, fra en byte i minnet [] , eller fra en annen kilde. Så du gir et grensesnitt som aksepterer en InputStream , som betyr at hvis de har en byte[] , må de pakke den inn i en ByteArrayInputStream .

  2. Du skriver kode som leser en nettverkstilkobling. Men for å utføre enhetstester på denne koden, vil du ikke åpne en forbindelse - du vil bare mate noen byte til koden. Så koden tar en InputStream og testen din består i en ByteArrayInputStream .