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。