คลาส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

มาพูดคุยเกี่ยวกับวิธีการที่เราสามารถใช้ในชั้นเรียนของเรา

มาลองใส่อะไรบางอย่างในสตรีมของเรา ในการทำเช่นนี้ เราจะใช้ วิธี การเขียน () — สามารถรับหนึ่งไบต์หรือหนึ่งชุดของไบต์สำหรับการเขียน

วิธี
เขียนเป็นโมฆะ (int b) เขียนหนึ่งไบต์
เขียนเป็นโมฆะ (ไบต์ b [], int off, 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]);
    }
}
    

บัฟเฟอร์ของเรามีอาร์เรย์ไบต์ที่เราส่งไป

วิธี การรีเซ็ต ()จะรีเซ็ตจำนวนไบต์ที่ถูกต้องในสตรีมเอาต์พุตอาร์เรย์ไบต์ให้เป็นศูนย์ (ดังนั้นทุกอย่างที่สะสมในเอาต์พุตจะถูกรีเซ็ต)


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

โปรดทราบว่า เมธอด close()ของคลาสByteArrayOutputStreamไม่ได้ทำอะไรเลย

ทำไมถึงเป็นเช่นนั้น? ByteArrayOutputStream เป็นสตรีมที่ใช้หน่วยความจำ (กล่าวคือ จัดการและเติมข้อมูลโดยผู้ใช้ในโค้ด) ดังนั้นการเรียกclose() จึงไม่มีผลใดๆ

ฝึกฝน

ตอนนี้ มาลองใช้ระบบไฟล์โดยใช้ByteArrayOutputStream และ ByteArrayInputStream ของ เรา

มาเขียน คลาส FileSystemโดยใช้ รูปแบบการออกแบบ singletonและใช้ Static 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 ใหม่ที่แทนที่เมธอดสองเมธอด: flushและclose การเรียกใช้เมธอดเหล่านี้ควรทำให้การเขียนเกิดขึ้น

และนั่นคือสิ่งที่เราทำ: เราได้รับอาร์เรย์ไบต์ที่ผู้ใช้เขียนถึง และจัดเก็บสำเนาเป็นค่าใน การแม็พ ไฟล์ด้วยคีย์ที่เหมาะสม

เราใช้รหัสต่อไปนี้เพื่อทดสอบระบบไฟล์ของเรา (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: เท็จ

ทำไมตัวอย่างนี้ถึงจำเป็น?

พูดง่ายๆ ก็คือ ข้อมูลจะเป็นชุดของไบต์เสมอ หากคุณต้องการอ่าน/เขียนข้อมูลจำนวนมากจาก/ไปยังดิสก์ โค้ดของคุณจะทำงานช้าเนื่องจากปัญหา I/O ในกรณีนี้ การรักษาระบบไฟล์เสมือนใน RAM เป็นเรื่องที่สมเหตุสมผล โดยทำงานร่วมกับระบบในลักษณะเดียวกับที่คุณทำกับดิสก์แบบเดิม และอะไรจะง่ายไปกว่าInputStreamและOutputStream ?

แน่นอนว่านี่เป็นตัวอย่างสำหรับคำแนะนำ ไม่ใช่โค้ดที่พร้อมสำหรับการผลิต ไม่นับ (รายการต่อไปนี้ไม่ครอบคลุม):

  • มัลติเธรด
  • ขีดจำกัดขนาดไฟล์ (จำนวน RAM ที่มีอยู่สำหรับ JVM ที่รันอยู่)
  • การตรวจสอบโครงสร้างเส้นทาง
  • การตรวจสอบอาร์กิวเมนต์เมธอด

ข้อมูลวงในที่น่าสนใจ:
เซิร์ฟเวอร์การตรวจสอบงาน CodeGym ใช้วิธีการที่คล้ายกัน เราสร้าง FS เสมือน กำหนดว่าการทดสอบใดจำเป็นต้องเรียกใช้สำหรับการตรวจสอบงาน เรียกใช้การทดสอบ และอ่านผลลัพธ์

บทสรุปและคำถามใหญ่

คำถามใหญ่หลังจากอ่านบทเรียนนี้คือ "ทำไมฉันถึงใช้ byte[]ไม่ได้เนื่องจากสะดวกกว่าและไม่มีข้อจำกัด"

ข้อได้เปรียบของByteArrayInputStreamคือมันบ่งชี้ว่าคุณกำลังจะใช้ไบต์แบบอ่านอย่างเดียว (เนื่องจากสตรีมไม่มีอินเทอร์เฟซสำหรับเปลี่ยนเนื้อหา) ที่กล่าวว่าเป็นสิ่งสำคัญที่จะต้องทราบว่าโปรแกรมเมอร์ ยังคง สามารถเข้าถึงไบต์ได้โดยตรง

แต่ถ้าบางครั้งคุณมีbyte[]บางครั้งคุณมีไฟล์ บางครั้งคุณมีการเชื่อมต่อเครือข่าย และอื่นๆ คุณจะต้องมีสิ่งที่เป็นนามธรรมสำหรับ "a stream of bytes และฉันไม่สนใจว่าพวกมันจะอยู่ที่ไหน มาจาก". และนั่นคือสิ่งที่InputStreamเป็น เมื่อแหล่งที่มาเป็นอาร์เรย์ไบต์ByteArrayInputStreamจะเป็นInputStream ที่ดี ที่จะใช้

สิ่งนี้มีประโยชน์ในหลาย ๆ สถานการณ์ แต่นี่คือตัวอย่างเฉพาะสองตัวอย่าง:

  1. คุณกำลังเขียนไลบรารีที่รับไบต์และประมวลผลด้วยวิธีใดวิธีหนึ่ง (ตัวอย่างเช่น สมมติว่าเป็นไลบรารีของโปรแกรมอรรถประโยชน์การประมวลผลภาพ) ผู้ใช้ไลบรารีของคุณสามารถระบุไบต์จากไฟล์ จากไบต์ในหน่วยความจำ[]หรือจากแหล่งอื่น ดังนั้นคุณจึงจัดเตรียมอินเทอร์เฟ ซที่ยอมรับInputStreamซึ่งหมายความว่าหากมีbyte[]พวกเขาจำเป็นต้องรวมไว้ในByteArrayInputStream

  2. คุณกำลังเขียนโค้ดที่อ่านการเชื่อมต่อเครือข่าย แต่ในการทำการทดสอบหน่วยกับรหัสนี้ คุณไม่ต้องการเปิดการเชื่อมต่อ — คุณเพียงแค่ต้องการป้อนโค้ดไม่กี่ไบต์ ดังนั้นโค้ดจะใช้InputStream และการ ทดสอบของคุณผ่านในByteArrayInputStream