12、ByteArrayInputStream和DataInputStream的源码分析和使用方法详细分析一、基础概念什么是输入流在Java中输入流InputStream是用于从数据源读取数据的抽象类。想象你有一个水龙头InputStream就是连接水龙头的水管而数据就是水流。不同的InputStream实现类代表不同类型的水源文件FileInputStream、字节数组ByteArrayInputStream等。关键点- InputStream是字节流按字节读取- 所有输入流都继承自java.io.InputStream- 需要配合其他类如DataInputStream来解析复杂数据## 二、ByteArrayInputStream从内存数组读取数据### 2.1 核心原理ByteArrayInputStream允许我们将一个字节数组作为数据源就像从文件中读取一样。它的底层原理非常简单内部维护一个字节数组引用和一个指针pos每次读取都从指针位置获取字节。源码分析简化版javapublic class ByteArrayInputStream extends InputStream { protected byte buf[]; // 存储数据的数组 protected int pos; // 当前读取位置 protected int count; // 有效字节数 // 构造函数接收字节数组 public ByteArrayInputStream(byte buf[]) { this.buf buf; this.pos 0; this.count buf.length; } // 读取一个字节 public synchronized int read() { return (pos count) ? (buf[pos] 0xff) : -1; }}关键设计- 不复制数组直接引用注意外部修改数组会影响流- 线程安全使用synchronized- 读取完返回-1标准结束标记### 2.2 基本使用方法java// 示例1ByteArrayInputStream基础用法import java.io.ByteArrayInputStream;public class ByteArrayExample1 { public static void main(String[] args) { // 创建数据源一个字节数组 byte[] data {72, 101, 108, 108, 111}; // 对应 Hello 的ASCII码 // 创建ByteArrayInputStream ByteArrayInputStream bais new ByteArrayInputStream(data); int byteRead; System.out.print(读取的数据); // 逐个读取字节直到返回-1 while ((byteRead bais.read()) ! -1) { System.out.print((char) byteRead ); // 转为字符显示 } // 输出H e l l o // 注意流不会关闭因为它不占用系统资源 // 但为了规范仍可调用close() bais.close(); }}运行结果读取的数据H e l l o## 三、DataInputStream读取基本数据类型### 3.1 为什么需要DataInputStreamByteArrayInputStream只能读取原始字节但实际开发中我们需要读取整数、浮点数、字符串等。DataInputStream就像一个翻译官将原始字节转换为Java基本数据类型。### 3.2 装饰者模式设计DataInputStream使用了装饰者模式它包装另一个InputStream增加新功能而不改变原类。它的构造函数接收一个InputStream参数javaDataInputStream dis new DataInputStream(new ByteArrayInputStream(data));### 3.3 源码核心方法分析javapublic final class DataInputStream extends FilterInputStream implements DataInput { // 读取int4字节大端序 public final int readInt() throws IOException { int ch1 in.read(); int ch2 in.read(); int ch3 in.read(); int ch4 in.read(); // 组合成32位整数 return ((ch1 24) (ch2 16) (ch3 8) ch4); } // 读取UTF-8字符串 public final String readUTF() throws IOException { // 先读取长度2字节 int length readUnsignedShort(); // 读取指定字节数并解码 byte[] bytearr new byte[length]; readFully(bytearr); return new String(bytearr, UTF-8); }}关键点- 所有方法都是final不能重写- 使用大端字节序网络字节序- readUTF()使用改良的UTF-8格式## 四、综合实战二进制数据读写### 4.1 完整示例写入和读取二进制数据java// 示例2综合使用ByteArrayOutputStream和DataOutputStream写入// 再用ByteArrayInputStream和DataInputStream读取import java.io.*;public class BinaryDataDemo { public static void main(String[] args) throws IOException { // 写入数据 ByteArrayOutputStream baos new ByteArrayOutputStream(); DataOutputStream dos new DataOutputStream(baos); // 写入不同类型的数据 dos.writeInt(2024); // 4字节整数 dos.writeFloat(3.14159f); // 4字节浮点数 dos.writeUTF(JavaIO); // 2字节长度 UTF-8编码 dos.writeBoolean(true); // 1字节布尔值 // 获取字节数组 byte[] binaryData baos.toByteArray(); System.out.println(写入的总字节数 binaryData.length); // 输出写入的总字节数19 // (4 4 (26) 1 17? 实际UTF-8编码每个字符1字节共19) // 关闭写入流 dos.close(); // 读取数据 ByteArrayInputStream bais new ByteArrayInputStream(binaryData); DataInputStream dis new DataInputStream(bais); // 按相同顺序读取 int number dis.readInt(); float pi dis.readFloat(); String text dis.readUTF(); boolean flag dis.readBoolean(); System.out.println(读取结果); System.out.println(整数 number); // 2024 System.out.println(浮点数 pi); // 3.14159 System.out.println(字符串 text); // JavaIO System.out.println(布尔值 flag); // true // 关闭读取流 dis.close(); }}运行结果写入的总字节数19读取结果整数2024浮点数3.14159字符串JavaIO布尔值true### 4.2 进阶技巧跳过和标记java// 演示skip()和available()方法ByteArrayInputStream bais new ByteArrayInputStream( new byte[]{10, 20, 30, 40, 50});DataInputStream dis new DataInputStream(bais);System.out.println(可用字节数 dis.available()); // 5dis.skip(2); // 跳过前2个字节System.out.println(读取第三个字节 dis.readByte()); // 30## 五、性能考虑和最佳实践### 5.1 ByteArrayInputStream的优势-零拷贝不复制底层数组内存效率高-无需关闭不占用系统资源-可重复使用通过reset()回到起始位置### 5.2 使用建议1.小数据量1MB优先使用ByteArrayInputStream2.大数据量考虑使用文件流或缓冲流3.线程安全ByteArrayInputStream方法已同步4.避免修改不要修改传入的数组否则流数据会变化## 六、总结通过本文的学习我们深入理解了1.ByteArrayInputStream将字节数组包装为输入流实现内存数据读取源码核心是维护数组引用和位置指针2.DataInputStream装饰者模式的典型应用为字节流添加读取基本数据类型和字符串的能力3.配合使用通过内存流数据流可以在不接触文件的情况下完成二进制数据的序列化和反序列化这两个类在以下场景特别有用- 网络协议解析读取TCP数据包- 配置文件解析- 内存数据缓存- 单元测试模拟输入流记住理解源码能帮你写出更高效的代码。当你知道ByteArrayInputStream不会复制数组时就会避免修改传入的数组当你知道DataInputStream使用大端序时就能与C/C程序正确交互。掌握这些底层知识是成为高级Java工程师的必经之路。