Класът ByteArrayOutputStream реализира изходен поток, който записва данни в byteов масив. Буферът нараства автоматично, докато данните се записват в него.
Класът ByteArrayOutputStream създава буфер в паметта и всички данни, изпратени към потока, се съхраняват в буфера.

ByteArrayOutputStream конструктори
Класът ByteArrayOutputStream има следните конструктори:
Конструктор | |
---|---|
ByteArrayOutputStream() | Този конструктор създава буфер в паметта с дължина 32 byteа. |
ByteArrayOutputStream(int a) | Този конструктор създава буфер в паметта с определен размер. |
Ето How изглежда класът отвътре:
// 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() — той може да приеме един byte or набор от byteове за запис.
Метод | |
---|---|
void write(int b) | Записва един byte. |
void write(byte b[], int off, int len) | Записва масив от byteове с определен размер. |
void writeBytes(byte b[]) | Записва масив от byteове. |
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() връща текущото съдържание на този изходен поток като масив от byteове. И можете да използвате метода toString(), за да получите buf byteовия масив като текст:
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]);
}
}
Нашият буфер съдържа byteовия масив, който сме му предали.
Методът reset() нулира броя на валидните byteове в изходния поток от byteов масив до нула (така че всичко, натрупано в изхода, се нулира).
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().
Този метод заслужава специално внимание. За да разберем Howво прави, нека надникнем вътре:
/**
* 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 е базиран на памет поток (т.е. той се управлява и попълва от потребителя в code), така че извикването на close () няма ефект.
Практикувайте
Сега нека се опитаме да внедрим файлова система, използвайки нашите ByteArrayOutputStream и ByteArrayInputStream .
Нека напишем клас 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 . Буферът е byteов масив, съхраняван в картата на файловете .
Друг интересен метод е newOutputStream . Когато този метод бъде извикан, ние връщаме нов обект ByteArrayOutputStream , който замества два метода: flush и close . Извикването на някой от тези методи трябва да доведе до извършване на запис.
И точно това правим: получаваме byteовия масив, в който потребителят е написал, и съхраняваме копие катостойноств картата на файловете с подходящ ключ.
Използваме следния code, за да тестваме нашата файлова система (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);
}
}
По време на теста ние проверяваме следните действия:
- Създаваме нов файл.
- Проверяваме дали създаденият файл е празен.
- Записваме някои данни във file.
- Прочитаме обратно данните и проверяваме дали съвпадат с написаното от нас.
- Изтриваме file.
- Проверяваме дали файлът е изтрит.
Изпълнението на този code ни дава този резултат:
Съдържание на file:
Данни, записани във файл
Съдържание на file: CodeGym
Файлът съществува: невярно
Защо беше необходим този пример?
Казано просто, данните винаги са набор от byteове. Ако трябва да прочетете/запишете много данни от/на диск, вашият code ще работи бавно поради I/O проблеми. В този случай има смисъл да поддържате виртуална файлова система в RAM, като работите с нея по същия начин, по който бихте работor с традиционен диск. И Howво може да бъде по-просто от InputStream и OutputStream ?
Разбира се, това е пример за инструкция, а не готов за производство code. Той НЕ отчита (следният списък не е изчерпателен):
- многопоточност
- ограничения на размера на file (количеството налична RAM за работеща JVM)
- проверка на структурата на пътя
- проверка на аргументите на метода
Интересна вътрешна информация:
Сървърът за проверка на задачите на CodeGym използва донякъде подобен подход. Ние завъртаме виртуален FS, определяме кои тестове трябва да бъдат изпълнени за проверка на задачата, изпълняваме тестовете и четем резултатите.
Заключение и големият въпрос
Големият въпрос след прочитането на този урок е "Защо не мога да използвам просто byte[] , след като е по-удобно и не налага ограничения?"
Предимството на ByteArrayInputStream е, че той силно показва, че ще използвате byteове само за четене (тъй като потокът не предоставя интерфейс за промяна на съдържанието му). Въпреки това е важно да се отбележи, че програмистът все още може да има директен достъп до byteовете.
Но ако понякога имате byte[] , понякога имате файл, понякога имате мрежова връзка и т.н., ще ви трябва няHowва абстракция за „поток от byteове и не ме интересува къде идвам от". И това е InputStream . Когато източникът се окаже byteов масив, ByteArrayInputStream е добър InputStream за използване.
Това е полезно в много ситуации, но ето два конкретни примера:
-
Вие пишете библиотека, която получава byteове и ги обработва по няHowъв начин (например, да предположим, че това е библиотека от помощни програми за обработка на изображения). Потребителите на вашата библиотека могат да предоставят byteове от файл, от byte в паметта[] or от няHowъв друг източник. Така че предоставяте интерфейс, който приема InputStream , което означава, че ако имат byte[] , те трябва да го обвият в ByteArrayInputStream .
-
Вие пишете code, който чете мрежова връзка. Но за да извършите модулни тестове на този code, не искате да отваряте връзка — просто искате да подадете няколко byteа към codeа. Така че codeът приема InputStream и вашият тест преминава в ByteArrayInputStream .
GO TO FULL VERSION