Kelas ByteArrayOutputStream melaksanakan aliran keluaran yang menulis data kepada tatasusunan bait. Penampan berkembang secara automatik apabila data ditulis kepadanya.

Kelas ByteArrayOutputStream mencipta penimbal dalam ingatan, dan semua data yang dihantar ke strim disimpan dalam penimbal.

Pembina ByteArrayOutputStream

Kelas ByteArrayOutputStream mempunyai pembina berikut:

Pembina
ByteArrayOutputStream() Pembina ini mencipta penimbal dalam memori yang panjangnya 32 bait.
ByteArrayOutputStream(int a) Pembina ini mencipta penimbal dalam memori dengan saiz tertentu.

Dan inilah rupa kelas di dalam:


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

Kaedah kelas ByteArrayOutputStream

Mari kita bercakap tentang kaedah yang boleh kita gunakan dalam kelas kita.

Mari cuba letak sesuatu dalam aliran kami. Untuk melakukan ini, kami akan menggunakan kaedah write() — ia boleh menerima satu bait atau satu set bait untuk menulis.

Kaedah
batal tulis(int b) Menulis satu bait.
batal tulis(bait b[], int off, int len) Menulis tatasusunan bait dengan saiz tertentu.
batal writeBytes(bait b[]) Menulis tatasusunan bait.
void writeTo(OutputStream out) Menulis semua data daripada aliran keluaran semasa kepada aliran keluaran yang diluluskan.

Pelaksanaan kaedah:


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 ialah fail output.txt baharu yang kelihatan seperti ini:

Kaedah toByteArray() mengembalikan kandungan semasa aliran keluaran ini sebagai tatasusunan bait. Dan anda boleh menggunakan kaedah toString() untuk mendapatkan tatasusunan bait buf 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]);
    }
}
    

Penampan kami mengandungi tatasusunan bait yang kami hantar kepadanya.

Kaedah reset() menetapkan semula bilangan bait yang sah dalam aliran output tatasusunan bait kepada sifar (jadi semua yang terkumpul dalam output ditetapkan semula).


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

Apabila kami memaparkan penimbal kami selepas memanggil kaedah reset() , kami tidak mendapat apa-apa.

Ciri khusus kaedah close().

Kaedah ini patut diberi perhatian khusus. Untuk memahami apa yang dilakukannya, mari kita lihat 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 {
}
    

Ambil perhatian bahawa kaedah close() kelas ByteArrayOutputStream sebenarnya tidak melakukan apa-apa.

Kenapa begitu? ByteArrayOutputStream ialah strim berasaskan memori (iaitu, ia diurus dan diisi oleh pengguna dalam kod), jadi panggilan close () tidak mempunyai kesan.

berlatih

Sekarang mari cuba untuk melaksanakan sistem fail menggunakan ByteArrayOutputStream dan ByteArrayInputStream kami .

Mari tulis kelas FileSystem menggunakan corak reka bentuk tunggal dan gunakan HashMap statik<String, byte[]> , di mana:

  • String ialah laluan ke fail
  • byte[] ialah data dalam fail 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");
       }
   }
}
    

Dalam kelas ini, kami mencipta kaedah awam berikut:

  • kaedah CRUD standard (buat, baca, kemas kini, padam),
  • kaedah untuk menyemak sama ada fail wujud,
  • kaedah untuk mendapatkan contoh sistem fail.

Untuk membaca daripada fail, kami mengembalikan InputStream . Di bawah tudung adalah pelaksanaan ByteArrayInputStream . Penampan ialah tatasusunan bait yang disimpan dalam peta fail .

Kaedah lain yang menarik ialah newOutputStream . Apabila kaedah ini dipanggil, kami mengembalikan objek ByteArrayOutputStream baharu yang mengatasi dua kaedah: flush dan close . Memanggil salah satu daripada kaedah ini seharusnya menyebabkan penulisan berlaku.

Dan itulah yang kami lakukan: kami mendapat tatasusunan bait yang telah ditulis oleh pengguna, dan menyimpan salinan sebagainilaidalam peta fail dengan kunci yang sesuai.

Kami menggunakan kod berikut untuk menguji sistem fail 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);
   }
}
    

Semasa ujian, kami mengesahkan tindakan berikut:

  1. Kami membuat fail baru.
  2. Kami menyemak bahawa fail yang dibuat kosong.
  3. Kami menulis beberapa data ke fail.
  4. Kami membaca semula data dan mengesahkan bahawa ia sepadan dengan apa yang kami tulis.
  5. Kami memadam fail.
  6. Kami mengesahkan bahawa fail telah dipadamkan.

Menjalankan kod ini memberi kita output ini:

Fail berjaya dibuat
Kandungan fail:
Data ditulis ke fail
Kandungan fail:
Fail CodeGym wujud: palsu

Mengapakah contoh ini diperlukan?

Ringkasnya, data sentiasa satu set bait. Jika anda perlu membaca/menulis banyak data dari/ke cakera, kod anda akan berjalan perlahan kerana masalah I/O. Dalam kes ini, masuk akal untuk mengekalkan sistem fail maya dalam RAM, bekerja dengannya dengan cara yang sama seperti yang anda lakukan dengan cakera tradisional. Dan apa yang lebih mudah daripada InputStream dan OutputStream ?

Sudah tentu, ini adalah contoh untuk arahan, bukan kod sedia pengeluaran. Ia TIDAK mengambil kira (senarai berikut tidak menyeluruh):

  • multithreading
  • had saiz fail (jumlah RAM yang tersedia untuk JVM yang sedang berjalan)
  • pengesahan struktur laluan
  • pengesahan hujah kaedah

Maklumat orang dalam yang menarik:
Pelayan pengesahan tugas CodeGym menggunakan pendekatan yang agak serupa. Kami memutarkan FS maya, menentukan ujian yang perlu dijalankan untuk pengesahan tugas, menjalankan ujian dan membaca keputusan.

Kesimpulan dan persoalan besar

Soalan besar selepas membaca pelajaran ini ialah "Mengapa saya tidak boleh menggunakan byte[] sahaja , kerana ia lebih mudah dan tidak mengenakan sekatan?"

Kelebihan ByteArrayInputStream ialah ia kuat menunjukkan bahawa anda akan menggunakan bait baca sahaja (kerana strim tidak menyediakan antara muka untuk menukar kandungannya). Walau bagaimanapun, adalah penting untuk ambil perhatian bahawa pengaturcara masih boleh mengakses bait secara langsung.

Tetapi jika kadangkala anda mempunyai bait[] , kadangkala anda mempunyai fail, kadangkala anda mempunyai sambungan rangkaian, dan seterusnya, anda memerlukan beberapa jenis abstraksi untuk "strim bait, dan saya tidak peduli di mana ia datang dari". Dan itulah InputStream . Apabila sumbernya menjadi tatasusunan bait, ByteArrayInputStream ialah InputStream yang baik untuk digunakan.

Ini berguna dalam banyak situasi, tetapi berikut ialah dua contoh khusus:

  1. Anda sedang menulis perpustakaan yang menerima bait dan memprosesnya entah bagaimana (contohnya, katakan ia adalah perpustakaan utiliti pemprosesan imej). Pengguna pustaka anda boleh memberikan bait daripada fail, daripada bait dalam memori[] , atau daripada beberapa sumber lain. Jadi anda menyediakan antara muka yang menerima InputStream , yang bermaksud bahawa jika mereka mempunyai byte[] , mereka perlu membungkusnya dalam ByteArrayInputStream .

  2. Anda sedang menulis kod yang membaca sambungan rangkaian. Tetapi untuk melaksanakan ujian unit pada kod ini, anda tidak mahu membuka sambungan — anda hanya mahu menyuapkan beberapa bait kepada kod tersebut. Jadi kod itu mengambil InputStream dan ujian anda lulus dalam ByteArrayInputStream .