ByteArrayOutputStream sınıfı , verileri bir bayt dizisine yazan bir çıktı akışı uygular. Veriler yazıldıkça arabellek otomatik olarak büyür.

ByteArrayOutputStream sınıfı, bellekte bir arabellek oluşturur ve akışa gönderilen tüm veriler arabellekte depolanır .

ByteArrayOutputStream yapıcıları

ByteArrayOutputStream sınıfı aşağıdaki yapıcılara sahiptir :

Yapıcı
ByteArrayOutputStream() Bu yapıcı, 32 bayt uzunluğunda bir bellek içi arabellek oluşturur.
ByteArrayOutputStream(int a) Bu yapıcı, belirli bir boyuta sahip bir bellek içi arabellek oluşturur.

Ve bu, sınıfın içeride nasıl göründüğü:

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

ByteArrayOutputStream sınıfının yöntemleri

Şimdi sınıfımızda kullanabileceğimiz yöntemlerden bahsedelim.

Akışımıza bir şeyler koymaya çalışalım. Bunu yapmak için write() yöntemini kullanacağız — yazmak için bir bayt veya bir dizi bayt kabul edebilir.

Yöntem
geçersiz yazma(int b) Bir bayt yazar.
geçersiz yazma(bayt b[], int kapalı, int len) Belirli bir boyutta bir bayt dizisi yazar.
geçersiz yazmaBytes(bayt b[]) Bir bayt dizisi yazar.
geçersiz yazmaTo(OutputStream çıkışı) Geçerli çıkış akışındaki tüm verileri geçirilen çıkış akışına yazar.

Yöntem uygulaması:

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

Sonuç, şuna benzeyen yeni bir output.txt dosyasıdır:

toByteArray () yöntemi, bu çıktı akışının geçerli içeriğini bir bayt dizisi olarak döndürür. Ve buf bayt dizisini metin olarak almak için toString() yöntemini kullanabilirsiniz :

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

Tamponumuz, kendisine ilettiğimiz bayt dizisini içerir.

reset () yöntemi, bayt dizisi çıktı akışındaki geçerli bayt sayısını sıfırlar (böylece çıktıda biriken her şey sıfırlanır).

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

reset() yöntemini çağırdıktan sonra arabelleğimizi görüntülediğimizde hiçbir şey alamıyoruz.

close() yönteminin belirli özellikleri

Bu yöntem özel ilgiyi hak ediyor. Ne işe yaradığını anlamak için içeriye bir göz atalım:

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

ByteArrayOutputStream sınıfının close() yönteminin aslında hiçbir şey yapmadığına dikkat edin .

Nedenmiş? Bir ByteArrayOutputStream , bellek tabanlı bir akıştır (yani, kullanıcı tarafından kodda yönetilir ve doldurulur), bu nedenle close() çağrısının hiçbir etkisi yoktur.

Pratik

Şimdi ByteArrayOutputStream ve ByteArrayInputStream'imizi kullanarak bir dosya sistemi uygulamaya çalışalım .

Singleton tasarım modelini kullanarak bir FileSystem sınıfı yazalım ve statik bir HashMap<String, byte[]> kullanalım , burada:

  • Dize, bir dosyanın yoludur
  • bayt[] kaydedilen dosyadaki verilerdir
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");
       }
   }
}

Bu sınıfta aşağıdaki genel yöntemleri oluşturduk:

  • standart CRUD yöntemleri (oluştur, oku, güncelle, sil),
  • bir dosyanın var olup olmadığını kontrol etmek için bir yöntem,
  • dosya sisteminin bir örneğini alma yöntemi.

Bir dosyadan okumak için bir InputStream döndürürüz . Kaputun altında ByteArrayInputStream uygulaması var. Tampon, dosya haritasında saklanan bir bayt dizisidir .

Bir başka ilginç yöntem de newOutputStream yöntemidir . Bu yöntem çağrıldığında, iki yöntemi geçersiz kılan yeni bir ByteArrayOutputStream nesnesi döndürürüz: flush ve close . Bu yöntemlerden birinin çağrılması, yazmanın gerçekleşmesine neden olmalıdır.

Ve biz de tam olarak bunu yapıyoruz: kullanıcının yazdığı bayt dizisini alıyoruz ve bir kopyasını şu şekilde saklıyoruz:değerdosyalar haritasında uygun bir anahtarla.

Dosya sistemimizi (FS) test etmek için aşağıdaki kodu kullanıyoruz:

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

Test sırasında aşağıdaki eylemleri doğrularız:

  1. Yeni bir dosya oluşturuyoruz.
  2. Oluşturulan dosyanın boş olup olmadığını kontrol ediyoruz.
  3. Dosyaya bazı veriler yazıyoruz.
  4. Verileri tekrar okuruz ve yazdıklarımızla eşleştiğini doğrularız.
  5. Dosyayı siliyoruz.
  6. Dosyanın silindiğini doğrularız.

Bu kodu çalıştırmak bize şu çıktıyı verir:

Dosya başarıyla oluşturuldu
Dosya içeriği:
Dosyaya yazılan veriler
Dosya içeriği: CodeGym
Dosya var: false

Bu örnek neden gerekliydi?

Basitçe söylemek gerekirse, veriler her zaman bir bayt kümesidir. Diskten/diske çok fazla veri okumanız/yazmanız gerekiyorsa, G/Ç sorunları nedeniyle kodunuz yavaş çalışacaktır. Bu durumda, sanal bir dosya sistemini RAM'de tutmak ve onunla geleneksel bir diskle yaptığınız gibi çalışmak mantıklıdır. Ve InputStream ve OutputStream'den daha basit ne olabilir ?

Elbette bu, üretime hazır kod değil, talimat için bir örnektir. Aşağıdakileri hesaba katmaz (aşağıdaki liste kapsamlı değildir):

  • çoklu iş parçacığı
  • dosya boyutu sınırları (çalışan bir JVM için kullanılabilir RAM miktarı)
  • yol yapısının doğrulanması
  • yöntem bağımsız değişkenlerinin doğrulanması

İlginç içeriden bilgi:
CodeGym görev doğrulama sunucusu biraz benzer bir yaklaşım kullanır. Sanal bir FS oluşturuyoruz, görev doğrulaması için hangi testlerin yapılması gerektiğini belirliyoruz, testleri çalıştırıyoruz ve sonuçları okuyoruz.

Sonuç ve büyük soru

Bu dersi okuduktan sonraki büyük soru, " Daha kullanışlı olduğu ve kısıtlamalar getirmediği için neden byte[] kullanamıyorum ?"

ByteArrayInputStream'in avantajı , güçlü olmasının salt okunur baytlar kullanacağınızı belirtmesidir (çünkü akış, içeriğini değiştirmek için bir arabirim sağlamaz). Bununla birlikte, bir programcının hala baytlara doğrudan erişebileceğini not etmek önemlidir .

Ancak bazen bir byte[] , bazen bir dosyanız, bazen bir ağ bağlantınız vb. varsa, "bir bayt akışı" için bir tür soyutlamaya ihtiyacınız olacak ve nerede oldukları umrumda değil dan geliyorum". Ve işte InputStream budur . Kaynak bir bayt dizisi olduğunda, ByteArrayInputStream kullanmak için iyi bir InputStream'dir .

Bu, birçok durumda yardımcı olur, ancak burada iki özel örnek verilmiştir:

  1. Bayt alan ve bunları bir şekilde işleyen bir kitaplık yazıyorsunuz (örneğin, resim işleme yardımcı programlarından oluşan bir kitaplık olduğunu varsayalım). Kitaplığınızın kullanıcıları, bir dosyadan, bir bellek içi bayttan [] veya başka bir kaynaktan bayt sağlayabilir. Böylece, bir InputStream kabul eden bir arabirim sağlarsınız ; bu, eğer bir byte[]'e sahiplerse , onu bir ByteArrayInputStream içine sarmaları gerektiği anlamına gelir .

  2. Bir ağ bağlantısını okuyan kod yazıyorsunuz. Ancak bu kod üzerinde birim testleri gerçekleştirmek için bir bağlantı açmak istemezsiniz; yalnızca koda birkaç bayt beslemek istersiniz. Böylece kod bir InputStream alır ve testiniz bir ByteArrayInputStream içinde geçer .