ByteArrayOutputStream類實現將數據寫入字節數組的輸出流。緩衝區會隨著數據寫入而自動增長。
ByteArrayOutputStream類在內存中創建一個緩衝區,所有發送到流的數據都存儲在緩衝區中。

ByteArrayOutputStream 構造函數
ByteArrayOutputStream類具有以下構造函數:
構造器 | |
---|---|
字節數組輸出流() | 此構造函數創建一個 32 字節長的內存緩衝區。 |
字節數組輸出流(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()方法——它可以接受一個字節或一組字節進行寫入。
方法 | |
---|---|
無效寫入(int b) | 寫入一個字節。 |
void write(byte b[], int off, int len) | 寫入特定大小的字節數組。 |
void writeBytes(字節 b[]) | 寫入一個字節數組。 |
void 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 ()方法將字節數組輸出流中的有效字節數重置為零(因此輸出中累積的所有內容都將重置)。
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()無效。
實踐
現在讓我們嘗試使用我們的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實現。緩衝區是存儲在文件映射中的字節數組。
另一個有趣的方法是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);
}
}
在測試期間,我們驗證以下操作:
- 我們創建一個新文件。
- 我們檢查創建的文件是否為空。
- 我們將一些數據寫入文件。
- 我們回讀數據並驗證它是否與我們寫入的內容相匹配。
- 我們刪除文件。
- 我們確認該文件已被刪除。
運行這段代碼給我們這個輸出:
文件內容:
寫入文件的數據
文件內容:CodeGym
文件存在:false
為什麼這個例子是必要的?
簡而言之,數據始終是一組字節。如果您需要從磁盤讀取/寫入大量數據,由於 I/O 問題,您的代碼將運行緩慢。在這種情況下,在 RAM 中維護一個虛擬文件系統是有意義的,就像使用傳統磁盤一樣使用它。還有什麼比InputStream和OutputStream更簡單的呢?
當然,這是一個指令示例,而不是生產就緒代碼。它不考慮(以下列表不完整):
- 多線程
- 文件大小限制(運行中的 JVM 的可用 RAM 量)
- 路徑結構的驗證
- 方法參數的驗證
有趣的內幕信息:
CodeGym 任務驗證服務器使用了某種類似的方法。我們啟動一個虛擬 FS,確定需要運行哪些測試以進行任務驗證,運行測試並讀取結果。
結論和大問題
讀完這節課後最大的問題是“為什麼我不能只使用byte[],因為它更方便而且不施加限制?”
ByteArrayInputStream的優點是它強烈指示您將使用只讀字節(因為流不提供更改其內容的接口)。也就是說,重要的是要注意程序員仍然可以直接訪問字節。
但是如果有時你有一個byte[],有時你有一個文件,有時你有一個網絡連接,等等,你將需要某種抽象“字節流,我不在乎它們在哪裡來自”。這就是InputStream。當源恰好是字節數組時,ByteArrayInputStream是一個很好用的InputStream 。
這在許多情況下都很有用,但這裡有兩個具體示例:
-
您正在編寫一個接收字節並以某種方式處理它們的庫(例如,假設它是一個圖像處理實用程序庫)。您的庫的用戶可以提供來自文件、內存中byte[]或其他來源的字節。所以你提供了一個接受InputStream 的接口,這意味著如果他們有一個byte[],他們需要將它包裝在一個ByteArrayInputStream中。
-
您正在編寫讀取網絡連接的代碼。但是要對此代碼執行單元測試,您不想打開連接——您只想向代碼提供幾個字節。因此代碼採用InputStream並且您的測試通過ByteArrayInputStream。
GO TO FULL VERSION