ByteArrayOutputStreamクラス、データをバイト配列に書き込む出力ストリームを実装します。バッファーは、データが書き込まれると自動的に増加します。

ByteArrayOutputStreamクラスメモリ内にバッファを作成し、ストリームに送信されたすべてのデータはバッファに格納されます。

ByteArrayOutputStream コンストラクター

ByteArrayOutputStreamクラスに次のコンストラクターがあります。

コンストラクタ
ByteArrayOutputStream() このコンストラクターは、32 バイト長のメモリー内バッファーを作成します。
ByteArrayOutputStream(int a) このコンストラクターは、特定のサイズのメモリ内バッファーを作成します。

そして、クラスの内部は次のようになります。


// 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 クラスのメソッド

クラスで使用できるメソッドについて話しましょう。

ストリームに何かを入れてみましょう。これを行うには、 write()メソッドを使用します。このメソッドは、書き込み用に 1 バイトまたはバイトのセットを受け入れることができます。

方法
無効な書き込み(int b) 1バイトを書き込みます。
void write(byte b[], int off, int len) 特定のサイズのバイトの配列を書き込みます。
void writeBytes(バイト b[]) バイトの配列を書き込みます。
void writeTo(OutputStream out) 現在の出力ストリームから渡された出力ストリームにすべてのデータを書き込みます。

メソッドの実装:


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

結果は、次のような新しいOutput.txtファイルになります。

toByteArray ()メソッドは、この出力ストリームの現在の内容をバイト配列として返します。toString()メソッドを使用して、bufバイト配列をテキストとして取得できます。


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

バッファには、渡したバイト配列が含まれています。

replace ()メソッドは、バイト配列出力ストリーム内の有効なバイト数をゼロにリセットします (そのため、出力に蓄積されたすべてがリセットされます)。


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

replace()メソッドを呼び出した後にバッファを表示しても、何も得られません。

close() メソッドの具体的な機能

この方法は特に注意が必要です。これが何をするのかを理解するために、内部を覗いてみましょう。


/**
 * 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クラスのclose()メソッドは実際には何も実行しないことに注意してください。

何故ですか?ByteArrayOutputStreamはメモリベースのストリーム (つまり、コード内でユーザーによって管理および設定されます) であるため、close()呼び出しても効果はありません。

練習

次に、 ByteArrayOutputStreamByteArrayInputStreamを使用してファイル システムを実装してみましょう。

シングルトン設計パターンを使用してFileSystemクラスを作成し、静的HashMap<String, byte[]>を使用してみましょう。

  • 文字列はファイルへのパスです
  • byte[]は保存されたファイル内のデータです

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

このクラスでは、次のパブリック メソッドを作成しました。

  • 標準 CRUD メソッド (作成、読み取り、更新、削除)、
  • ファイルが存在するかどうかを確認するメソッド、
  • ファイルシステムのインスタンスを取得するメソッド。

ファイルから読み取るには、InputStreamを返します。内部にはByteArrayInputStream実装があります。バッファーは、ファイルマップに格納されるバイト配列です。

もう 1 つの興味深いメソッドはnewOutputStreamです。このメソッドが呼び出されると、 flushcloseの 2 つのメソッドをオーバーライドする新しいByteArrayOutputStreamオブジェクトが返されます。これらのメソッドのいずれかを呼び出すと、書き込みが行われます。

そして、それはまさに私たちが行っていることです。ユーザーが書き込んだバイト配列を取得し、そのコピーを価値ファイルマップ内で適切なキーを使用します。

次のコードを使用してファイル システム (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);
   }
}
    

テスト中、次のアクションを検証します。

  1. 新しいファイルを作成します。
  2. 作成したファイルが空であることを確認します。
  3. いくつかのデータをファイルに書き込みます。
  4. データを読み返して、書き込んだ内容と一致していることを確認します。
  5. ファイルを削除します。
  6. ファイルが削除されたことを確認します。

このコードを実行すると、次の出力が得られます。

ファイルは正常に作成されました
ファイルの内容:
ファイルに書き込まれたデータ
ファイルの内容: CodeGym
ファイルの存在: false

なぜこの例が必要だったのでしょうか?

簡単に言えば、データは常にバイトのセットです。ディスクに対して大量のデータを読み書きする必要がある場合、I/O の問題によりコードの実行が遅くなります。この場合、仮想ファイル システムを RAM 内に維持し、従来のディスクと同じように操作することが合理的です。そして、 InputStreamOutputStreamよりも単純なものは何でしょうか?

もちろん、これは説明のための例であり、実稼働対応のコードではありません。これは考慮されていません (次のリストは包括的なものではありません)。

  • マルチスレッド
  • ファイル サイズ制限 (実行中の JVM に使用可能な RAM の量)
  • パス構造の検証
  • メソッド引数の検証

興味深い内部情報:
CodeGym タスク検証サーバーは、ある程度似たアプローチを使用しています。仮想 FS をスピンアップし、タスク検証のためにどのテストを実行する必要があるかを決定し、テストを実行して、結果を読み取ります。

結論と大きな疑問

このレッスンを読んだ後の大きな疑問は、「 byte[] の方が便利で制限も課されないのに、なぜ単純に使用できないのですか?」ということです。

ByteArrayInputStreamの利点は、読み取り専用バイトを使用することを強く示すことです (ストリームはコンテンツを変更するためのインターフェイスを提供しないため)。とはいえ、プログラマは引き続きバイトに直接アクセスできることに注意することが重要です。

しかし、 byte[]がある場合、ファイルがある場合、ネットワーク接続がある場合などがある場合、「バイトのストリーム」に対するある種の抽象化が必要になりますが、それらがどこにあるかは気にしません。から来る"。それがInputStreamです。ソースがバイト配列の場合、ByteArrayInputStream が使用するのに適したInputStreamです。

これは多くの状況で役立ちますが、ここでは 2 つの具体的な例を示します。

  1. バイトを受け取り、何らかの方法で処理するライブラリを作成しています (たとえば、それが画像処理ユーティリティのライブラリであるとします)。ライブラリのユーザーは、ファイル、メモリ内のbyte[]、またはその他のソースからバイトを提供できます。したがって、 InputStreamを受け入れるインターフェイスを提供します。つまり、byte[]がある場合は、それをByteArrayInputStreamでラップする必要があります。

  2. ネットワーク接続を読み取るコードを作成しています。ただし、このコードで単体テストを実行するには、接続を開く必要はありません。コードに数バイトをフィードするだけです。したがって、コードはInputStreamを受け取り、テストはByteArrayInputStreamに渡します。