ByteArrayOutputStream的扩容机制

53 阅读5分钟

ByteArrayOutputStream 基本介绍

ByteArrayOutputStream 是 Java IO 体系中 OutputStream 抽象类的核心实现类之一,其核心功能是将数据写入内存中的字节数组缓冲区,而非直接写入磁盘或网络等外部设备。作为一种内存基流,它具备缓冲区自动扩容、数据快速存取等特性,是处理内存中字节数据的关键工具。

核心特性:

  • 内存存储:所有写入的数据均存储在内部字节数组中,避免了磁盘 I/O 操作的性能开销,数据处理速度更快。

  • 动态扩容:初始化时可指定缓冲区容量,当写入数据超过当前容量时,缓冲区会自动扩容,无需手动管理数组大小。

  • 便捷的数据提取:提供 toByteArray() 方法可直接获取缓冲区中的完整字节数组,也可通过 toString() 方法转换为指定编码的字符串。

  • 关闭无影响:作为内存流,关闭(调用 close() 方法)后仍可正常调用其他方法,不会抛出 IOException,因为其不依赖外部资源句柄。

  • AutoCloseable 支持:实现了 AutoCloseable 接口,可配合 try-with-resources 语法实现自动资源管理,代码更简洁规范。

基于源码进行扩容机制解析

一、关键属性

字节数组buf[]、已用数组长度标记变量count

      
    //字节数组
    protected byte buf[];
    
    //已使用字节数组长度
    protected int count;

二、构造方法

        //字节数组默认初始长度为32
    public ByteArrayOutputStream() {
        this(32);
    }

    //自定义字节数组初始化长度的构造函数
    public ByteArrayOutputStream(int size) {
        if (size < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                                               + size);
        }
        buf = new byte[size];
    }

三、具体扩容步骤

1:写入方法内部都会执行一个ensureCapacity()方法,判断当前字节数组是否需要扩容,需要便进行扩容

//写入单个字节
public synchronized void write(int b) {
        //扩容方法
        ensureCapacity(count + 1);
        buf[count] = (byte) b;
        count += 1;
    }
    //写入一个字节数组
    public synchronized void write(byte b[], int off, int len) {
        if ((off < 0) || (off > b.length) || (len < 0) ||
            ((off + len) - b.length > 0)) {
            throw new IndexOutOfBoundsException();
        }
        //扩容方法
        ensureCapacity(count + len);
        System.arraycopy(b, off, buf, count, len);
        count += len;
    }

2:ensureCapacity()判断是否需要进行扩容,扩容执行grow(minCapacity)方法

    private void ensureCapacity(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - buf.length > 0)
            grow(minCapacity);
    }

3:grow(int minCapacity),默认进行两倍扩容,若是两倍扩容还是无法满足写入的需求,则扩容之后的长度设置为最小需要的长度,若是扩容之后的长度超过了字节数组的最大长度限制(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 约为2GB),则执行 hugeCapacity(minCapacity)

    private void grow(int minCapacity) {
        // 原字节数组的长度
        int oldCapacity = buf.length;
        //原长度的两倍
        int newCapacity = oldCapacity << 1;
        //扩容两倍不满足:扩容为最小需要长度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //扩容长度超过限制,执行hugeCapacity()
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //复制并且创建新的字节数组
        buf = Arrays.copyOf(buf, newCapacity);
    }

4:方法确保进行扩容的时候不会超过JVM数组大小的限制:

当minCapacity为负数时(表示整数溢出),抛出OutOfMemoryError 当minCapacity超过最大数组大小限制时,返回Integer.MAX_VALUE 否则返回MAX_ARRAY_SIZE(即Integer.MAX_VALUE - 8)

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

实战示例

内存缓冲区:动态构建字节数组

当需要逐步拼接多个字节数据片段(如多段字符串、文件片段),且无法提前确定总长度时,使用 ByteArrayOutputStream 作为缓冲区可避免手动计算数组大小的繁琐。

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class BufferExample {
    public static void main(String[] args) {
        // 模拟多段待拼接数据
        String part1 = "Hello, ";
        String part2 = "ByteArrayOutputStream! ";
        String part3 = "This is a buffer test.";
        
        // try-with-resources 自动管理资源
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            // 逐步写入多段数据
            bos.write(part1.getBytes("UTF-8"));
            bos.write(part2.getBytes("UTF-8"));
            bos.write(part3.getBytes("UTF-8"));
            
            // 提取完整字节数组并转换为字符串
            byte[] resultBytes = bos.toByteArray();
            String result = new String(resultBytes, "UTF-8");
            
            System.out.println("拼接结果:" + result);
            // 输出:拼接结果:Hello, ByteArrayOutputStream! This is a buffer test.
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

数据转换与合并:多来源数据统一处理

当需要将不同来源的字节数据(如字符串、文件、网络响应)合并为一个统一的字节数组时,ByteArrayOutputStream 可作为数据“收集器”,高效整合多源数据。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class DataMergeExample {
    public static void main(String[] args) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            // 1. 写入字符串数据
            String strData = "来源:字符串数据\n";
            bos.write(strData.getBytes("UTF-8"));
            
            // 2. 写入文件数据(模拟读取小型配置文件)
            try (FileInputStream fis = new FileInputStream("config.txt")) {
                byte[] buf = new byte[1024];
                int len;
                while ((len = fis.read(buf)) != -1) {
                    bos.write(buf, 0, len); // 写入文件的有效数据片段
                }
            }
            
            // 3. 写入网络模拟数据(用 ByteArrayInputStream 模拟)
            byte[] networkData = "来源:网络响应数据".getBytes("UTF-8");
            try (ByteArrayInputStream bis = new ByteArrayInputStream(networkData)) {
                int b;
                while ((b = bis.read()) != -1) {
                    bos.write(b);
                }
            }
            
            // 提取合并后的完整数据
            byte[] mergedData = bos.toByteArray();
            System.out.println("合并后的数据:\n" + new String(mergedData, "UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

临时缓存:内存中暂存数据

在需临时缓存数据且无需持久化的场景(如网络请求重试前暂存响应数据、多步骤处理中间数据),ByteArrayOutputStream 可替代临时文件,减少磁盘 I/O 并提升效率。

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class TempCacheExample {
    public static void main(String[] args) {
        // 模拟网络请求获取数据并缓存
        ByteArrayOutputStream cacheBos = new ByteArrayOutputStream();
        try {
            // 模拟第一次网络请求成功,缓存数据
            byte[] networkResponse = fetchDataFromNetwork();
            cacheBos.write(networkResponse);
            System.out.println("数据已缓存至内存");
            
            // 模拟后续处理:直接从缓存提取数据
            processData(cacheBos.toByteArray());
            
            // 如需重试,可重置缓存重新写入
            cacheBos.reset();
            byte[] newResponse = fetchDataFromNetwork();
            cacheBos.write(newResponse);
            System.out.println("缓存已更新并重新处理");
            processData(cacheBos.toByteArray());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    // 模拟网络请求
    private static byte[] fetchDataFromNetwork() {
        return "模拟网络响应数据".getBytes();
    }
    
    // 模拟数据处理
    private static void processData(byte[] data) {
        System.out.println("处理数据:" + new String(data));
    }
}