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바이트 씁니다.
무효 쓰기(바이트 b[], int 꺼짐, int len) 특정 크기의 바이트 배열을 씁니다.
무효 writeBytes(바이트 b[]) 바이트 배열을 씁니다.
무효 writeTo(OutputStream 출력) 현재 출력 스트림의 모든 데이터를 전달된 출력 스트림에 씁니다.

방법 구현:


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

버퍼에는 전달한 바이트 배열이 포함되어 있습니다.

reset () 메서드는 바이트 배열 출력 스트림의 유효한 바이트 수를 0으로 재설정합니다(따라서 출력에 누적된 모든 항목이 재설정됨).


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() 메서드를 호출한 후 버퍼를 표시하면 아무것도 얻지 못합니다.

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 구현이 있습니다. 버퍼는 파일 맵에 저장된 바이트 배열입니다.

또 다른 흥미로운 방법은 newOutputStream 입니다 . 이 메서드가 호출되면 플러시닫기 라는 두 메서드를 재정의하는 새 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 입니다.

이것은 많은 상황에서 도움이 되지만 다음은 두 가지 구체적인 예입니다.

  1. 바이트를 수신하고 어떻게든 처리하는 라이브러리를 작성하고 있습니다(예: 이미지 처리 유틸리티 라이브러리라고 가정). 라이브러리 사용자는 파일, 메모리 내 byte[] 또는 다른 소스에서 바이트를 제공할 수 있습니다. 따라서 InputStream 을 허용하는 인터페이스를 제공합니다 . 즉, byte[] 가 있는 경우 이를 ByteArrayInputStream 으로 래핑해야 합니다 .

  2. 네트워크 연결을 읽는 코드를 작성하고 있습니다. 하지만 이 코드에서 단위 테스트를 수행하려면 연결을 여는 것이 아니라 코드에 몇 바이트만 입력하면 됩니다. 따라서 코드는 InputStream을 사용 하고 테스트는 ByteArrayInputStream 을 통과합니다 .