Ang klase ng ByteArrayOutputStream ay nagpapatupad ng isang output stream na nagsusulat ng data sa isang byte array. Ang buffer ay awtomatikong lumalaki habang ang data ay nakasulat dito.

Ang klase ng ByteArrayOutputStream ay lumilikha ng buffer sa memorya, at lahat ng data na ipinadala sa stream ay nakaimbak sa buffer.

ByteArrayOutputStream constructors

Ang klase ng ByteArrayOutputStream ay may mga sumusunod na konstruktor:

Tagabuo
ByteArrayOutputStream() Ang constructor na ito ay lumilikha ng in-memory buffer na 32 bytes ang haba.
ByteArrayOutputStream(int a) Lumilikha ang constructor na ito ng in-memory buffer na may partikular na laki.

At ito ang hitsura ng klase sa loob:


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

Mga pamamaraan ng klase ng ByteArrayOutputStream

Pag-usapan natin ang mga pamamaraan na magagamit natin sa ating klase.

Subukan nating maglagay ng isang bagay sa ating stream. Para magawa ito, gagamitin namin ang write() method — maaari itong tumanggap ng isang byte o isang set ng byte para sa pagsusulat.

Pamamaraan
void write(int b) Sumulat ng isang byte.
void write(byte b[], int off, int len) Nagsusulat ng hanay ng mga byte ng isang partikular na laki.
void writeBytes(byte b[]) Nagsusulat ng hanay ng mga byte.
void writeTo(OutputStream out) Isinulat ang lahat ng data mula sa kasalukuyang output stream hanggang sa naipasa na output stream.

Pagpapatupad ng pamamaraan:


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

Ang resulta ay isang bagong output.txt file na ganito ang hitsura:

Ibinabalik ng toByteArray() na pamamaraan ang kasalukuyang nilalaman ng output stream na ito bilang isang hanay ng mga byte. At maaari mong gamitin ang toString() na paraan upang makuha ang buf byte array bilang text:


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

Ang aming buffer ay naglalaman ng byte array na ipinasa namin dito.

Ang reset() method ay nire-reset ang bilang ng mga wastong byte sa byte array output stream sa zero (kaya lahat ng naipon sa output ay na-reset).


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

Kapag ipinakita namin ang aming buffer pagkatapos tawagan ang reset() na pamamaraan, wala kaming makukuha.

Mga partikular na tampok ng close() na pamamaraan

Ang pamamaraang ito ay nararapat na espesyal na pansin. Upang maunawaan kung ano ang ginagawa nito, tingnan natin ang loob:


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

Tandaan na ang pamamaraang close() ng klase ng ByteArrayOutputStream ay walang aktwal na nagagawa.

Bakit ganon? Ang ByteArrayOutputStream ay isang memory-based na stream (iyon ay, ito ay pinamamahalaan at na-populate ng user sa code), kaya ang pagtawag sa close() ay walang epekto.

Magsanay

Ngayon subukan nating magpatupad ng file system gamit ang ating ByteArrayOutputStream at ByteArrayInputStream .

Sumulat tayo ng klase ng FileSystem gamit ang pattern ng disenyo ng singleton at gumamit ng static na HashMap<String, byte[]> , kung saan:

  • Ang string ay ang landas patungo sa isang file
  • ang byte[] ay ang data sa naka-save na file

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

Sa klase na ito, nilikha namin ang mga sumusunod na pampublikong pamamaraan:

  • karaniwang mga pamamaraan ng CRUD (lumikha, magbasa, mag-update, magtanggal),
  • isang paraan upang suriin kung mayroong isang file,
  • isang paraan upang makakuha ng isang halimbawa ng file system.

Upang magbasa mula sa isang file, nagbabalik kami ng isang InputStream . Sa ilalim ng hood ay ang pagpapatupad ng ByteArrayInputStream . Ang buffer ay isang byte array na nakaimbak sa mga file map.

Ang isa pang kawili-wiling paraan ay newOutputStream . Kapag tinawag ang pamamaraang ito, nagbabalik kami ng bagong object na ByteArrayOutputStream na nag-o-override sa dalawang pamamaraan: flush at close . Ang pagtawag sa alinman sa mga pamamaraang ito ay dapat maging sanhi ng pagsulat.

At iyon mismo ang ginagawa namin: nakukuha namin ang byte array kung saan isinulat ng user, at nag-iimbak ng kopya bilang anghalagasa mapa ng mga file na may naaangkop na key.

Ginagamit namin ang sumusunod na code upang subukan ang aming file system (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);
   }
}
    

Sa panahon ng pagsubok, bini-verify namin ang mga sumusunod na aksyon:

  1. Gumawa kami ng bagong file.
  2. Sinusuri namin na ang nilikhang file ay walang laman.
  3. Sumulat kami ng ilang data sa file.
  4. Binabasa namin muli ang data at i-verify na tumutugma ito sa aming isinulat.
  5. Tinatanggal namin ang file.
  6. Bine-verify namin na ang file ay tinanggal.

Ang pagpapatakbo ng code na ito ay nagbibigay sa amin ng output na ito:

Matagumpay na nalikha ang file
Mga nilalaman ng file:
Data na isinulat sa file
Mga nilalaman ng file:
Umiiral ang CodeGym File: false

Bakit kailangan ang halimbawang ito?

Sa madaling salita, ang data ay palaging isang hanay ng mga byte. Kung kailangan mong magbasa/magsulat ng maraming data mula/papunta sa disk, dahan-dahang tatakbo ang iyong code dahil sa mga problema sa I/O. Sa kasong ito, makatuwiran na mapanatili ang isang virtual na file system sa RAM, nagtatrabaho dito sa parehong paraan na gagawin mo sa isang tradisyonal na disk. At ano ang maaaring mas simple kaysa sa InputStream at OutputStream ?

Siyempre, ito ay isang halimbawa para sa pagtuturo, hindi code na handa sa produksyon. HINDI nito isinasaalang-alang ang (hindi komprehensibo ang sumusunod na listahan):

  • multithreading
  • mga limitasyon sa laki ng file (ang halaga ng magagamit na RAM para sa isang tumatakbong JVM)
  • pagpapatunay ng istraktura ng landas
  • pagpapatunay ng mga argumento ng pamamaraan

Kawili-wiling impormasyon ng tagaloob:
Gumagamit ang server ng pag-verify ng gawain ng CodeGym ng medyo katulad na diskarte. Pinaikot namin ang isang virtual na FS, tinutukoy kung aling mga pagsubok ang kailangang patakbuhin para sa pag-verify ng gawain, patakbuhin ang mga pagsubok, at basahin ang mga resulta.

Konklusyon at ang malaking tanong

Ang malaking tanong pagkatapos basahin ang araling ito ay "Bakit hindi ko na lang gamitin ang byte[] , dahil ito ay mas maginhawa at hindi nagpapataw ng mga paghihigpit?"

Ang bentahe ng ByteArrayInputStream ay malakas itong nagpapahiwatig na gagamit ka ng mga read-only na byte (dahil ang stream ay hindi nagbibigay ng interface sa pagbabago ng nilalaman nito). Iyon ay sinabi, mahalagang tandaan na ang isang programmer ay maaari pa ring direktang ma-access ang mga byte.

Ngunit kung minsan mayroon kang isang byte[] , minsan mayroon kang isang file, kung minsan mayroon kang koneksyon sa network, at iba pa, kakailanganin mo ng ilang uri ng abstraction para sa "isang stream ng mga byte, at wala akong pakialam kung saan sila nanggaling sa". At iyon ang InputStream . Kapag ang pinagmulan ay isang byte array, ang ByteArrayInputStream ay isang magandang InputStream na gagamitin.

Nakakatulong ito sa maraming sitwasyon, ngunit narito ang dalawang partikular na halimbawa:

  1. Nagsusulat ka ng isang library na tumatanggap ng mga byte at pinoproseso ang mga ito sa anumang paraan (halimbawa, ipagpalagay na ito ay isang library ng mga kagamitan sa pagproseso ng imahe). Ang mga user ng iyong library ay maaaring magbigay ng mga byte mula sa isang file, mula sa isang in-memory na byte[] , o mula sa ibang pinagmulan. Kaya't nagbibigay ka ng interface na tumatanggap ng InputStream , na nangangahulugan na kung mayroon silang byte[] , kailangan nilang balutin ito sa isang ByteArrayInputStream .

  2. Nagsusulat ka ng code na nagbabasa ng koneksyon sa network. Ngunit para magsagawa ng mga unit test sa code na ito, hindi mo gustong magbukas ng koneksyon — gusto mo lang mag-feed ng ilang byte sa code. Kaya ang code ay tumatagal ng isang InputStream at ang iyong pagsubok ay pumasa sa isang ByteArrayInputStream .