Kelas ByteArrayOutputStream mengimplementasikan aliran keluaran yang menulis data ke array byte. Buffer tumbuh secara otomatis saat data ditulis ke dalamnya.

Kelas ByteArrayOutputStream membuat buffer di memori, dan semua data yang dikirim ke aliran disimpan di buffer.

konstruktor ByteArrayOutputStream

Kelas ByteArrayOutputStream memiliki konstruktor berikut :

Konstruktor
ByteArrayOutputStream() Konstruktor ini membuat buffer dalam memori sepanjang 32 byte.
ByteArrayOutputStream(int a) Konstruktor ini membuat buffer dalam memori dengan ukuran tertentu.

Dan seperti inilah tampilan kelas di dalamnya:


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

Metode kelas ByteArrayOutputStream

Mari kita bicara tentang metode yang bisa kita gunakan di kelas kita.

Mari kita coba memasukkan sesuatu ke aliran kita. Untuk melakukannya, kita akan menggunakan metode write() — metode ini dapat menerima satu byte atau satu set byte untuk ditulis.

metode
batal tulis (int b) Menulis satu byte.
batal tulis (byte b[], int off, int len) Menulis array byte dengan ukuran tertentu.
batal tulisBytes(byte b[]) Menulis array byte.
batal writeTo (OutputStream keluar) Menulis semua data dari aliran keluaran saat ini ke aliran keluaran yang diteruskan.

Implementasi metode:


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

Hasilnya adalah file output.txt baru yang terlihat seperti ini:

Metode toByteArray() mengembalikan konten aliran keluaran saat ini sebagai larik byte. Dan Anda bisa menggunakan metode toString() untuk mendapatkan array buf byte sebagai teks:


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

Buffer kami berisi array byte yang kami berikan padanya.

Metode reset() mengatur ulang jumlah byte yang valid dalam aliran output array byte ke nol (sehingga semua yang terakumulasi dalam output diatur ulang).


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

Saat kami menampilkan buffer kami setelah memanggil metode reset() , kami tidak mendapatkan apa-apa.

Fitur khusus dari metode close()

Metode ini patut mendapat perhatian khusus. Untuk memahami apa fungsinya, mari kita intip ke dalam:


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

Perhatikan bahwa metode close() kelas ByteArrayOutputStream sebenarnya tidak melakukan apa pun.

Mengapa demikian? ByteArrayOutputStream adalah aliran berbasis memori (yaitu, dikelola dan diisi oleh pengguna dalam kode), jadi memanggil close () tidak berpengaruh.

Praktik

Sekarang mari kita coba mengimplementasikan sistem file menggunakan ByteArrayOutputStream dan ByteArrayInputStream .

Mari kita menulis kelas FileSystem menggunakan pola desain singleton dan menggunakan HashMap<String, byte[]> statis , di mana:

  • String adalah path ke file
  • byte[] adalah data dalam file yang disimpan

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

Di kelas ini, kami membuat metode publik berikut:

  • metode CRUD standar (buat, baca, perbarui, hapus),
  • metode untuk memeriksa apakah ada file,
  • metode untuk mendapatkan instance dari sistem file.

Untuk membaca dari file, kami mengembalikan InputStream . Di bawah tenda adalah implementasi ByteArrayInputStream . Buffer adalah array byte yang disimpan di peta file .

Metode lain yang menarik adalah newOutputStream . Saat metode ini dipanggil, kami mengembalikan objek ByteArrayOutputStream baru yang menggantikan dua metode: flush dan close . Memanggil salah satu dari metode ini akan menyebabkan penulisan dilakukan.

Dan itulah yang kami lakukan: kami mendapatkan array byte yang telah ditulis oleh pengguna, dan menyimpan salinan sebagainilaidi peta file dengan kunci yang sesuai.

Kami menggunakan kode berikut untuk menguji sistem file kami (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);
   }
}
    

Selama pengujian, kami memverifikasi tindakan berikut:

  1. Kami membuat file baru.
  2. Kami memeriksa apakah file yang dibuat kosong.
  3. Kami menulis beberapa data ke file.
  4. Kami membaca kembali data dan memverifikasi bahwa itu cocok dengan yang kami tulis.
  5. Kami menghapus file.
  6. Kami memverifikasi bahwa file telah dihapus.

Menjalankan kode ini memberi kita hasil ini:

File berhasil dibuat
Isi file:
Data ditulis ke file
Isi file: CodeGym
File ada: salah

Mengapa contoh ini diperlukan?

Sederhananya, data selalu merupakan kumpulan byte. Jika Anda perlu membaca/menulis banyak data dari/ke disk, kode Anda akan berjalan lambat karena masalah I/O. Dalam hal ini, masuk akal untuk memelihara sistem file virtual dalam RAM, bekerja dengannya dengan cara yang sama seperti yang Anda lakukan dengan disk tradisional. Dan apa yang lebih sederhana dari InputStream dan OutputStream ?

Tentu saja, ini adalah contoh untuk instruksi, bukan kode siap produksi. TIDAK memperhitungkan (daftar berikut ini tidak lengkap):

  • multithreading
  • batas ukuran file (jumlah RAM yang tersedia untuk JVM yang sedang berjalan)
  • verifikasi struktur jalan
  • verifikasi argumen metode

Informasi orang dalam yang menarik:
Server verifikasi tugas CodeGym menggunakan pendekatan yang agak mirip. Kami menjalankan FS virtual, menentukan pengujian mana yang perlu dijalankan untuk verifikasi tugas, menjalankan pengujian, dan membaca hasilnya.

Kesimpulan dan pertanyaan besar

Pertanyaan besar setelah membaca pelajaran ini adalah "Mengapa saya tidak bisa menggunakan byte[] saja , karena lebih nyaman dan tidak memberlakukan batasan?"

Keuntungan ByteArrayInputStream adalah kuat menunjukkan bahwa Anda akan menggunakan byte hanya-baca (karena aliran tidak menyediakan antarmuka untuk mengubah kontennya). Yang mengatakan, penting untuk dicatat bahwa seorang programmer masih dapat mengakses byte secara langsung.

Tetapi jika terkadang Anda memiliki byte[] , terkadang Anda memiliki file, terkadang Anda memiliki koneksi jaringan, dan seterusnya, Anda memerlukan semacam abstraksi untuk "aliran byte, dan saya tidak peduli di mana mereka berasal dari". Dan itulah InputStream . Ketika sumbernya adalah array byte, ByteArrayInputStream adalah InputStream yang baik untuk digunakan.

Ini sangat membantu dalam banyak situasi, tetapi berikut adalah dua contoh spesifik:

  1. Anda sedang menulis perpustakaan yang menerima byte dan memprosesnya entah bagaimana (misalnya, misalkan itu adalah perpustakaan utilitas pemrosesan gambar). Pengguna pustaka Anda dapat menyediakan byte dari file, dari byte dalam memori[] , atau dari beberapa sumber lain. Jadi Anda menyediakan antarmuka yang menerima InputStream , yang berarti jika mereka memiliki byte[] , mereka harus membungkusnya dalam ByteArrayInputStream .

  2. Anda sedang menulis kode yang membaca koneksi jaringan. Tetapi untuk melakukan pengujian unit pada kode ini, Anda tidak ingin membuka koneksi — Anda hanya ingin memasukkan beberapa byte ke kode. Jadi kode mengambil InputStream dan tes Anda lolos dalam ByteArrayInputStream .