A ByteArrayOutputStream osztály egy kimeneti adatfolyamot valósít meg, amely adatokat ír egy bájttömbbe. A puffer automatikusan növekszik, amikor adatot írnak bele.

A ByteArrayOutputStream osztály puffert hoz létre a memóriában, és az adatfolyamba küldött összes adat a pufferben tárolódik.

ByteArrayOutputStream konstruktorok

A ByteArrayOutputStream osztály a következő konstruktorokkal rendelkezik:

Konstruktőr
ByteArrayOutputStream() Ez a konstruktor egy 32 bájt hosszúságú memórián belüli puffert hoz létre.
ByteArrayOutputStream(int a) Ez a konstruktor meghatározott méretű memórián belüli puffert hoz létre.

És így néz ki belülről az osztály:


// 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];
}
    

A ByteArrayOutputStream osztály módszerei

Beszéljünk azokról a módszerekről, amelyeket az osztályunkban használhatunk.

Próbáljunk meg valamit betenni a folyamunkba. Ehhez a write() metódust használjuk – ez egy bájtot vagy bájtkészletet tud elfogadni az íráshoz.

Módszer
érvénytelen írás (b int) Egy bájtot ír.
void write (bájt b[], int off, int len) Egy adott méretű bájttömböt ír.
void writeBytes(bájt b[]) Egy bájttömböt ír.
void writeTo (OutputStream out) Minden adatot ír az aktuális kimeneti adatfolyamból az átadott kimeneti adatfolyamba.

A módszer megvalósítása:


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

Az eredmény egy új output.txt fájl, amely így néz ki:

A toByteArray() metódus ennek a kimeneti adatfolyamnak az aktuális tartalmát adja vissza bájtok tömbjeként. És használhatja a toString() metódust is, hogy a buf bájttömböt szövegként kapja meg :


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]);
    }
}
    

A pufferünk tartalmazza azt a bájttömböt, amelyet átadtunk neki.

A reset() metódus nullára állítja a bájttömb kimeneti adatfolyamának érvényes bájtok számát (tehát a kimenetben felhalmozott minden visszaállításra kerül).


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

Amikor a reset() metódus meghívása után megjelenítjük a pufferünket , nem kapunk semmit.

A close() metódus sajátosságai

Ez a módszer különös figyelmet érdemel. Hogy megértsük, mit csinál, vessünk egy pillantást a belsejébe:


/**
 * 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 {
}
    

Vegye figyelembe, hogy a ByteArrayOutputStream osztály close() metódusa valójában nem csinál semmit.

Miert van az? A ByteArrayOutputStream egy memória alapú adatfolyam (azaz a felhasználó kezeli és tölti fel kódban), így a close() hívásnak nincs hatása.

Gyakorlat

Most próbáljunk meg egy fájlrendszert megvalósítani a ByteArrayOutputStream és a ByteArrayInputStream segítségével .

Írjunk egy FileSystem osztályt a singleton tervezési mintával, és használjunk egy statikus HashMap<String, byte[]> értéket , ahol:

  • A karakterlánc a fájl elérési útja
  • bájt[] a mentett fájlban lévő adat

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");
       }
   }
}
    

Ebben az osztályban a következő nyilvános metódusokat hoztuk létre:

  • szabványos CRUD módszerek (létrehozás, olvasás, frissítés, törlés),
  • egy módszer annak ellenőrzésére, hogy létezik-e fájl,
  • egy módszer a fájlrendszer egy példányának beszerzésére.

Fájlból való olvasáshoz az InputStream értéket adjuk vissza . A motorháztető alatt a ByteArrayInputStream megvalósítás található. A puffer a fájlleképezésben tárolt bájttömb .

Egy másik érdekes módszer a newOutputStream . A metódus meghívásakor egy új ByteArrayOutputStream objektumot adunk vissza, amely felülír két módszert: flush és close . E metódusok bármelyikének meghívása az írást eredményezi.

És pontosan ezt tesszük: megkapjuk a bájttömböt, amelybe a felhasználó írt, és eltároljuk a másolatotértéka fájlleképezésben egy megfelelő kulccsal.

A következő kódot használjuk a fájlrendszerünk (FS) teszteléséhez:


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

A teszt során a következő műveleteket ellenőrizzük:

  1. Létrehozunk egy új fájlt.
  2. Ellenőrizzük, hogy a létrehozott fájl üres-e.
  3. Néhány adatot írunk a fájlba.
  4. Visszaolvasjuk az adatokat, és ellenőrizzük, hogy megegyeznek-e azzal, amit írtunk.
  5. Töröljük a fájlt.
  6. Ellenőrizzük, hogy a fájlt törölték-e.

Ennek a kódnak a futtatása a következő kimenetet adja:

A fájl sikeresen létrehozva
Fájltartalom:
Fájlba írt adatok Fájltartalom
: CodeGym
A fájl létezik: false

Miért volt szükség erre a példára?

Egyszerűen fogalmazva, az adatok mindig bájtok halmazai. Ha sok adatot kell olvasnia/írnia a lemezről/lemezre, a kód lassan fut az I/O problémák miatt. Ebben az esetben érdemes egy virtuális fájlrendszert a RAM-ban karbantartani, ugyanúgy dolgozni vele, mint egy hagyományos lemezzel. És mi lehetne egyszerűbb, mint az InputStream és az OutputStream ?

Természetesen ez egy példa az utasításokhoz, nem a gyártásra kész kód. NEM számol (a következő lista nem teljes):

  • többszálú
  • fájlméret-korlátok (a rendelkezésre álló RAM mennyisége egy futó JVM-hez)
  • az útvonalszerkezet ellenőrzése
  • metódus argumentumok ellenőrzése

Érdekes bennfentes információ:
A CodeGym feladatellenőrző szervere némileg hasonló megközelítést alkalmaz. Felpörgetünk egy virtuális FS-t, meghatározzuk, mely teszteket kell futtatni a feladat ellenőrzéséhez, lefuttatjuk a teszteket, és elolvassuk az eredményeket.

Következtetés és a nagy kérdés

A nagy kérdés a lecke elolvasása után az, hogy "Miért nem használhatom a byte[]-t , mivel az kényelmesebb és nem szab meg korlátozásokat?"

A ByteArrayInputStream előnye, hogy erős azt jelzi, hogy csak olvasható bájtokat fog használni (mivel az adatfolyam nem biztosít felületet a tartalom megváltoztatásához). Ennek ellenére fontos megjegyezni, hogy a programozó továbbra is közvetlenül hozzáférhet a bájtokhoz.

De ha néha van egy bájt[] , néha van egy fájl, néha van hálózati kapcsolat, és így tovább, akkor szüksége lesz valamiféle absztrakcióra "egy bájtfolyamhoz, és nem érdekel, hol jönni valahonnan". És ez az InputStream . Ha a forrás történetesen egy bájttömb, a ByteArrayInputStreamInputStream használható.

Ez sok esetben hasznos, de itt van két konkrét példa:

  1. Olyan könyvtárat ír, amely fogadja a bájtokat, és valahogy feldolgozza azokat (például tegyük fel, hogy ez egy képfeldolgozó segédprogramok könyvtára). A könyvtár felhasználói bájtokat biztosíthatnak fájlból, a memóriában lévő bájtból [] vagy más forrásból. Tehát olyan felületet adsz meg, amely elfogad egy InputStream -et , ami azt jelenti, hogy ha van egy byte[] , akkor azt egy ByteArrayInputStream- be kell csomagolniuk .

  2. Olyan kódot ír, amely beolvassa a hálózati kapcsolatot. De ahhoz, hogy egységteszteket hajtson végre ezen a kódon, nem akar kapcsolatot nyitni, hanem csak néhány bájtot szeretne betáplálni a kódba. Tehát a kód egy InputStream-et vesz fel , a teszt pedig egy ByteArrayInputStream-en megy át .